// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 MediaTek Inc. */ #define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_COMPAT #include #endif #include "flashlight-core.h" #ifdef CONFIG_MTK_FLASHLIGHT_PT #include "mtk_battery_oc_throttling.h" #include "mtk_low_battery_throttling.h" #include "mtk_bp_thl.h" #endif #ifdef CONFIG_MTK_FLASHLIGHT_DLPT #include "mtk_pbm.h" /* DLPT */ #endif /****************************************************************************** * Definition *****************************************************************************/ static DEFINE_MUTEX(fl_mutex); LIST_HEAD(flashlight_list); /* duty current */ static struct flashlight_arg duty_current_arg; /* power variables */ #ifdef CONFIG_MTK_FLASHLIGHT_PT static int pt_low_vol = LOW_BATTERY_LEVEL_0; static int pt_low_bat = BATTERY_PERCENT_LEVEL_0; static int pt_over_cur = BATTERY_OC_LEVEL_0; static int max_pt_low_vol = LOW_BATTERY_LEVEL_2; #ifdef CONFIG_MTK_FLASHLIGHT_PT_STRICT static int pt_strict = 1; #else static int pt_strict; /* always be zero in C standard */ #endif static int pt_is_low(int pt_low_vol, int pt_low_bat, int pt_over_cur); #endif /****************************************************************************** * Flashlight operations *****************************************************************************/ static int fl_set_level(struct flashlight_dev *fdev, int level) { struct flashlight_dev_arg fl_dev_arg; if (!fdev || !fdev->ops) { pr_info("Failed with no flashlight ops\n"); return -EINVAL; } /* if pt is low */ #ifdef CONFIG_MTK_FLASHLIGHT_PT if (pt_is_low(pt_low_vol, pt_low_bat, pt_over_cur)) if (fdev->low_pt_level >= 0 && level > fdev->low_pt_level) { level = fdev->low_pt_level; pr_info("Set level to (%d) since pt(%d,%d,%d), pt strict(%d)\n", level, pt_low_vol, pt_low_bat, pt_over_cur, pt_strict); } #endif /* ioctl */ fl_dev_arg.channel = fdev->dev_id.channel; fl_dev_arg.arg = level; if (fdev->ops->flashlight_ioctl(FLASH_IOC_SET_DUTY, (unsigned long)&fl_dev_arg)) { pr_info("Failed to set level\n"); return -EFAULT; } /* update device status */ fdev->level = level; return 0; } static int fl_enable(struct flashlight_dev *fdev, int enable) { struct flashlight_dev_arg fl_dev_arg; if (!fdev || !fdev->ops) { pr_info("Failed with no flashlight ops\n"); return -EINVAL; } /* consider pt status */ #ifdef CONFIG_MTK_FLASHLIGHT_DLPT kicker_pbm_by_flash(enable); #endif #ifdef CONFIG_MTK_FLASHLIGHT_PT if (pt_is_low(pt_low_vol, pt_low_bat, pt_over_cur) == 2) if (enable) { enable = 0; pr_info("Failed to enable since pt(%d,%d,%d), pt strict(%d)\n", pt_low_vol, pt_low_bat, pt_over_cur, pt_strict); } #endif if (fdev->sw_disable_status == FLASHLIGHT_SW_DISABLE_ON) { pr_info("Sw disable on\n"); return 0; } /* ioctl */ fl_dev_arg.channel = fdev->dev_id.channel; fl_dev_arg.arg = enable; if (fdev->ops->flashlight_ioctl(FLASH_IOC_SET_ONOFF, (unsigned long)&fl_dev_arg)) { pr_info("Failed to set on/off\n"); return -EFAULT; } /* update device status */ fdev->enable = enable; return 0; } /* verify function */ int flashlight_verify_type_index(int type_index) { if (type_index < 0 || type_index >= FLASHLIGHT_TYPE_MAX) { pr_info("type index (%d) is not valid\n", type_index); return -1; } return 0; } EXPORT_SYMBOL(flashlight_verify_type_index); int flashlight_verify_ct_index(int ct_index) { if (ct_index < 0 || ct_index >= FLASHLIGHT_CT_MAX) { pr_info("ct index (%d) is not valid\n", ct_index); return -1; } return 0; } EXPORT_SYMBOL(flashlight_verify_ct_index); int flashlight_verify_part_index(int part_index) { if (part_index < 0 || part_index >= FLASHLIGHT_PART_MAX) { pr_info("part index (%d) is not valid\n", part_index); return -1; } return 0; } EXPORT_SYMBOL(flashlight_verify_part_index); int flashlight_verify_index(int type_index, int ct_index, int part_index) { if (flashlight_verify_type_index(type_index) || flashlight_verify_ct_index(ct_index) || flashlight_verify_part_index(part_index)) return -1; return 0; } EXPORT_SYMBOL(flashlight_verify_index); static int flashlight_verify_arg(struct flashlight_arg fl_arg) { if (flashlight_verify_index(fl_arg.type, fl_arg.ct, fl_arg.part)) return -1; if (fl_arg.level < -1 || fl_arg.level > FLASHLIGHT_ARG_LEVEL_MAX) { pr_info("level (%d) is not valid\n", fl_arg.level); return -1; } if (fl_arg.dur < 0 || fl_arg.dur > FLASHLIGHT_ARG_DUR_MAX) { pr_info("duration (%d) is not valid\n", fl_arg.dur); return -1; } return 0; } /* get id */ int flashlight_get_type_id(int type_index) { if (flashlight_verify_type_index(type_index)) { pr_info("type index (%d) is not valid\n", type_index); return -1; } return type_index + 1; } EXPORT_SYMBOL(flashlight_get_type_id); int flashlight_get_ct_id(int ct_index) { if (flashlight_verify_ct_index(ct_index)) { pr_info("color temperature index (%d) is not valid\n", ct_index); return -1; } return ct_index + 1; } EXPORT_SYMBOL(flashlight_get_ct_id); int flashlight_get_part_id(int part_index) { if (flashlight_verify_part_index(part_index)) { pr_info("part (%d) is not valid\n", part_index); return -1; } return part_index + 1; } EXPORT_SYMBOL(flashlight_get_part_id); /* get index */ int flashlight_get_type_index(int type_id) { if (type_id < 1 || type_id > FLASHLIGHT_TYPE_MAX) { pr_info("type id (%d) is not valid\n", type_id); return -1; } return type_id - 1; } EXPORT_SYMBOL(flashlight_get_type_index); int flashlight_get_ct_index(int ct_id) { if (ct_id < 1 || ct_id > FLASHLIGHT_CT_MAX) { pr_info("color temperature id (%d) is not valid\n", ct_id); return -1; } return ct_id - 1; } EXPORT_SYMBOL(flashlight_get_ct_index); int flashlight_get_part_index(int part_id) { if (part_id < 1 || part_id > FLASHLIGHT_PART_MAX) { pr_info("part id (%d) is not valid\n", part_id); return -1; } return part_id - 1; } EXPORT_SYMBOL(flashlight_get_part_index); /****************************************************************************** * Flashlight devices *****************************************************************************/ /* find device */ static struct flashlight_dev *flashlight_find_dev_by_full_index( const int type, const int ct, const int part) { struct flashlight_dev *fdev; /* return the first flashlight device */ list_for_each_entry(fdev, &flashlight_list, node) { if (fdev->dev_id.type == type && fdev->dev_id.ct == ct && fdev->dev_id.part == part) return fdev; } return NULL; } static struct flashlight_dev *flashlight_find_dev_by_index( const int type, const int ct) { struct flashlight_dev *fdev; /* return the first flashlight device */ list_for_each_entry(fdev, &flashlight_list, node) { if (fdev->dev_id.type == type && fdev->dev_id.ct == ct) return fdev; } return NULL; } static struct flashlight_dev *flashlight_find_dev_by_device_id( const struct flashlight_device_id *dev_id) { struct flashlight_dev *fdev; if (!dev_id) return NULL; /* return the first flashlight device */ list_for_each_entry(fdev, &flashlight_list, node) { if (fdev->dev_id.type == dev_id->type && fdev->dev_id.ct == dev_id->ct && fdev->dev_id.part == dev_id->part) return fdev; } return NULL; } /* * Register devices * * Please DO NOT register flashlight device driver, * until success to probe hardware. */ int flashlight_dev_register( const char *name, struct flashlight_operations *dev_ops) { struct flashlight_dev *fdev; int type_index, ct_index, part_index; int i; for (i = 0; i < flashlight_device_num; i++) { if (!strncmp(name, flashlight_id[i].name, FLASHLIGHT_NAME_SIZE)) { type_index = flashlight_id[i].type; ct_index = flashlight_id[i].ct; part_index = flashlight_id[i].part; if (flashlight_verify_index( type_index, ct_index, part_index)) { pr_info("Failed to register device (%s)\n", flashlight_id[i].name); continue; } pr_info("%s %d %d %d\n", flashlight_id[i].name, type_index, ct_index, part_index); mutex_lock(&fl_mutex); fdev = kzalloc(sizeof(*fdev), GFP_KERNEL); if (!fdev) { mutex_unlock(&fl_mutex); return -ENOMEM; } fdev->ops = dev_ops; fdev->dev_id = flashlight_id[i]; fdev->low_pt_level = -1; fdev->charger_status = FLASHLIGHT_CHARGER_READY; fdev->torch_status = FLASHLIGHT_TORCH_OFF; list_add_tail(&fdev->node, &flashlight_list); mutex_unlock(&fl_mutex); } } return 0; } EXPORT_SYMBOL(flashlight_dev_register); int flashlight_dev_unregister(const char *name) { struct flashlight_dev *fdev; int type_index, ct_index, part_index; int i; for (i = 0; i < flashlight_device_num; i++) { if (!strncmp(name, flashlight_id[i].name, FLASHLIGHT_NAME_SIZE)) { type_index = flashlight_id[i].type; ct_index = flashlight_id[i].ct; part_index = flashlight_id[i].part; if (flashlight_verify_index( type_index, ct_index, part_index)) { pr_info("Failed to unregister device (%s)\n", flashlight_id[i].name); continue; } pr_info("%s %d %d %d\n", flashlight_id[i].name, type_index, ct_index, part_index); mutex_lock(&fl_mutex); fdev = flashlight_find_dev_by_device_id( &flashlight_id[i]); if (fdev) { list_del(&fdev->node); kfree(fdev); } mutex_unlock(&fl_mutex); } } return 0; } EXPORT_SYMBOL(flashlight_dev_unregister); /* * Register devices * * Please DO NOT register flashlight device driver, * until success to probe hardware. */ int flashlight_dev_register_by_device_id( struct flashlight_device_id *dev_id, struct flashlight_operations *dev_ops) { struct flashlight_dev *fdev; if (!dev_id || !dev_ops) return -EINVAL; if (flashlight_verify_index(dev_id->type, dev_id->ct, dev_id->part)) { pr_info("Failed to register device (%d,%d,%d)\n", dev_id->type, dev_id->ct, dev_id->part); return -EINVAL; } pr_info("Register device (%d,%d,%d)\n", dev_id->type, dev_id->ct, dev_id->part); mutex_lock(&fl_mutex); fdev = kzalloc(sizeof(*fdev), GFP_KERNEL); if (!fdev) { mutex_unlock(&fl_mutex); return -ENOMEM; } fdev->ops = dev_ops; fdev->dev_id = *dev_id; fdev->low_pt_level = -1; fdev->charger_status = FLASHLIGHT_CHARGER_READY; fdev->torch_status = FLASHLIGHT_TORCH_OFF; list_add_tail(&fdev->node, &flashlight_list); mutex_unlock(&fl_mutex); return 0; } EXPORT_SYMBOL(flashlight_dev_register_by_device_id); int flashlight_dev_unregister_by_device_id(struct flashlight_device_id *dev_id) { struct flashlight_dev *fdev; if (!dev_id) return -EINVAL; if (flashlight_verify_index(dev_id->type, dev_id->ct, dev_id->part)) { pr_info("Failed to unregister device (%d,%d,%d)\n", dev_id->type, dev_id->ct, dev_id->part); return -EINVAL; } pr_info("Unregister device (%d,%d,%d)\n", dev_id->type, dev_id->ct, dev_id->part); mutex_lock(&fl_mutex); fdev = flashlight_find_dev_by_device_id(dev_id); if (fdev) { list_del(&fdev->node); kfree(fdev); } mutex_unlock(&fl_mutex); return 0; } EXPORT_SYMBOL(flashlight_dev_unregister_by_device_id); /****************************************************************************** * Vsync IRQ *****************************************************************************/ /* * LED flash control for high current capture mode * which is used by "imgsensor/src/[PLAT]/kd_sensorlist.c" * * Already be removed from kernel-4.4. */ ssize_t strobe_VDIrq(void) { return 0; } EXPORT_SYMBOL(strobe_VDIrq); /****************************************************************************** * Charger Status *****************************************************************************/ static int flashlight_update_charger_status(struct flashlight_dev *fdev) { struct flashlight_dev_arg fl_dev_arg; if (!fdev || !fdev->ops) { pr_info("Failed with no flashlight ops\n"); return -EINVAL; } /* ioctl */ fl_dev_arg.channel = fdev->dev_id.channel; if (fdev->ops->flashlight_ioctl(FLASH_IOC_IS_CHARGER_READY, (unsigned long)&fl_dev_arg)) pr_info("Failed to get charger status\n"); else fdev->charger_status = fl_dev_arg.arg; return 0; } /****************************************************************************** * Power throttling *****************************************************************************/ #ifdef CONFIG_MTK_FLASHLIGHT_DLPT void flashlight_kicker_pbm(bool status) { kicker_pbm_by_flash(status); } EXPORT_SYMBOL(flashlight_kicker_pbm); #endif #ifdef CONFIG_MTK_FLASHLIGHT_PT int flashlight_pt_is_low(void) { return pt_is_low(pt_low_vol, pt_low_bat, pt_over_cur); } EXPORT_SYMBOL(flashlight_pt_is_low); static int pt_arg_verify(int pt_low_vol, int pt_low_bat, int pt_over_cur) { if (pt_low_vol < LOW_BATTERY_LEVEL_0 || pt_low_vol > LOW_BATTERY_LEVEL_3) { pr_info("PT low voltage (%d) is not valid\n", pt_low_vol); return -1; } if (pt_low_bat < BATTERY_PERCENT_LEVEL_0 || pt_low_bat > BATTERY_PERCENT_LEVEL_1) { pr_info("PT low battery (%d) is not valid\n", pt_low_bat); return -1; } if (pt_over_cur < BATTERY_OC_LEVEL_0 || pt_over_cur > BATTERY_OC_LEVEL_1) { pr_info("PT over current (%d) is not valid\n", pt_over_cur); return -1; } return 0; } static int pt_is_low(int pt_low_vol, int pt_low_bat, int pt_over_cur) { int is_low = 0; if (max_pt_low_vol == LOW_BATTERY_LEVEL_3) { if (pt_low_vol == LOW_BATTERY_LEVEL_2 || pt_low_vol == LOW_BATTERY_LEVEL_3) { is_low = 1; if (pt_strict) is_low = 2; } } else { if (pt_low_vol != LOW_BATTERY_LEVEL_0) { is_low = 1; if (pt_strict) is_low = 2; } } if (pt_low_bat != BATTERY_PERCENT_LEVEL_0 || pt_over_cur != BATTERY_OC_LEVEL_0) { is_low = 1; if (pt_strict) is_low = 2; } return 0;/*prize modify by zhuzhengjiang for low power,flashlight not effect*/ //return is_low; } static int pt_trigger(void) { struct flashlight_dev *fdev; return 0;/*prize remove by zhuzhengjiang for flashlight can open when low power*/ mutex_lock(&fl_mutex); list_for_each_entry(fdev, &flashlight_list, node) { if (!fdev->ops) continue; fdev->ops->flashlight_open(); fdev->ops->flashlight_set_driver(1); if (pt_strict) { pr_info_ratelimited("PT trigger(%d,%d,%d) disable flashlight\n", pt_low_vol, pt_low_bat, pt_over_cur); fl_enable(fdev, 0); } else { pr_info_ratelimited("PT trigger(%d,%d,%d) decrease duty: %d\n", pt_low_vol, pt_low_bat, pt_over_cur, fdev->low_pt_level); fl_set_level(fdev, fdev->low_pt_level); } fdev->ops->flashlight_set_driver(0); fdev->ops->flashlight_release(); } mutex_unlock(&fl_mutex); return 0; } static void pt_low_vol_callback(enum LOW_BATTERY_LEVEL_TAG level) { if (level == LOW_BATTERY_LEVEL_0) { pt_low_vol = LOW_BATTERY_LEVEL_0; } else if (level == LOW_BATTERY_LEVEL_1) { pt_low_vol = LOW_BATTERY_LEVEL_1; if (max_pt_low_vol == LOW_BATTERY_LEVEL_2) pt_trigger(); } else if (level == LOW_BATTERY_LEVEL_2) { pt_low_vol = LOW_BATTERY_LEVEL_2; pt_trigger(); } else if (level == LOW_BATTERY_LEVEL_3) { pt_low_vol = LOW_BATTERY_LEVEL_3; pt_trigger(); } else { /* unlimited cpu and gpu */ } } static void pt_low_bat_callback(enum BATTERY_PERCENT_LEVEL_TAG level) { if (level == BATTERY_PERCENT_LEVEL_0) { pt_low_bat = BATTERY_PERCENT_LEVEL_0; } else if (level == BATTERY_PERCENT_LEVEL_1) { pt_low_bat = BATTERY_PERCENT_LEVEL_1; pt_trigger(); } else { /* unlimited cpu and gpu*/ } } static void pt_oc_callback(enum BATTERY_OC_LEVEL_TAG level) { if (level == BATTERY_OC_LEVEL_0) { pt_over_cur = BATTERY_OC_LEVEL_0; } else if (level == BATTERY_OC_LEVEL_1) { pt_over_cur = BATTERY_OC_LEVEL_1; pt_trigger(); } else { /* unlimited cpu and gpu*/ } } #endif /****************************************************************************** * File operations *****************************************************************************/ static long _flashlight_ioctl( struct file *file, unsigned int cmd, unsigned long arg) { struct flashlight_user_arg fl_arg; struct flashlight_dev_arg fl_dev_arg; struct flashlight_dev *fdev; int type, ct, part; int ret = 0; memset(&fl_arg, 0, sizeof(struct flashlight_user_arg)); if (copy_from_user(&fl_arg, (void __user *)arg, sizeof(struct flashlight_user_arg))) { pr_info("Failed copy arguments from user\n"); return -EFAULT; } /* find flashlight device */ mutex_lock(&fl_mutex); fdev = flashlight_find_dev_by_index( flashlight_get_type_index(fl_arg.type_id), flashlight_get_ct_index(fl_arg.ct_id)); mutex_unlock(&fl_mutex); if (!fdev) { pr_info_ratelimited("Find no flashlight device\n"); return -EINVAL; } /* setup flash dev arguments */ fl_dev_arg.arg = fl_arg.arg; fl_dev_arg.channel = fdev->dev_id.channel; type = fdev->dev_id.type; ct = fdev->dev_id.ct; part = fdev->dev_id.part; if (flashlight_verify_index(type, ct, part)) { pr_info("Failed with error index\n"); return -EINVAL; } switch (cmd) { case FLASH_IOC_GET_PROTOCOL_VERSION: pr_debug("FLASH_IOC_GET_PROTOCOL_VERSION(%d,%d,%d): %d\n", type, ct, part, FLASHLIGHT_PROTOCOL_VERSION); ret = FLASHLIGHT_PROTOCOL_VERSION; break; case FLASH_IOC_IS_LOW_POWER: fl_arg.arg = 0; #ifdef CONFIG_MTK_FLASHLIGHT_PT fl_arg.arg = pt_is_low(pt_low_vol, pt_low_bat, pt_over_cur); if (fl_arg.arg) pr_debug("Pt status: (%d,%d,%d)\n", pt_low_vol, pt_low_bat, pt_over_cur); #endif if (copy_to_user((void __user *)arg, (void *)&fl_arg, sizeof(struct flashlight_user_arg))) { pr_info("Failed to copy power status to user\n"); return -EFAULT; } break; case FLASH_IOC_LOW_POWER_DETECT_START: pr_debug("FLASH_IOC_LOW_POWER_DETECT_START(%d,%d,%d): %d\n", type, ct, part, fl_arg.arg); mutex_lock(&fl_mutex); fdev->low_pt_level = fl_arg.arg; mutex_unlock(&fl_mutex); break; case FLASH_IOC_LOW_POWER_DETECT_END: pr_debug("FLASH_IOC_LOW_POWER_DETECT_END(%d,%d,%d)\n", type, ct, part); mutex_lock(&fl_mutex); fdev->low_pt_level = -1; mutex_unlock(&fl_mutex); break; case FLASH_IOC_IS_CHARGER_READY: mutex_lock(&fl_mutex); flashlight_update_charger_status(fdev); mutex_unlock(&fl_mutex); fl_arg.arg = fdev->charger_status; pr_debug("FLASH_IOC_IS_CHARGER_READY(%d,%d,%d): %d\n", type, ct, part, fl_arg.arg); if (copy_to_user((void __user *)arg, (void *)&fl_arg, sizeof(struct flashlight_user_arg))) { pr_info("Failed to copy charger status to user\n"); return -EFAULT; } break; case FLASH_IOC_IS_HARDWARE_READY: if (fdev->ops) fl_arg.arg = 1; else fl_arg.arg = 0; pr_debug("FLASH_IOC_IS_HARDWARE_READY(%d,%d,%d): %d\n", type, ct, part, fl_arg.arg); if (copy_to_user((void __user *)arg, (void *)&fl_arg, sizeof(struct flashlight_user_arg))) { pr_info("Failed to copy hardware status to user\n"); return -EFAULT; } break; case FLASHLIGHTIOC_X_SET_DRIVER: pr_debug("FLASHLIGHTIOC_X_SET_DRIVER(%d,%d,%d): %d\n", type, ct, part, fl_arg.arg); if (fdev->ops) { ret = fdev->ops->flashlight_set_driver(fl_arg.arg); if (fdev->dev_id.decouple) { fl_dev_arg.arg = FLASHLIGHT_SCENARIO_DECOUPLE; fdev->ops->flashlight_ioctl( FLASH_IOC_SET_SCENARIO, (unsigned long)&fl_dev_arg); } } else { pr_info("Failed with no flashlight ops\n"); return -EFAULT; } break; case FLASH_IOC_SET_SCENARIO: pr_debug("FLASH_IOC_SET_SCENARIO(%d,%d,%d): %d\n", type, ct, part, fl_arg.arg); if (fdev->ops) { if (fdev->dev_id.decouple) fl_dev_arg.arg |= FLASHLIGHT_SCENARIO_DECOUPLE; ret = fdev->ops->flashlight_ioctl( cmd, (unsigned long)&fl_dev_arg); } else { pr_info("Failed with no flashlight ops\n"); return -EFAULT; } break; case FLASH_IOC_GET_PART_ID: case FLASH_IOC_GET_MAIN_PART_ID: case FLASH_IOC_GET_SUB_PART_ID: case FLASH_IOC_GET_MAIN2_PART_ID: fl_arg.arg = flashlight_get_part_id(part); pr_debug("FLASH_IOC_GET_PART_ID(%d,%d,%d): %d\n", type, ct, part, fl_arg.arg); if (copy_to_user((void __user *)arg, (void *)&fl_arg, sizeof(struct flashlight_user_arg))) { pr_info("Failed to copy part id to user\n"); return -EFAULT; } break; case FLASH_IOC_SET_DUTY: pr_debug("FLASH_IOC_SET_DUTY(%d,%d,%d): %d\n", type, ct, part, fl_arg.arg); mutex_lock(&fl_mutex); ret = fl_set_level(fdev, fl_arg.arg); mutex_unlock(&fl_mutex); break; case FLASH_IOC_SET_ONOFF: pr_debug("FLASH_IOC_SET_ONOFF(%d,%d,%d): %d\n", type, ct, part, fl_arg.arg); mutex_lock(&fl_mutex); ret = fl_enable(fdev, fl_arg.arg); mutex_unlock(&fl_mutex); break; case FLASH_IOC_GET_DUTY_NUMBER: case FLASH_IOC_GET_DUTY_CURRENT: case FLASH_IOC_GET_HW_FAULT: case FLASH_IOC_GET_HW_FAULT2: if (fdev->ops) { ret = fdev->ops->flashlight_ioctl( cmd, (unsigned long)&fl_dev_arg); fl_arg.arg = fl_dev_arg.arg; if (copy_to_user((void __user *)arg, (void *)&fl_arg, sizeof(struct flashlight_user_arg))) { pr_info("Failed to copy arg to user cmd:%d\n", _IOC_NR(cmd)); return -EFAULT; } } else { pr_info("Failed with no flashlight ops\n"); return -ENOTTY; } break; default: if (fdev->ops) ret = fdev->ops->flashlight_ioctl( cmd, (unsigned long)&fl_dev_arg); else { pr_info("Failed with no flashlight ops\n"); return -ENOTTY; } break; } return ret; } static long flashlight_ioctl( struct file *file, unsigned int cmd, unsigned long arg) { return _flashlight_ioctl(file, cmd, arg); } #ifdef CONFIG_COMPAT static long flashlight_compat_ioctl( struct file *filep, unsigned int cmd, unsigned long arg) { return _flashlight_ioctl(filep, cmd, (unsigned long)compat_ptr(arg)); } #endif static int flashlight_open(struct inode *inode, struct file *file) { struct flashlight_dev *fdev; mutex_lock(&fl_mutex); list_for_each_entry(fdev, &flashlight_list, node) { if (!fdev->ops) continue; pr_debug("Open(%d,%d,%d)\n", fdev->dev_id.type, fdev->dev_id.ct, fdev->dev_id.part); fdev->ops->flashlight_open(); } mutex_unlock(&fl_mutex); return 0; } static int flashlight_release(struct inode *inode, struct file *file) { struct flashlight_dev *fdev; mutex_lock(&fl_mutex); list_for_each_entry(fdev, &flashlight_list, node) { if (!fdev->ops) continue; pr_debug("Release(%d,%d,%d)\n", fdev->dev_id.type, fdev->dev_id.ct, fdev->dev_id.part); fdev->ops->flashlight_release(); } mutex_unlock(&fl_mutex); return 0; } static const struct file_operations flashlight_fops = { .owner = THIS_MODULE, .unlocked_ioctl = flashlight_ioctl, .open = flashlight_open, .release = flashlight_release, #ifdef CONFIG_COMPAT .compat_ioctl = flashlight_compat_ioctl, #endif }; /****************************************************************************** * SYSFS *****************************************************************************/ /* flashlight strobe sysfs */ static ssize_t flashlight_strobe_show(struct device *dev, struct device_attribute *attr, char *buf) { pr_debug("Strobe show\n"); return scnprintf(buf, PAGE_SIZE, "[TYPE] [CT] [PART] [LEVEL] [DURATION(ms)]\n"); } static ssize_t flashlight_strobe_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct flashlight_dev *fdev; struct flashlight_arg fl_arg; s32 num; int count = 0; char delim[] = " "; char *token, *cur = (char *)buf; int ret; pr_debug("Strobe store\n"); while (cur) { token = strsep(&cur, delim); ret = kstrtos32(token, 10, &num); if (ret) { pr_info("Error arguments\n"); goto unlock; } if (count == FLASHLIGHT_ARG_TYPE) fl_arg.type = (int)num; else if (count == FLASHLIGHT_ARG_CT) fl_arg.ct = (int)num; else if (count == FLASHLIGHT_ARG_PART) fl_arg.part = (int)num; else if (count == FLASHLIGHT_ARG_LEVEL) fl_arg.level = (int)num; else if (count == FLASHLIGHT_ARG_DUR) fl_arg.dur = (int)num; else { count++; break; } count++; } /* verify data */ if (count != FLASHLIGHT_ARG_NUM) { pr_info("Error argument number: (%d)\n", count); ret = -1; goto unlock; } if (flashlight_verify_arg(fl_arg)) { pr_info("Error arguments\n"); ret = -1; goto unlock; } pr_debug("(%d, %d, %d), (%d, %d)\n", fl_arg.type, fl_arg.ct, fl_arg.part, fl_arg.level, fl_arg.dur); /* call callback function */ mutex_lock(&fl_mutex); fdev = flashlight_find_dev_by_full_index( fl_arg.type, fl_arg.ct, fl_arg.part); mutex_unlock(&fl_mutex); if (!fdev) { pr_info("Find no flashlight device\n"); ret = -1; goto unlock; } fl_arg.channel = fdev->dev_id.channel; fl_arg.decouple = fdev->dev_id.decouple; pr_info("channel:%d decouple:%d\n", fl_arg.channel, fl_arg.decouple); if (fdev->ops) { fdev->ops->flashlight_strobe_store(fl_arg); ret = size; } else { pr_info("Failed with no flashlight ops\n"); ret = -1; } unlock: return ret; } static DEVICE_ATTR_RW(flashlight_strobe); /* pt status sysfs */ static ssize_t flashlight_pt_show(struct device *dev, struct device_attribute *attr, char *buf) { pr_debug("Power throttling show\n"); #ifdef CONFIG_MTK_FLASHLIGHT_PT return scnprintf(buf, PAGE_SIZE, "[LOW_VOLTAGE] [LOW_BATTERY] [OVER_CURRENT] [PT_STRICT]\n%d %d %d %d\n", pt_low_vol, pt_low_bat, pt_over_cur, pt_strict); #else return scnprintf(buf, PAGE_SIZE, "No support power throttling\n"); #endif } static ssize_t flashlight_pt_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { int low_vol = 0; int low_bat = 0; int over_cur = 0; int strict = 1; u32 num; int count = 0; char delim[] = " "; char *token, *cur = (char *)buf; int ret; pr_debug("Power throttling store\n"); while (cur) { token = strsep(&cur, delim); ret = kstrtou32(token, 10, &num); if (ret) { pr_info("Error arguments\n"); goto unlock; } if (count == PT_NOTIFY_LOW_VOL) low_vol = (int)num; else if (count == PT_NOTIFY_LOW_BAT) low_bat = (int)num; else if (count == PT_NOTIFY_OVER_CUR) over_cur = (int)num; else if (count == PT_NOTIFY_STRICT) strict = (int)num; else { count++; break; } count++; } /* verify data */ if (count != PT_NOTIFY_NUM) { pr_info("Error argument number: (%d)\n", count); ret = -1; goto unlock; } #ifdef CONFIG_MTK_FLASHLIGHT_PT if (pt_arg_verify(low_vol, low_bat, over_cur) || strict < 0 || strict > 1) { pr_info("Error arguments\n"); ret = -1; goto unlock; } pr_debug("PT status (%d, %d, %d) with strict(%d)\n", low_vol, low_bat, over_cur, strict); /* call callback function */ pt_strict = strict; pt_low_vol_callback(low_vol); pt_low_bat_callback(low_bat); pt_oc_callback(over_cur); #endif ret = size; unlock: return ret; } static DEVICE_ATTR_RW(flashlight_pt); /* charger status sysfs */ static ssize_t flashlight_charger_show( struct device *dev, struct device_attribute *attr, char *buf) { struct flashlight_dev *fdev; char status[FLASHLIGHT_CHARGER_STATUS_BUF_SIZE]; char status_tmp[FLASHLIGHT_CHARGER_STATUS_TMPBUF_SIZE]; int ret; pr_debug("Charger status show\n"); memset(status, '\0', FLASHLIGHT_CHARGER_STATUS_BUF_SIZE); mutex_lock(&fl_mutex); list_for_each_entry(fdev, &flashlight_list, node) { if (!fdev->ops) continue; flashlight_update_charger_status(fdev); ret = snprintf(status_tmp, FLASHLIGHT_CHARGER_STATUS_TMPBUF_SIZE, "%d %d %d %d\n", fdev->dev_id.type, fdev->dev_id.ct, fdev->dev_id.part, fdev->charger_status); if (ret < 0) pr_info("snprintf failed\n"); strncat(status, status_tmp, FLASHLIGHT_CHARGER_STATUS_TMPBUF_SIZE); } mutex_unlock(&fl_mutex); return scnprintf(buf, PAGE_SIZE, "[TYPE] [CT] [PART] [CHARGER_STATUS]\n%s\n", status); } static ssize_t flashlight_charger_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct flashlight_dev *fdev; struct flashlight_arg fl_arg; int charger_status_tmp = 0; s32 num; int count = 0; char delim[] = " "; char *token, *cur = (char *)buf; int ret; pr_debug("Charger status store\n"); memset(&fl_arg, 0, sizeof(struct flashlight_arg)); while (cur) { token = strsep(&cur, delim); ret = kstrtos32(token, 10, &num); if (ret) { pr_info("Error arguments\n"); goto unlock; } if (count == FLASHLIGHT_CHARGER_TYPE) fl_arg.type = (int)num; else if (count == FLASHLIGHT_CHARGER_CT) fl_arg.ct = (int)num; else if (count == FLASHLIGHT_CHARGER_PART) fl_arg.part = (int)num; else if (count == FLASHLIGHT_CHARGER_STATUS) charger_status_tmp = (int)num; else { count++; break; } count++; } /* verify data */ if (count != FLASHLIGHT_CHARGER_NUM) { pr_info("Error argument number: (%d)\n", count); ret = -1; goto unlock; } if (flashlight_verify_index(fl_arg.type, fl_arg.ct, fl_arg.part)) { pr_info("Error arguments\n"); ret = -1; goto unlock; } if (charger_status_tmp < FLASHLIGHT_CHARGER_NOT_READY || charger_status_tmp > FLASHLIGHT_CHARGER_READY) { pr_info("Error arguments charger status(%d)\n", charger_status_tmp); ret = -1; goto unlock; } pr_debug("(%d, %d, %d), (%d)\n", fl_arg.type, fl_arg.ct, fl_arg.part, charger_status_tmp); /* store charger status */ mutex_lock(&fl_mutex); fdev = flashlight_find_dev_by_full_index( fl_arg.type, fl_arg.ct, fl_arg.part); mutex_unlock(&fl_mutex); if (!fdev) { pr_info("Find no flashlight device\n"); ret = -1; goto unlock; } fdev->charger_status = charger_status_tmp; ret = size; unlock: return ret; } static DEVICE_ATTR_RW(flashlight_charger); /* torch status sysfs */ static ssize_t flashlight_torch_show( struct device *dev, struct device_attribute *attr, char *buf) { struct flashlight_dev *fdev; char status[FLASHLIGHT_TORCH_STATUS_BUF_SIZE]; char status_tmp[FLASHLIGHT_TORCH_STATUS_TMPBUF_SIZE]; int ret; pr_debug("Torch status show\n"); memset(status, '\0', FLASHLIGHT_TORCH_STATUS_BUF_SIZE); mutex_lock(&fl_mutex); list_for_each_entry(fdev, &flashlight_list, node) { if (!fdev->ops) continue; ret = snprintf(status_tmp, FLASHLIGHT_TORCH_STATUS_TMPBUF_SIZE, "%d %d %d %d\n", fdev->dev_id.type, fdev->dev_id.ct, fdev->dev_id.part, fdev->torch_status); if (ret < 0) pr_info("snprintf failed\n"); strncat(status, status_tmp, FLASHLIGHT_TORCH_STATUS_TMPBUF_SIZE); } mutex_unlock(&fl_mutex); return scnprintf(buf, PAGE_SIZE, "[TYPE] [CT] [PART] [TORCH_STATUS]\n%s\n", status); } static ssize_t flashlight_torch_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct flashlight_dev *fdev; struct flashlight_arg fl_arg; int torch_status_tmp = 0; s32 num; int count = 0; char delim[] = " "; char *token, *cur = (char *)buf; int ret; pr_debug("Torch status store\n"); memset(&fl_arg, 0, sizeof(struct flashlight_arg)); while (cur) { token = strsep(&cur, delim); ret = kstrtos32(token, 10, &num); if (ret) { pr_info("Error arguments\n"); goto unlock; } if (count == FLASHLIGHT_TORCH_TYPE) fl_arg.type = (int)num; else if (count == FLASHLIGHT_TORCH_CT) fl_arg.ct = (int)num; else if (count == FLASHLIGHT_TORCH_PART) fl_arg.part = (int)num; else if (count == FLASHLIGHT_TORCH_STATUS) torch_status_tmp = (int)num; else { count++; break; } count++; } /* verify data */ if (count != FLASHLIGHT_TORCH_NUM) { pr_info("Error argument number: (%d)\n", count); ret = -1; goto unlock; } if (flashlight_verify_index(fl_arg.type, fl_arg.ct, fl_arg.part)) { pr_info("Error arguments\n"); ret = -1; goto unlock; } if (torch_status_tmp < FLASHLIGHT_TORCH_OFF || torch_status_tmp > FLASHLIGHT_TORCH_ON) { pr_info("Error arguments torch status(%d)\n", torch_status_tmp); ret = -1; goto unlock; } pr_debug("(%d, %d, %d), (%d)\n", fl_arg.type, fl_arg.ct, fl_arg.part, torch_status_tmp); /* store torch status */ mutex_lock(&fl_mutex); fdev = flashlight_find_dev_by_full_index( fl_arg.type, fl_arg.ct, fl_arg.part); mutex_unlock(&fl_mutex); if (!fdev) { pr_info("Find no flashlight device\n"); ret = -1; goto unlock; } pr_info("torch status:%d\n", fdev->torch_status); if (fdev->ops && (fdev->torch_status != torch_status_tmp)) { if (torch_status_tmp) { fdev->ops->flashlight_open(); fdev->ops->flashlight_set_driver(1); fl_enable(fdev, 1); } else { fl_enable(fdev, 0); fdev->ops->flashlight_set_driver(0); fdev->ops->flashlight_release(); } } fdev->torch_status = torch_status_tmp; ret = size; unlock: return ret; } static DEVICE_ATTR_RW(flashlight_torch); /* flashlight capability sysfs */ static ssize_t flashlight_capability_show( struct device *dev, struct device_attribute *attr, char *buf) { struct flashlight_dev *fdev; struct flashlight_dev_arg fl_dev_arg; /* flashlight capability */ int hw_timeout; int max_duty; int max_torch_duty; char capability[FLASHLIGHT_CAPABILITY_BUF_SIZE]; char capability_tmp[FLASHLIGHT_CAPABILITY_TMPBUF_SIZE]; int ret; pr_debug("Capability show\n"); memset(capability, '\0', FLASHLIGHT_CAPABILITY_BUF_SIZE); mutex_lock(&fl_mutex); list_for_each_entry(fdev, &flashlight_list, node) { if (!fdev->ops) continue; fl_dev_arg.channel = fdev->dev_id.channel; fl_dev_arg.arg = -1; fdev->ops->flashlight_ioctl(FLASH_IOC_GET_HW_TIMEOUT, (unsigned long)&fl_dev_arg); hw_timeout = fl_dev_arg.arg; fl_dev_arg.arg = -1; fdev->ops->flashlight_ioctl(FLASH_IOC_GET_DUTY_NUMBER, (unsigned long)&fl_dev_arg); max_duty = fl_dev_arg.arg - 1; fl_dev_arg.arg = -1; fdev->ops->flashlight_ioctl(FLASH_IOC_GET_MAX_TORCH_DUTY, (unsigned long)&fl_dev_arg); max_torch_duty = fl_dev_arg.arg; ret = snprintf(capability_tmp, FLASHLIGHT_CAPABILITY_TMPBUF_SIZE, "%d %d %d %s %d %d %d %d %d\n", fdev->dev_id.type, fdev->dev_id.ct, fdev->dev_id.part, fdev->dev_id.name, fdev->dev_id.channel, fdev->dev_id.decouple, hw_timeout, max_duty, max_torch_duty); if (ret < 0) pr_info("snprintf failed\n"); strncat(capability, capability_tmp, FLASHLIGHT_CAPABILITY_TMPBUF_SIZE); } mutex_unlock(&fl_mutex); return scnprintf(buf, PAGE_SIZE, "[TYPE] [CT] [PART] [DEVICE] [CHANNEL] [DECOUPLE] [HW TIMEOUT] [MAX DUTY] [MAX TORCH DUTY]\n%s\n", capability); } static DEVICE_ATTR_RO(flashlight_capability); /* flashlight current sysfs */ static ssize_t flashlight_current_show( struct device *dev, struct device_attribute *attr, char *buf) { struct flashlight_dev *fdev; struct flashlight_dev_arg fl_dev_arg; int i, ret; /* flashlight current */ int duty_num = 0; char duty_current_tmp[FLASHLIGHT_DUTY_CURRENT_TMPBUF_SIZE]; char duty_current[FLASHLIGHT_DUTY_CURRENT_BUF_SIZE]; pr_debug("Current show\n"); memset(duty_current, '\0', FLASHLIGHT_DUTY_CURRENT_BUF_SIZE); mutex_lock(&fl_mutex); fdev = flashlight_find_dev_by_full_index(duty_current_arg.type, duty_current_arg.ct, duty_current_arg.part); mutex_unlock(&fl_mutex); if (fdev && fdev->ops) { fl_dev_arg.channel = fdev->dev_id.channel; fl_dev_arg.arg = -1; fdev->ops->flashlight_ioctl(FLASH_IOC_GET_DUTY_NUMBER, (unsigned long)&fl_dev_arg); duty_num = fl_dev_arg.arg; ret = snprintf(duty_current, FLASHLIGHT_DUTY_CURRENT_BUF_SIZE, "%d %d %d %d ", fdev->dev_id.type, fdev->dev_id.ct, fdev->dev_id.part, duty_num); if (ret < 0) pr_info("snprintf failed\n"); for (i = 0; i < duty_num; i++) { fl_dev_arg.arg = i; if (fdev->ops->flashlight_ioctl( FLASH_IOC_GET_DUTY_CURRENT, (unsigned long)&fl_dev_arg)) break; snprintf(duty_current_tmp, FLASHLIGHT_DUTY_CURRENT_TMPBUF_SIZE, "%d,", fl_dev_arg.arg); strncat(duty_current, duty_current_tmp, FLASHLIGHT_DUTY_CURRENT_TMPBUF_SIZE); } duty_current[strlen(duty_current) - 1] = '\0'; } else { ret = snprintf(duty_current, FLASHLIGHT_DUTY_CURRENT_BUF_SIZE, "%d %d %d %d ", duty_current_arg.type, duty_current_arg.ct, duty_current_arg.part, duty_num); if (ret < 0) pr_info("snprintf failed\n"); } return scnprintf(buf, PAGE_SIZE, "[TYPE] [CT] [PART] [DUTY NUM] [DUTY CURRENT]\n%s\n", duty_current); } static ssize_t flashlight_current_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct flashlight_arg fl_arg; s32 num; int count = 0; char delim[] = " "; char *token, *cur = (char *)buf; int ret; pr_debug("Current store\n"); memset(&fl_arg, 0, sizeof(struct flashlight_arg)); while (cur) { token = strsep(&cur, delim); ret = kstrtos32(token, 10, &num); if (ret) { pr_info("Error arguments\n"); goto unlock; } if (count == FLASHLIGHT_CURRENT_TYPE) fl_arg.type = (int)num; else if (count == FLASHLIGHT_CURRENT_CT) fl_arg.ct = (int)num; else if (count == FLASHLIGHT_CURRENT_PART) fl_arg.part = (int)num; else { count++; break; } count++; } /* verify data */ if (count != FLASHLIGHT_CURRENT_NUM) { pr_info("Error argument number: (%d)\n", count); ret = -1; goto unlock; } if (flashlight_verify_index(fl_arg.type, fl_arg.ct, fl_arg.part)) { pr_info("Error arguments\n"); ret = -1; goto unlock; } pr_debug("(%d, %d, %d)\n", fl_arg.type, fl_arg.ct, fl_arg.part); /* store duty current */ duty_current_arg = fl_arg; ret = size; unlock: return ret; } static DEVICE_ATTR_RW(flashlight_current); /* flashlight fault sysfs */ static ssize_t flashlight_fault_show( struct device *dev, struct device_attribute *attr, char *buf) { struct flashlight_dev *fdev; struct flashlight_dev_arg fl_dev_arg; /* flashlight capability */ int fault_flag1; int fault_flag2; char fault[FLASHLIGHT_FAULT_BUF_SIZE]; char fault_tmp[FLASHLIGHT_FAULT_TMPBUF_SIZE]; int ret; pr_debug("Fault show\n"); memset(fault, '\0', FLASHLIGHT_FAULT_BUF_SIZE); mutex_lock(&fl_mutex); list_for_each_entry(fdev, &flashlight_list, node) { if (!fdev->ops) continue; fl_dev_arg.channel = fdev->dev_id.channel; fl_dev_arg.arg = -1; fdev->ops->flashlight_ioctl(FLASH_IOC_GET_HW_FAULT, (unsigned long)&fl_dev_arg); fault_flag1 = fl_dev_arg.arg; fl_dev_arg.arg = -1; fdev->ops->flashlight_ioctl(FLASH_IOC_GET_HW_FAULT2, (unsigned long)&fl_dev_arg); fault_flag2 = fl_dev_arg.arg; ret = snprintf(fault_tmp, FLASHLIGHT_FAULT_TMPBUF_SIZE, "%d %d %d %s %d %d %d %d\n", fdev->dev_id.type, fdev->dev_id.ct, fdev->dev_id.part, fdev->dev_id.name, fdev->dev_id.channel, fdev->dev_id.decouple, fault_flag1, fault_flag2); if (ret < 0) pr_info("snprintf failed\n"); strncat(fault, fault_tmp, FLASHLIGHT_FAULT_TMPBUF_SIZE); } mutex_unlock(&fl_mutex); return scnprintf(buf, PAGE_SIZE, "[TYPE] [CT] [PART] [DEVICE] [CHANNEL] [DECOUPLE] [FAULT FLAG1] [FAULT FLAG2]\n%s\n", fault); } static DEVICE_ATTR_RO(flashlight_fault); /* sw disable sysfs */ static ssize_t flashlight_sw_disable_show( struct device *dev, struct device_attribute *attr, char *buf) { struct flashlight_dev *fdev; char status[FLASHLIGHT_SW_DISABLE_STATUS_BUF_SIZE]; char status_tmp[FLASHLIGHT_SW_DISABLE_STATUS_TMPBUF_SIZE]; int ret; pr_debug("Sw disable status show\n"); memset(status, '\0', FLASHLIGHT_SW_DISABLE_STATUS_BUF_SIZE); mutex_lock(&fl_mutex); list_for_each_entry(fdev, &flashlight_list, node) { if (!fdev->ops) continue; ret = snprintf(status_tmp, FLASHLIGHT_SW_DISABLE_STATUS_TMPBUF_SIZE, "%d %d\n", fdev->dev_id.type, fdev->sw_disable_status); if (ret < 0) pr_info("snprintf failed\n"); strncat(status, status_tmp, FLASHLIGHT_SW_DISABLE_STATUS_TMPBUF_SIZE); } mutex_unlock(&fl_mutex); return scnprintf(buf, PAGE_SIZE, "[TYPE] [SW_DISABLE_STATUS]\n%s\n", status); } static ssize_t flashlight_sw_disable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct flashlight_dev *fdev; struct flashlight_arg fl_arg; int sw_disable_status_tmp = 0; s32 num; int count = 0; char delim[] = " "; char *token, *cur = (char *)buf; int ret; pr_debug("Sw disable store\n"); memset(&fl_arg, 0, sizeof(struct flashlight_arg)); while (cur) { token = strsep(&cur, delim); ret = kstrtos32(token, 10, &num); if (ret) { pr_info("Error arguments\n"); goto unlock; } if (count == FLASHLIGHT_SW_DISABLE_TYPE) fl_arg.type = (int)num; else if (count == FLASHLIGHT_SW_DISABLE_STATUS) sw_disable_status_tmp = (int)num; else { count++; break; } count++; } /* verify data */ if (count != FLASHLIGHT_SW_DISABLE_NUM) { pr_info("Error argument number: (%d)\n", count); ret = -1; goto unlock; } if (sw_disable_status_tmp < FLASHLIGHT_SW_DISABLE_OFF || sw_disable_status_tmp > FLASHLIGHT_SW_DISABLE_ON) { pr_info("Error arguments sw disable status(%d)\n", sw_disable_status_tmp); ret = -1; goto unlock; } pr_debug("(%d), (%d)\n", fl_arg.type, sw_disable_status_tmp); /* store sw_disable status */ mutex_lock(&fl_mutex); list_for_each_entry(fdev, &flashlight_list, node) { if (!fdev->ops) continue; if (fl_arg.type == fdev->dev_id.type) { if (sw_disable_status_tmp == FLASHLIGHT_SW_DISABLE_ON) fl_enable(fdev, 0); fdev->sw_disable_status = sw_disable_status_tmp; } } mutex_unlock(&fl_mutex); ret = size; unlock: return ret; } static DEVICE_ATTR_RW(flashlight_sw_disable); /****************************************************************************** * Platform device and driver *****************************************************************************/ static struct class *flashlight_class; static struct device *flashlight_device; static dev_t flashlight_devno; static struct cdev *flashlight_cdev; static int fl_init(void) { return 0; } static int fl_uninit(void) { struct flashlight_dev *fdev, *n; mutex_lock(&fl_mutex); list_for_each_entry_safe(fdev, n, &flashlight_list, node) { /* uninit device */ if (fdev->ops) { fdev->ops->flashlight_open(); fdev->ops->flashlight_set_driver(1); fl_enable(fdev, 0); fdev->ops->flashlight_set_driver(0); fdev->ops->flashlight_release(); } /* clear node and free memory */ list_del(&fdev->node); kfree(fdev); } mutex_unlock(&fl_mutex); return 0; } static int flashlight_probe(struct platform_device *dev) { pr_debug("Probe start\n"); /* allocate char device number */ if (alloc_chrdev_region(&flashlight_devno, 0, 1, FLASHLIGHT_DEVNAME)) { pr_info("Failed to allocate char device region\n"); goto err_allocate_chrdev; } pr_debug("Allocate major number and minor number: (%d, %d)\n", MAJOR(flashlight_devno), MINOR(flashlight_devno)); /* allocate char device */ flashlight_cdev = cdev_alloc(); if (!flashlight_cdev) { pr_info("Failed to allcoate cdev\n"); goto err_allocate_cdev; } flashlight_cdev->ops = &flashlight_fops; flashlight_cdev->owner = THIS_MODULE; /* add char device to the system */ if (cdev_add(flashlight_cdev, flashlight_devno, 1)) { pr_info("Failed to add cdev\n"); goto err_add_cdev; } /* create class */ flashlight_class = class_create(THIS_MODULE, FLASHLIGHT_CORE); if (IS_ERR(flashlight_class)) { pr_info("Failed to create class (%d)\n", (int)PTR_ERR(flashlight_class)); goto err_create_class; } /* create device */ flashlight_device = device_create(flashlight_class, NULL, flashlight_devno, NULL, FLASHLIGHT_DEVNAME); if (!flashlight_device) { pr_info("Failed to create device\n"); goto err_create_device; } /* create device file */ if (device_create_file(flashlight_device, &dev_attr_flashlight_strobe)) { pr_info("Failed to create device file(strobe)\n"); goto err_create_strobe_device_file; } if (device_create_file(flashlight_device, &dev_attr_flashlight_pt)) { pr_info("Failed to create device file(pt)\n"); goto err_create_pt_device_file; } if (device_create_file(flashlight_device, &dev_attr_flashlight_charger)) { pr_info("Failed to create device file(charger)\n"); goto err_create_charger_device_file; } if (device_create_file(flashlight_device, &dev_attr_flashlight_capability)) { pr_info("Failed to create device file(capability)\n"); goto err_create_capability_device_file; } if (device_create_file(flashlight_device, &dev_attr_flashlight_current)) { pr_info("Failed to create device file(current)\n"); goto err_create_current_device_file; } if (device_create_file(flashlight_device, &dev_attr_flashlight_fault)) { pr_info("Failed to create device file(fault)\n"); goto err_create_fault_device_file; } if (device_create_file(flashlight_device, &dev_attr_flashlight_sw_disable)) { pr_info("Failed to create device file(sw_disable)\n"); goto err_create_sw_disable_device_file; } if (device_create_file(flashlight_device, &dev_attr_flashlight_torch)) { pr_info("Failed to create device file(torch)\n"); goto err_create_torch_device_file; } /* init flashlight */ fl_init(); pr_debug("Probe done\n"); return 0; err_create_torch_device_file: device_remove_file(flashlight_device, &dev_attr_flashlight_torch); err_create_sw_disable_device_file: device_remove_file(flashlight_device, &dev_attr_flashlight_sw_disable); err_create_fault_device_file: device_remove_file(flashlight_device, &dev_attr_flashlight_fault); err_create_current_device_file: device_remove_file(flashlight_device, &dev_attr_flashlight_capability); err_create_capability_device_file: device_remove_file(flashlight_device, &dev_attr_flashlight_charger); err_create_charger_device_file: device_remove_file(flashlight_device, &dev_attr_flashlight_pt); err_create_pt_device_file: device_remove_file(flashlight_device, &dev_attr_flashlight_strobe); err_create_strobe_device_file: device_destroy(flashlight_class, flashlight_devno); err_create_device: class_destroy(flashlight_class); err_create_class: err_add_cdev: cdev_del(flashlight_cdev); err_allocate_cdev: unregister_chrdev_region(flashlight_devno, 1); err_allocate_chrdev: return -1; } static int flashlight_remove(struct platform_device *dev) { fl_uninit(); /* remove device file */ device_remove_file(flashlight_device, &dev_attr_flashlight_torch); device_remove_file(flashlight_device, &dev_attr_flashlight_sw_disable); device_remove_file(flashlight_device, &dev_attr_flashlight_fault); device_remove_file(flashlight_device, &dev_attr_flashlight_current); device_remove_file(flashlight_device, &dev_attr_flashlight_capability); device_remove_file(flashlight_device, &dev_attr_flashlight_charger); device_remove_file(flashlight_device, &dev_attr_flashlight_pt); device_remove_file(flashlight_device, &dev_attr_flashlight_strobe); /* remove device */ device_destroy(flashlight_class, flashlight_devno); /* remove class */ class_destroy(flashlight_class); /* remove char device */ cdev_del(flashlight_cdev); /* unregister char device number */ unregister_chrdev_region(flashlight_devno, 1); return 0; } static void flashlight_shutdown(struct platform_device *dev) { fl_uninit(); } #ifdef CONFIG_OF static const struct of_device_id flashlight_of_match[] = { {.compatible = "mediatek,flashlight_core"}, {}, }; MODULE_DEVICE_TABLE(of, flashlight_of_match); #else static struct platform_device flashlight_platform_device[] = { { .name = FLASHLIGHT_DEVNAME, .id = 0, .dev = {} }, {} }; MODULE_DEVICE_TABLE(platform, flashlight_platform_device); #endif static struct platform_driver flashlight_platform_driver = { .probe = flashlight_probe, .remove = flashlight_remove, .shutdown = flashlight_shutdown, .driver = { .name = FLASHLIGHT_DEVNAME, .owner = THIS_MODULE, #ifdef CONFIG_OF .of_match_table = flashlight_of_match, #endif }, }; static int __init flashlight_init(void) { int ret; pr_debug("Init start\n"); #ifndef CONFIG_OF ret = platform_device_register(&flashlight_platform_device); if (ret) { pr_info("Failed to register platform device\n"); return ret; } #endif ret = platform_driver_register(&flashlight_platform_driver); if (ret) { pr_info("Failed to register platform driver\n"); return ret; } #ifdef CONFIG_MTK_FLASHLIGHT_PT ret = register_low_battery_notify( &pt_low_vol_callback, LOW_BATTERY_PRIO_FLASHLIGHT); if (ret == LOW_BATTERY_LEVEL_3) max_pt_low_vol = ret; register_bp_thl_notify( &pt_low_bat_callback, BATTERY_PERCENT_PRIO_FLASHLIGHT); register_battery_oc_notify( &pt_oc_callback, BATTERY_OC_PRIO_FLASHLIGHT); #endif pr_debug("Init done\n"); return 0; } static void __exit flashlight_exit(void) { pr_debug("Exit start\n"); platform_driver_unregister(&flashlight_platform_driver); pr_debug("Exit done\n"); } module_init(flashlight_init); module_exit(flashlight_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Simon Wang "); MODULE_DESCRIPTION("MTK Flashlight Core Driver");