// SPDX-License-Identifier: GPL-2.0 /* * LED driver for Samsung S2MPB02 * * Copyright (C) 2014 Samsung Electronics * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This driver is based on leds-max77804.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include struct device *camera_flash_dev; struct s2mpb02_led_data *global_led_datas[S2MPB02_LED_MAX]; struct s2mpb02_led_data { struct led_classdev led; struct s2mpb02_dev *s2mpb02; struct s2mpb02_led *data; struct i2c_client *i2c; struct work_struct work; struct mutex lock; spinlock_t value_lock; int brightness; int test_brightness; }; static u8 leds_mask[S2MPB02_LED_MAX] = { S2MPB02_FLASH_MASK, S2MPB02_TORCH_MASK, S2MPB02_TORCH_MASK, }; static u8 leds_shift[S2MPB02_LED_MAX] = { 4, 0, 0, }; u32 flash1_gpio; u32 torch1_gpio; #ifdef CONFIG_LEDS_S2MPB02_MULTI_TORCH_REAR2 u32 flash2_gpio; u32 torch2_gpio; #endif static enum s2mpb02_torch_current torch_recording; bool flash_config_factory; u32 original_brightness[S2MPB02_LED_MAX]; static int s2mpb02_set_bits(struct i2c_client *client, const u8 reg, const u8 mask, const u8 inval) { int ret; u8 value; ret = s2mpb02_read_reg(client, reg, &value); if (unlikely(ret < 0)) return ret; value = (value & ~mask) | (inval & mask); ret = s2mpb02_write_reg(client, reg, value); return ret; } static int s2mpb02_led_get_en_value(struct s2mpb02_led_data *led_data, int on) { if (on) { if (led_data->data->id == S2MPB02_FLASH_LED_1) { return ((S2MPB02_FLED_ENABLE << S2MPB02_FLED_ENABLE_SHIFT) | (S2MPB02_FLED_FLASH_MODE << S2MPB02_FLED_MODE_SHIFT)); /* Turn on FLASH by I2C */ } else if (led_data->data->id == S2MPB02_TORCH_LED_2) { return S2MPB02_FLED2_TORCH_ON; /* Turn on TORCH by I2C */ } else { return ((S2MPB02_FLED_ENABLE << S2MPB02_FLED_ENABLE_SHIFT) | (S2MPB02_FLED_TORCH_MODE << S2MPB02_FLED_MODE_SHIFT)); /* Turn on TORCH by I2C */ } } else { return (S2MPB02_FLED_DISABLE << S2MPB02_FLED_ENABLE_SHIFT); /* controlled by GPIO */ } } static void s2mpb02_led_set(struct led_classdev *led_cdev, enum led_brightness value) { } static void s2mpb02_gpio_set(unsigned int gpio, int onoff) { if (!gpio_is_valid(gpio)) { pr_info("%s: not valid gpio"); return; } gpio_request(gpio, NULL); if (onoff) gpio_direction_output(gpio, 1); else gpio_direction_output(gpio, 0); gpio_free(gpio); } static void led_set(struct s2mpb02_led_data *led_data, int turn_way) { int ret; struct s2mpb02_led *data = led_data->data; int id = data->id; int value; u8 reg; u8 mask; if (turn_way == S2MPB02_LED_TURN_WAY_GPIO) { /* Turn way LED by GPIO */ value = s2mpb02_led_get_en_value(led_data, 0); if ((id == S2MPB02_FLASH_LED_1) || (id == S2MPB02_TORCH_LED_1)) { reg = S2MPB02_REG_FLED_CTRL1; mask = S2MPB02_FLED_ENABLE_MODE_MASK; } else { reg = S2MPB02_REG_FLED_CTRL2; mask = S2MPB02_FLED2_ENABLE_MODE_MASK; } ret = s2mpb02_set_bits(led_data->i2c, id, mask, value); if (unlikely(ret)) goto error_set_bits; if (led_data->data->brightness == LED_OFF) { if ((id == S2MPB02_FLASH_LED_1) || (id == S2MPB02_TORCH_LED_1)) reg = S2MPB02_REG_FLED_CUR1; else reg = S2MPB02_REG_FLED_CUR2; /* set current */ ret = s2mpb02_set_bits(led_data->i2c, reg, leds_mask[id], data->brightness << leds_shift[id]); if (unlikely(ret)) goto error_set_bits; s2mpb02_gpio_set(flash1_gpio, 0); s2mpb02_gpio_set(torch1_gpio, 0); #ifdef CONFIG_LEDS_S2MPB02_MULTI_TORCH_REAR2 s2mpb02_gpio_set(flash2_gpio, 0); s2mpb02_gpio_set(torch2_gpio, 0); #endif } else { if ((id == S2MPB02_FLASH_LED_1) || (id == S2MPB02_TORCH_LED_1)) reg = S2MPB02_REG_FLED_CUR1; else reg = S2MPB02_REG_FLED_CUR2; /* set current */ ret = s2mpb02_set_bits(led_data->i2c, reg, leds_mask[id], data->brightness << leds_shift[id]); if (unlikely(ret)) goto error_set_bits; if (id == S2MPB02_FLASH_LED_1) { s2mpb02_gpio_set(flash1_gpio, 1); s2mpb02_gpio_set(torch1_gpio, 0); #ifdef CONFIG_LEDS_S2MPB02_MULTI_TORCH_REAR2 s2mpb02_gpio_set(flash2_gpio, 0); s2mpb02_gpio_set(torch2_gpio, 0); #endif #ifdef CONFIG_LEDS_S2MPB02_MULTI_TORCH_REAR2 } else if (id == S2MPB02_TORCH_LED_2) { s2mpb02_gpio_set(flash1_gpio, 0); s2mpb02_gpio_set(torch1_gpio, 0); s2mpb02_gpio_set(flash2_gpio, 0); s2mpb02_gpio_set(torch2_gpio, 1); #endif } else { /* S2MPB02_TORCH_LED_1 */ s2mpb02_gpio_set(flash1_gpio, 0); s2mpb02_gpio_set(torch1_gpio, 1); #ifdef CONFIG_LEDS_S2MPB02_MULTI_TORCH_REAR2 s2mpb02_gpio_set(flash2_gpio, 0); s2mpb02_gpio_set(torch2_gpio, 0); #endif } } } else { /* (turn_way == S2MPB02_LED_TURN_WAY_I2C) */ if (led_data->data->brightness == LED_OFF) { value = s2mpb02_led_get_en_value(led_data, 0); if ((id == S2MPB02_FLASH_LED_1) || (id == S2MPB02_TORCH_LED_1)) { reg = S2MPB02_REG_FLED_CTRL1; mask = S2MPB02_FLED_ENABLE_MODE_MASK; } else { reg = S2MPB02_REG_FLED_CTRL2; mask = S2MPB02_FLED2_ENABLE_MODE_MASK; } ret = s2mpb02_set_bits(led_data->i2c, reg, mask, value); if (unlikely(ret)) goto error_set_bits; if ((id == S2MPB02_FLASH_LED_1) || (id == S2MPB02_TORCH_LED_1)) reg = S2MPB02_REG_FLED_CUR1; else reg = S2MPB02_REG_FLED_CUR2; /* set current */ ret = s2mpb02_set_bits(led_data->i2c, reg, leds_mask[id], data->brightness << leds_shift[id]); if (unlikely(ret)) goto error_set_bits; } else { if ((id == S2MPB02_FLASH_LED_1) || (id == S2MPB02_TORCH_LED_1)) reg = S2MPB02_REG_FLED_CUR1; else reg = S2MPB02_REG_FLED_CUR2; /* set current */ ret = s2mpb02_set_bits(led_data->i2c, reg, leds_mask[id], data->brightness << leds_shift[id]); if (unlikely(ret)) goto error_set_bits; /* Turn on LED by I2C */ value = s2mpb02_led_get_en_value(led_data, 1); if ((id == S2MPB02_FLASH_LED_1) || (id == S2MPB02_TORCH_LED_1)) { reg = S2MPB02_REG_FLED_CTRL1; mask = S2MPB02_FLED_ENABLE_MODE_MASK; } else { reg = S2MPB02_REG_FLED_CTRL2; mask = S2MPB02_FLED2_ENABLE_MODE_MASK; } ret = s2mpb02_set_bits(led_data->i2c, reg, mask, value); if (unlikely(ret)) goto error_set_bits; } } error_set_bits: pr_err("%s: can't set led level %d\n", __func__, ret); } static void s2mpb02_led_work(struct work_struct *work) { struct s2mpb02_led_data *led_data = container_of(work, struct s2mpb02_led_data, work); pr_debug("[LED] %s\n", __func__); mutex_lock(&led_data->lock); led_set(led_data, S2MPB02_LED_TURN_WAY_I2C); mutex_unlock(&led_data->lock); } static int s2mpb02_led_setup(struct s2mpb02_led_data *led_data) { int ret = 0; struct s2mpb02_led *data = led_data->data; int id, value; if (data == NULL) { pr_err("%s : data is null\n", __func__); return -1; } id = data->id; /* set Low Voltage operating mode disable */ ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL1, S2MPB02_FLED_CTRL1_LV_DISABLE, S2MPB02_FLED_CTRL1_LV_EN_MASK); /* set current & timeout */ ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CUR1, data->brightness << leds_shift[id], leds_mask[id]); ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_TIME1, data->timeout << leds_shift[id], leds_mask[id]); value = s2mpb02_led_get_en_value(led_data, 0); ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL1, value, S2MPB02_FLED_ENABLE_MODE_MASK); #ifdef CONFIG_LEDS_S2MPB02_MULTI_TORCH_REAR2 /* set current & timeout */ ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CUR2, data->brightness << leds_shift[id], leds_mask[id]); ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_TIME2, 0x00, leds_mask[id]); value = s2mpb02_led_get_en_value(led_data, 0); ret = s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL2, value, S2MPB02_FLED2_ENABLE_MODE_MASK); #endif ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL2, 0x04, S2MPB02_TORCH_MASK); #ifdef CONFIG_LEDS_IRIS_IRLED_SUPPORT /* Remote-mode off, Power-LED Mode on, GPIO Polarity */ ret |= s2mpb02_write_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL2, 0x38); /* set current - 1050mA */ ret |= s2mpb02_write_reg(led_data->i2c, S2MPB02_REG_FLED_CUR2, 0xAF); /* set 0x18, 0x19, 0x1A, 0x1B */ ret |= s2mpb02_write_reg(led_data->i2c, S2MPB02_REG_FLED_IRON1, 0x19); ret |= s2mpb02_write_reg(led_data->i2c, S2MPB02_REG_FLED_IRON2, 0x0B); ret |= s2mpb02_write_reg(led_data->i2c, S2MPB02_REG_FLED_IRD1, 0x00); ret |= s2mpb02_write_reg(led_data->i2c, S2MPB02_REG_FLED_IRD2, 0x2C); #endif return ret; } void s2mpb02_led_get_status(struct s2mpb02_led_data *led_data) { int ret = 0; u8 value[11] = {0, }; ret = s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_INT1, &value[0]); /*INT1*/ ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL1, &value[1]); /*FLED_CTRL1*/ ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL2, &value[2]); /*FLED_CTRL2*/ ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_CUR1, &value[3]); /*FLED_CUR1*/ ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_TIME1, &value[4]); /*FLED_TIME1*/ ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_CUR2, &value[5]); /*FLED_CUR2*/ ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_TIME2, &value[6]); /*FLED_TIME2*/ ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_IRON1, &value[7]); /*FLED_IRON1*/ ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_IRON2, &value[8]); /*FLED_IRON2*/ ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_IRD1, &value[9]); /*FLED_IRD1*/ ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_IRD2, &value[10]); /*FLED_IRD2*/ if (unlikely(ret < 0)) pr_info("%s : i2c fail\n", __func__); pr_info("%s: INT1[0x%02x] FLED CTRL1[0x%02x] CTRL2[0x%02x] CUR1[0x%02x] TIME1[0x%02x]\n", __func__, value[0], value[1], value[2], value[3], value[4]); pr_info("%s: CUR2[0x%02x] TIME2[0x%02x] IRON1[0x%02x] IRON2[0x%02x] IRD1[0x%02x] IRD2[0x%02x]\n", __func__, value[5], value[6], value[7], value[8], value[9], value[10]); } /*++ Only use in HealthHW R&D Group to check the ALS sensor performance ++*/ int s2mpb02_led_en(int mode, int onoff, enum s2mpb02_led_turn_way turn_way) { int ret = 0; if (global_led_datas[S2MPB02_TORCH_LED_1] == NULL) { pr_err("<%s> global_led_datas[S2MPB02_TORCH_LED_1] is NULL\n", __func__); return -1; } mutex_lock(&global_led_datas[mode]->lock); if (onoff > 0) {/* enable */ pr_info("<%s> enable %d, %d\n", __func__, onoff, mode); if (mode == S2MPB02_TORCH_LED_1) { if (onoff >= S2MPB02_TORCH_OUT_I_MAX) onoff = S2MPB02_TORCH_OUT_I_MAX-1; } else if (mode == S2MPB02_FLASH_LED_1) { if (onoff >= S2MPB02_FLASH_OUT_I_MAX) onoff = S2MPB02_FLASH_OUT_I_MAX-1; } else { pr_err("<%s> mode %d is invalid\n", __func__, mode); ret = -1; goto p_unlock; } global_led_datas[mode]->data->brightness = onoff; } else {/* disable */ pr_info("<%s> disable %d, %d\n", __func__, onoff, mode); global_led_datas[mode]->data->brightness = LED_OFF; } led_set(global_led_datas[mode], turn_way); p_unlock: mutex_unlock(&global_led_datas[mode]->lock); return ret; } EXPORT_SYMBOL(s2mpb02_led_en); /*-- Request from the HealthHW R&D Group to check the ALS sensor performance --*/ void s2mpb02_led_dump_register(u8 s_reg, u8 e_reg) { int ret = 0; u8 value = 0; u8 i = 0; struct s2mpb02_led_data *led_data = global_led_datas[S2MPB02_TORCH_LED_1]; for (i = s_reg; i <= e_reg; i++) { ret |= s2mpb02_read_reg(led_data->i2c, i, &value); /*Fled_ctrl2*/ if (unlikely(ret < 0)) pr_info("%s : error to read register\n", __func__); else pr_info("%s : reg. 0x%2X = 0x%2X\n", __func__, i, value); } } EXPORT_SYMBOL(s2mpb02_led_dump_register); int s2mpb02_set_torch_current(enum s2mpb02_torch_mode torch_mode, unsigned int intensity) { struct s2mpb02_led_data *led_data = global_led_datas[S2MPB02_TORCH_LED_1]; struct s2mpb02_led *data = led_data->data; int ret = 0; u8 cur_brightness; switch (torch_mode) { case S2MPB02_TORCH_RECORDING: if (intensity) cur_brightness = (intensity / 20); else cur_brightness = torch_recording; break; case S2MPB02_TORCH_PREFLASH: case S2MPB02_TORCH_NORMAL: default: if (intensity) cur_brightness = (intensity / 20); else cur_brightness = original_brightness[data->id]; break; } pr_info("%s: torch_mode[%s] intensity[%d] cur_brightness[%d]\n", __func__, torch_mode ? (torch_mode == S2MPB02_TORCH_PREFLASH ? "PRE-FLASH" : "NORMAL") : "RECORDING", intensity, cur_brightness); mutex_lock(&led_data->lock); data->brightness = cur_brightness; /* set current */ ret = s2mpb02_set_bits(led_data->i2c, S2MPB02_REG_FLED_CUR1, leds_mask[data->id], data->brightness << leds_shift[data->id]); if (unlikely(ret)) pr_err("%s: failed to set FLED_CUR1, %d\n", __func__, ret); mutex_unlock(&led_data->lock); return ret; } EXPORT_SYMBOL_GPL(s2mpb02_set_torch_current); int s2mpb02_set_flash_current(unsigned int intensity) { struct s2mpb02_led_data *led_data = global_led_datas[S2MPB02_FLASH_LED_1]; struct s2mpb02_led *data = led_data->data; int ret = 0; u8 cur_brightness = (intensity > 150) ? ((intensity - 50) / 100) : 1; pr_info("%s: flash intensity %d\n", __func__, intensity); if (cur_brightness < S2MPB02_FLASH_OUT_I_150MA) cur_brightness = S2MPB02_FLASH_OUT_I_150MA; else if (cur_brightness >= S2MPB02_FLASH_OUT_I_MAX) cur_brightness = S2MPB02_FLASH_OUT_I_1550MA; mutex_lock(&led_data->lock); /* set current */ s2mpb02_set_bits(led_data->i2c, S2MPB02_REG_FLED_CUR1, leds_mask[data->id], cur_brightness << leds_shift[data->id]); led_data->brightness = cur_brightness; if (unlikely(ret)) pr_err("%s: failed to set FLED_CUR1, %d\n", __func__, ret); mutex_unlock(&led_data->lock); return ret; } EXPORT_SYMBOL_GPL(s2mpb02_set_flash_current); ssize_t s2mpb02_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int value = 0; int ret = 0; if ((buf == NULL) || kstrtouint(buf, 10, &value)) return -1; if (global_led_datas[S2MPB02_TORCH_LED_1] == NULL) { pr_err("<%s> global_led_datas[S2MPB02_TORCH_LED_1] is NULL\n", __func__); return -1; } pr_info("[LED]%s , value:%d\n", __func__, value); mutex_lock(&global_led_datas[S2MPB02_TORCH_LED_1]->lock); if (value == 0) { /* Turn off Torch */ if (!flash_config_factory) { global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness = LED_OFF; led_set(global_led_datas[S2MPB02_TORCH_LED_1], S2MPB02_LED_TURN_WAY_GPIO); } else { global_led_datas[S2MPB02_FLASH_LED_1]->data->brightness = LED_OFF; if (!gpio_is_valid(flash1_gpio)) led_set(global_led_datas[S2MPB02_FLASH_LED_1], S2MPB02_LED_TURN_WAY_I2C); else led_set(global_led_datas[S2MPB02_FLASH_LED_1], S2MPB02_LED_TURN_WAY_GPIO); } } else if (value == 1) { /* Turn on Torch */ global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness = S2MPB02_TORCH_OUT_I_60MA; led_set(global_led_datas[S2MPB02_TORCH_LED_1], S2MPB02_LED_TURN_WAY_GPIO); } else if (value == 100) { /* Factory mode Turn on Torch */ global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness = S2MPB02_TORCH_OUT_I_240MA; led_set(global_led_datas[S2MPB02_TORCH_LED_1], S2MPB02_LED_TURN_WAY_GPIO); } else if (value == 200) { /* Factory mode Turn on Flash */ /* set reserved reg. 0x63 for continuous flash on */ flash_config_factory = true; ret = s2mpb02_write_reg(global_led_datas[S2MPB02_FLASH_LED_1]->i2c, 0x63, 0x5F); if (ret < 0) pr_info("[LED]%s , failed set flash register setting\n", __func__); global_led_datas[S2MPB02_FLASH_LED_1]->data->brightness = S2MPB02_FLASH_OUT_I_350MA; if (!gpio_is_valid(flash1_gpio)) led_set(global_led_datas[S2MPB02_FLASH_LED_1], S2MPB02_LED_TURN_WAY_I2C); else led_set(global_led_datas[S2MPB02_FLASH_LED_1], S2MPB02_LED_TURN_WAY_GPIO); } else if (value >= 1001 && value <= 1010) { /* Torch mode, step 1~5 value : 1001, 1002, 1004, 1006, 1009 */ int brightness_value = value - 1001; int torch_intensity = -1; if (global_led_datas[S2MPB02_TORCH_LED_1]->data->torch_table[brightness_value] != 0) torch_intensity = global_led_datas[S2MPB02_TORCH_LED_1]->data->torch_table[brightness_value]; if (torch_intensity < 0) { pr_info("[LED]%s , Invalid torch_intensity(%d), reset as default: %d\n", __func__, torch_intensity, global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness); torch_intensity = global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness; } /* Turn on Torch Step 40mA ~ 240mA */ pr_info("[LED]%s , %d->%d(%dmA)\n", __func__, brightness_value, torch_intensity, (torch_intensity)*20); global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness = torch_intensity; led_set(global_led_datas[S2MPB02_TORCH_LED_1], S2MPB02_LED_TURN_WAY_GPIO); } else if (value >= 2000 && value <= 2015) { int torch_intensity = value - 2000; /* Turn on Torch 20mA ~ 300mA of each step 20mA*/ pr_info("[LED]%s , torch brightness set (%dmA)\n", __func__, (torch_intensity)*20); global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness = torch_intensity; led_set(global_led_datas[S2MPB02_TORCH_LED_1], S2MPB02_LED_TURN_WAY_GPIO); } else { pr_info("[LED]%s , Invalid value:%d\n", __func__, value); } if (value <= 0) { s2mpb02_set_bits(global_led_datas[S2MPB02_TORCH_LED_1]->i2c, S2MPB02_REG_FLED_CUR1, leds_mask[global_led_datas[S2MPB02_TORCH_LED_1]->data->id], original_brightness[S2MPB02_TORCH_LED_1] << leds_shift[global_led_datas[S2MPB02_TORCH_LED_1]->data->id]); global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness = original_brightness[S2MPB02_TORCH_LED_1]; if (flash_config_factory) { s2mpb02_write_reg(global_led_datas[S2MPB02_FLASH_LED_1]->i2c, 0x63, 0x7F); s2mpb02_set_bits(global_led_datas[S2MPB02_FLASH_LED_1]->i2c, S2MPB02_REG_FLED_CUR1, leds_mask[global_led_datas[S2MPB02_FLASH_LED_1]->data->id], original_brightness[S2MPB02_FLASH_LED_1] << leds_shift[global_led_datas[S2MPB02_FLASH_LED_1]->data->id]); global_led_datas[S2MPB02_FLASH_LED_1]->data->brightness = original_brightness[S2MPB02_FLASH_LED_1]; flash_config_factory = false; } } mutex_unlock(&global_led_datas[S2MPB02_TORCH_LED_1]->lock); pr_info("[LED]%s, gpio state flash:%d, torch:%d", __func__, gpio_get_value(flash1_gpio), gpio_get_value(torch1_gpio)); return count; } #ifdef CONFIG_LEDS_S2MPB02_MULTI_TORCH_REAR2 ssize_t s2mpb02_store_2nd(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int value = 0; int ret = 0; if ((buf == NULL) || kstrtouint(buf, 10, &value)) return -1; if (global_led_datas[S2MPB02_TORCH_LED_2] == NULL) { pr_err("<%s> global_led_datas[S2MPB02_TORCH_LED_2] is NULL\n", __func__); return -1; } pr_info("[LED]%s , value:%d\n", __func__, value); mutex_lock(&global_led_datas[S2MPB02_TORCH_LED_2]->lock); if (value == 0) { if (!flash_config_factory) { global_led_datas[S2MPB02_TORCH_LED_2]->data->brightness = LED_OFF; led_set(global_led_datas[S2MPB02_TORCH_LED_2], S2MPB02_LED_TURN_WAY_I2C); } else { global_led_datas[S2MPB02_FLASH_LED_1]->data->brightness = LED_OFF; led_set(global_led_datas[S2MPB02_FLASH_LED_1], S2MPB02_LED_TURN_WAY_I2C); } } else if (value == 1) { /* Turn on Torch */ global_led_datas[S2MPB02_TORCH_LED_2]->data->brightness = S2MPB02_TORCH_OUT_I_60MA; led_set(global_led_datas[S2MPB02_TORCH_LED_2], S2MPB02_LED_TURN_WAY_I2C); } else if (value == 100) { /* Factory mode Turn on Torch */ global_led_datas[S2MPB02_TORCH_LED_2]->data->brightness = S2MPB02_TORCH_OUT_I_240MA; led_set(global_led_datas[S2MPB02_TORCH_LED_2], S2MPB02_LED_TURN_WAY_I2C); } else if (value == 200) { /* Factory mode Turn on Flash */ /* set reserved reg. 0x63 for continuous flash on */ flash_config_factory = true; ret = s2mpb02_write_reg(global_led_datas[S2MPB02_FLASH_LED_1]->i2c, 0x63, 0x5F); if (ret < 0) pr_info("[LED]%s , failed set flash register setting\n", __func__); global_led_datas[S2MPB02_FLASH_LED_1]->data->brightness = S2MPB02_FLASH_OUT_I_350MA; led_set(global_led_datas[S2MPB02_FLASH_LED_1], S2MPB02_LED_TURN_WAY_I2C); } else if (value >= 1001 && value <= 1010) { /* Torch mode, step 1~5 value : 1001, 1002, 1004, 1006, 1009 */ int brightness_value = value - 1001; int torch_intensity = -1; if (global_led_datas[S2MPB02_TORCH_LED_2]->data->torch_table[brightness_value] != 0) torch_intensity = global_led_datas[S2MPB02_TORCH_LED_2]->data->torch_table[brightness_value]; if (torch_intensity < 0) { pr_info("[LED]%s , Invalid torch_intensity(%d), reset as default: %d\n", __func__, torch_intensity, global_led_datas[S2MPB02_TORCH_LED_2]->data->brightness); torch_intensity = global_led_datas[S2MPB02_TORCH_LED_2]->data->brightness; } /* Turn on Torch Step 40mA ~ 240mA */ pr_info("[LED]%s , %d->%d(%dmA)\n", __func__, brightness_value, torch_intensity, (torch_intensity)*20); global_led_datas[S2MPB02_TORCH_LED_2]->data->brightness = torch_intensity; led_set(global_led_datas[S2MPB02_TORCH_LED_2], S2MPB02_LED_TURN_WAY_I2C); } else if (value >= 2000 && value <= 2015) { int torch_intensity = value - 2000; /* Turn on Torch 20mA ~ 300mA of each step 20mA*/ pr_info("[LED]%s , torch brightness set (%dmA)\n", __func__, (torch_intensity)*20); global_led_datas[S2MPB02_TORCH_LED_2]->data->brightness = torch_intensity; led_set(global_led_datas[S2MPB02_TORCH_LED_2], S2MPB02_LED_TURN_WAY_I2C); } else { pr_info("[LED]%s , Invalid value:%d\n", __func__, value); } if (value <= 0) { s2mpb02_set_bits(global_led_datas[S2MPB02_TORCH_LED_2]->i2c, S2MPB02_REG_FLED_CUR1, leds_mask[global_led_datas[S2MPB02_TORCH_LED_2]->data->id], original_brightness[S2MPB02_TORCH_LED_2] << leds_shift[global_led_datas[S2MPB02_TORCH_LED_2]->data->id]); global_led_datas[S2MPB02_TORCH_LED_2]->data->brightness = original_brightness[S2MPB02_TORCH_LED_2]; if (flash_config_factory) { s2mpb02_write_reg(global_led_datas[S2MPB02_FLASH_LED_1]->i2c, 0x63, 0x7F); s2mpb02_set_bits(global_led_datas[S2MPB02_FLASH_LED_1]->i2c, S2MPB02_REG_FLED_CUR2, leds_mask[global_led_datas[S2MPB02_FLASH_LED_1]->data->id], original_brightness[S2MPB02_FLASH_LED_1] << leds_shift[global_led_datas[S2MPB02_FLASH_LED_1]->data->id]); global_led_datas[S2MPB02_FLASH_LED_1]->data->brightness = original_brightness[S2MPB02_FLASH_LED_1]; flash_config_factory = false; } } mutex_unlock(&global_led_datas[S2MPB02_TORCH_LED_2]->lock); return count; } #endif ssize_t s2mpb02_show(struct device *dev, struct device_attribute *attr, char *buf) { if (global_led_datas[S2MPB02_TORCH_LED_1] == NULL) { pr_err("<%s> global_led_datas[S2MPB02_TORCH_LED_1] is NULL\n", __func__); return -1; } pr_info("[LED] %s , MAX STEP TORCH_LED:%d\n", __func__, S2MPB02_TORCH_OUT_I_MAX - 1); return sprintf(buf, "%d\n", S2MPB02_TORCH_OUT_I_MAX - 1); } ssize_t s2mpb02_state_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", gpio_get_value(torch1_gpio)); } #ifdef CONFIG_LEDS_IRIS_IRLED_CERTIFICATE_SUPPORT ssize_t s2mpb02_irled_torch_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int value = 0; u8 ledvalue = 0; int ret = 0; if ((buf == NULL) || kstrtouint(buf, 10, &value)) return -1; if (global_led_datas[0] == NULL) { pr_err("<%s> global_led_datas[S2MPB02_TORCH_LED_1] is NULL\n", __func__); return -1; } pr_info("%s , value:%d\n", __func__, value); if (value == 1) { pr_info("%s IRLED TORCH ON\n", __func__); /* FLED_TIME2.IR_MAX_TIMER_EN Disable */ ret |= s2mpb02_update_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_TIME2, S2MPB02_FLED_TIME2_IRMAX_TIMER_DISABLE, S2MPB02_FLED_TIME2_IRMAX_TIMER_EN_MASK); /* Remote-mode off, TORCH MODE */ ret |= s2mpb02_update_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_CTRL2, S2MPB02_FLED_CTRL2_TORCH_ON, S2MPB02_FLED_CTRL2_TORCH_MASK); ret |= s2mpb02_update_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_CUR2, 0x0E, S2MPB02_FLED_CUR2_TORCH_CUR2_MASK); } else if (value == 0) { pr_info("%s IRLED TORCH OFF\n", __func__); /* Remote-mode off, Power-LED Mode on, GPIO Polarity */ ret |= s2mpb02_update_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_CTRL2, S2MPB02_FLED_CTRL2_FLASH_ON, S2MPB02_FLED_CTRL2_TORCH_MASK); } else { pr_err("%s value(%d) is invalid!!\n", __func__, value); return -1; } ret |= s2mpb02_read_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_CTRL2, &ledvalue); pr_info("[LED]%s , ledvalue:%x\n", __func__, ledvalue); return count; } ssize_t s2mpb02_irled_torch_show(struct device *dev, struct device_attribute *attr, char *buf) { struct s2mpb02_led_data *led_data; int ret = 0; u8 value[10] = {0, }; if (global_led_datas[0] == NULL) { pr_err("<%s> global_led_datas[S2MPB02_TORCH_LED_1] is NULL\n", __func__); return -1; } led_data = global_led_datas[0]; ret = s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL1, &value[0]); //Fled_ctrl1 ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL2, &value[1]); //Fled_ctrl2 ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_CUR1, &value[2]); //Fled_cur1 ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_TIME1, &value[3]); //Fled_time1 ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_CUR2, &value[4]); //Fled_cur2 ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_TIME2, &value[5]); //Fled_time2 ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_IRON1, &value[6]); //Fled_iron1 ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_IRON2, &value[7]); //Fled_iron2 ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_IRD1, &value[8]); //Fled_ird1 ret |= s2mpb02_read_reg(led_data->i2c, S2MPB02_REG_FLED_IRD2, &value[9]); //Fled_ird2 if (unlikely(ret < 0)) pr_err("%s : error to get dt node\n", __func__); pr_info("%s : Fled_ctrl1 = 0x%12x, Fled_ctrl2 = 0x%13x, Fled_cur1 = 0x%14x, Fled_time1 = 0x%15x\n", __func__, value[0], value[1], value[2], value[3]); pr_info("%s : Fled_cur2 = 0x%16x, Fled_time2 = 0x%17x, Fled_iron1 = 0x%x\n", __func__, value[4], value[5], value[6]); pr_info("%s : Fled_iron2 = 0x%x, Fled_ird1 = 0x%x, Fled_ird2 = 0x%x\n", __func__, value[7], value[8], value[9]); return sprintf(buf, "0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x,0x%x\n", value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9]); } static DEVICE_ATTR(irled_torch, 00644, s2mpb02_irled_torch_show, s2mpb02_irled_torch_store); #endif static DEVICE_ATTR(rear_flash, 00644, s2mpb02_show, s2mpb02_store); #ifdef CONFIG_LEDS_S2MPB02_MULTI_TORCH_REAR2 static DEVICE_ATTR(rear_flash2, 00644, s2mpb02_show, s2mpb02_store_2nd); #endif static DEVICE_ATTR(flash_state, 00444, s2mpb02_state_show, NULL); #ifdef CONFIG_LEDS_IRIS_IRLED_SUPPORT int s2mpb02_ir_led_current(uint32_t current_value) { int ret = 0; unsigned int value = current_value - 1; unsigned char data = 0; pr_info("[%s] led current value : %u\n", __func__, value); data = ((value & 0x0F) << 4) | 0x0F; ret = s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_CUR2, data); if (ret < 0) pr_err("[%s] i2c write error", __func__); return ret; } EXPORT_SYMBOL(s2mpb02_ir_led_current); int s2mpb02_ir_led_pulse_width(uint32_t width) { unsigned int value = width; unsigned char iron1 = 0; unsigned char iron2 = 0; int ret = 0; pr_info("[%s] led pulse_width value : %u\n", __func__, value); iron1 = (value >> 2) & 0xFF; iron2 = (value & 0x03) << 6; pr_info("[%s] IRON1(0x%02x), IRON2(0x%02x)\n", __func__, iron1, iron2); /* set 0x18, 0x19 */ ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRON1, iron1); ret |= s2mpb02_set_bits(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRON2, S2MPB02_FLED2_IRON2_MASK, iron2); if (ret < 0) pr_err("[%s] i2c write error", __func__); return ret; } EXPORT_SYMBOL(s2mpb02_ir_led_pulse_width); int s2mpb02_ir_led_pulse_delay(uint32_t delay) { unsigned int value = delay; unsigned char ird1 = 0; unsigned char ird2 = 0; int ret = 0; pr_info("[%s] led pulse_delay value : %u\n", __func__, value); ird1 = (value >> 2) & 0xFF; ird2 = ((value & 0x03) << 6) | 0x2C; /* value 0x2C means RSVD[5:0] Reserved */ pr_info("[%s] IRD1(0x%02x), IRD2(0x%02x)\n", __func__, ird1, ird2); /* set 0x1A, 0x1B */ ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRD1, ird1); ret |= s2mpb02_write_reg(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRD2, ird2); if (ret < 0) pr_err("[%s] i2c write error", __func__); return ret; } EXPORT_SYMBOL(s2mpb02_ir_led_pulse_delay); int s2mpb02_ir_led_max_time(uint32_t max_time) { int ret = 0; pr_info("[%s] led max_time value : %u\n", __func__, max_time); /* set IRLED max timer interrupt clear and enabled */ ret |= s2mpb02_set_bits(global_led_datas[0]->i2c, S2MPB02_REG_FLED_CTRL2, S2MPB02_FLED2_MAX_TIME_CLEAR_MASK, 0x00); ret |= s2mpb02_set_bits(global_led_datas[0]->i2c, S2MPB02_REG_FLED_TIME2, S2MPB02_FLED2_MAX_TIME_EN_MASK, 0x00); if (max_time > 0) { ret |= s2mpb02_set_bits(global_led_datas[0]->i2c, S2MPB02_REG_FLED_TIME2, S2MPB02_FLED2_MAX_TIME_EN_MASK, 0x01); ret |= s2mpb02_set_bits(global_led_datas[0]->i2c, S2MPB02_REG_FLED_IRON2, S2MPB02_FLED2_MAX_TIME_MASK, (u8) max_time - 1); } s2mpb02_led_get_status(global_led_datas[0]); return ret; } EXPORT_SYMBOL(s2mpb02_ir_led_max_time); #endif #if defined(CONFIG_OF) static int of_s2mpb02_torch_dt(struct s2mpb02_dev *iodev, struct s2mpb02_led_platform_data *pdata) { struct device_node *led_np, *np, *c_np; int ret; u32 temp; u32 irda_off = 0; const char *temp_str; int index; u32 torch_table_enable = 0; led_np = iodev->dev->of_node; if (!led_np) { pr_info("<%s> could not find led sub-node\n", __func__); return -ENODEV; } np = of_find_node_by_name(led_np, "torch"); if (!np) { pr_info("<%s> could not find led sub-node\n", __func__); return -EINVAL; } torch1_gpio = of_get_named_gpio(np, "torch1-gpio", 0); if (!gpio_is_valid(torch1_gpio)) pr_info("%s failed to get a torch1_gpio\n", __func__); else s2mpb02_gpio_set(torch1_gpio, 0); flash1_gpio = of_get_named_gpio(np, "flash1-gpio", 0); if (!gpio_is_valid(flash1_gpio)) pr_info("%s failed to get a flash1_gpio\n", __func__); else s2mpb02_gpio_set(flash1_gpio, 0); #ifdef CONFIG_LEDS_S2MPB02_MULTI_TORCH_REAR2 torch2_gpio = of_get_named_gpio(np, "torch2-gpio", 0); if (!gpio_is_valid(torch2_gpio)) pr_info("%s failed to get a torch2_gpio\n", __func__); else s2mpb02_gpio_set(torch2_gpio, 0); flash2_gpio = of_get_named_gpio(np, "flash2-gpio", 0); if (!gpio_is_valid(flash2_gpio)) pr_info("%s failed to get a flash2_gpio\n", __func__); else s2mpb02_gpio_set(flash2_gpio, 0); #endif ret = of_property_read_u32(np, "torch_recording", &temp); if (ret) { torch_recording = S2MPB02_TORCH_OUT_I_220MA; pr_info("%s failed to get a torch_current. default 220mA\n", __func__); } else torch_recording = (enum s2mpb02_torch_current)temp; pdata->num_leds = of_get_child_count(np); for_each_child_of_node(np, c_np) { ret = of_property_read_u32(c_np, "id", &temp); if (ret) pr_info("%s failed to get a id\n", __func__); index = temp; pdata->leds[index].id = temp; ret = of_property_read_string(c_np, "ledname", &temp_str); if (ret) pr_info("%s failed to get a ledname\n", __func__); pdata->leds[index].name = temp_str; ret = of_property_read_u32(c_np, "brightness", &temp); if (ret) pr_info("%s failed to get a brightness\n", __func__); if (temp > S2MPB02_FLASH_TORCH_CURRENT_MAX) pr_info("%s out of range : brightness\n", __func__); pdata->leds[index].brightness = temp; original_brightness[index] = temp; ret = of_property_read_u32(c_np, "timeout", &temp); if (ret) pr_info("%s failed to get a timeout\n", __func__); if (temp > S2MPB02_TIMEOUT_MAX) pr_info("%s out of range : timeout\n", __func__); pdata->leds[index].timeout = temp; if (index == 1) { ret = of_property_read_u32(c_np, "irda_off", &irda_off); if (ret) pr_info("%s failed to get a irda_off\n", __func__); pdata->leds[index].irda_off = irda_off; } ret = of_property_read_u32(c_np, "torch_table_enable", &torch_table_enable); if (ret) pr_info("%s failed to get a torch_table_enable\n", __func__); if (torch_table_enable == 1) { pdata->leds[index].torch_table_enable = torch_table_enable; ret = of_property_read_u32_array(c_np, "torch_table", pdata->leds[index].torch_table, TORCH_STEP); } else { pdata->leds[index].torch_table_enable = 0; } } of_node_put(led_np); return 0; } #endif /* CONFIG_OF */ int s2mpb02_create_sysfs(struct class *class) { if (class == NULL) { class = class_create(THIS_MODULE, "camera"); if (IS_ERR(class)) { pr_err("Failed to create class(camera)!\n"); return PTR_ERR(class); } } camera_flash_dev = device_create(class, NULL, 3, NULL, "flash"); if (IS_ERR(camera_flash_dev)) { pr_err("<%s> Failed to create device(flash)!\n", __func__); } else { if (device_create_file(camera_flash_dev, &dev_attr_rear_flash) < 0) { pr_err("<%s> failed to create device file, %s\n", __func__, dev_attr_rear_flash.attr.name); } #ifdef CONFIG_LEDS_IRIS_IRLED_CERTIFICATE_SUPPORT if (device_create_file(camera_flash_dev, &dev_attr_irled_torch) < 0) { pr_err("<%s> failed to create device file, %s\n", __func__, dev_attr_irled_torch.attr.name); } #endif #ifdef CONFIG_LEDS_S2MPB02_MULTI_TORCH_REAR2 if (device_create_file(camera_flash_dev, &dev_attr_rear_flash2) < 0) { pr_err("<%s> failed to create device file, %s\n", __func__, dev_attr_rear_flash2.attr.name); } #endif if (device_create_file(camera_flash_dev, &dev_attr_flash_state) < 0) { pr_err("<%s> failed to create device file, %s\n", __func__, dev_attr_flash_state.attr.name); } } return 0; } EXPORT_SYMBOL(s2mpb02_create_sysfs); int s2mpb02_destroy_sysfs(struct class *class) { if (camera_flash_dev) { device_remove_file(camera_flash_dev, &dev_attr_rear_flash); #ifdef CONFIG_LEDS_S2MPB02_MULTI_TORCH_REAR2 device_remove_file(camera_flash_dev, &dev_attr_rear_flash2); #endif #ifdef CONFIG_LEDS_IRIS_IRLED_CERTIFICATE_SUPPORT device_remove_file(camera_flash_dev, &dev_attr_irled_torch); #endif device_remove_file(camera_flash_dev, &dev_attr_flash_state); } if (class && camera_flash_dev) device_destroy(class, camera_flash_dev->devt); return 0; } EXPORT_SYMBOL(s2mpb02_destroy_sysfs); static int s2mpb02_led_probe(struct platform_device *pdev) { int ret = 0; int i; struct s2mpb02_dev *s2mpb02 = dev_get_drvdata(pdev->dev.parent); #ifndef CONFIG_OF struct s2mpb02_platform_data *s2mpb02_pdata = dev_get_platdata(s2mpb02->dev); #endif struct s2mpb02_led_platform_data *pdata; struct s2mpb02_led_data *led_data; struct s2mpb02_led *data; struct s2mpb02_led_data **led_datas; #ifdef CONFIG_OF pdata = kzalloc(sizeof(struct s2mpb02_led_platform_data), GFP_KERNEL); if (!pdata) return -ENOMEM; ret = of_s2mpb02_torch_dt(s2mpb02, pdata); if (ret < 0) { pr_err("s2mpb02-torch : %s not found torch dt! ret[%d]\n", __func__, ret); kfree(pdata); return -1; } #else pdata = s2mpb02_pdata->led_data; if (pdata == NULL) { pr_err("[LED] no platform data for this led is found\n"); return -EFAULT; } #endif led_datas = kzalloc(sizeof(struct s2mpb02_led_data *) * S2MPB02_LED_MAX, GFP_KERNEL); if (unlikely(!led_datas)) { pr_err("[LED] memory allocation error %s", __func__); kfree(pdata); return -ENOMEM; } platform_set_drvdata(pdev, led_datas); pr_info("[LED] %s %d leds\n", __func__, pdata->num_leds); for (i = 0; i != pdata->num_leds; ++i) { pr_info("%s led%d setup ...\n", __func__, i); data = kzalloc(sizeof(struct s2mpb02_led), GFP_KERNEL); if (unlikely(!data)) { pr_err("[LED] memory allocation error %s\n", __func__); ret = -ENOMEM; continue; } memcpy(data, &(pdata->leds[i]), sizeof(struct s2mpb02_led)); led_data = kzalloc(sizeof(struct s2mpb02_led_data), GFP_KERNEL); global_led_datas[i] = led_data; led_datas[i] = led_data; if (unlikely(!led_data)) { pr_err("[LED] memory allocation error %s\n", __func__); ret = -ENOMEM; kfree(data); continue; } led_data->s2mpb02 = s2mpb02; led_data->i2c = s2mpb02->i2c; led_data->data = data; led_data->led.name = data->name; led_data->led.brightness_set = s2mpb02_led_set; led_data->led.brightness = LED_OFF; led_data->brightness = data->brightness; led_data->led.flags = 0; led_data->led.max_brightness = S2MPB02_FLASH_TORCH_CURRENT_MAX; mutex_init(&led_data->lock); spin_lock_init(&led_data->value_lock); INIT_WORK(&led_data->work, s2mpb02_led_work); ret = led_classdev_register(&pdev->dev, &led_data->led); if (unlikely(ret)) { pr_err("unable to register LED\n"); cancel_work_sync(&led_data->work); mutex_destroy(&led_data->lock); kfree(data); kfree(led_data); led_datas[i] = NULL; global_led_datas[i] = NULL; ret = -EFAULT; continue; } ret = s2mpb02_led_setup(led_data); /* To prevent faint LED light, enable active discharge */ ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_IRD2, 0x02, 0x02); if (unlikely(ret)) { pr_err("unable to register LED\n"); cancel_work_sync(&led_data->work); mutex_destroy(&led_data->lock); led_classdev_unregister(&led_data->led); kfree(data); kfree(led_data); led_datas[i] = NULL; global_led_datas[i] = NULL; ret = -EFAULT; } } #ifdef CONFIG_OF kfree(pdata); #endif pr_err("<%s> end\n", __func__); return ret; } static int s2mpb02_led_remove(struct platform_device *pdev) { struct s2mpb02_led_data **led_datas = platform_get_drvdata(pdev); int i; for (i = 0; i != S2MPB02_LED_MAX; ++i) { if (led_datas[i] == NULL) continue; cancel_work_sync(&led_datas[i]->work); mutex_destroy(&led_datas[i]->lock); led_classdev_unregister(&led_datas[i]->led); kfree(led_datas[i]->data); kfree(led_datas[i]); led_datas[i] = NULL; global_led_datas[i] = NULL; } kfree(led_datas); return 0; } static void s2mpb02_led_shutdown(struct device *dev) { global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness = LED_OFF; led_set(global_led_datas[S2MPB02_TORCH_LED_1], S2MPB02_LED_TURN_WAY_I2C); } static struct platform_driver s2mpb02_led_driver = { .probe = s2mpb02_led_probe, .remove = s2mpb02_led_remove, .driver = { .name = "s2mpb02-led", .owner = THIS_MODULE, .shutdown = s2mpb02_led_shutdown, }, }; static int s2mpb02_led_init(void) { int ret = 0; ret = platform_driver_register(&s2mpb02_led_driver); if (ret) pr_err("s2mpb02_led_driver register failed(%d)", ret); return ret; } module_init(s2mpb02_led_init); static void s2mpb02_led_exit(void) { platform_driver_unregister(&s2mpb02_led_driver); } module_exit(s2mpb02_led_exit); MODULE_DESCRIPTION("S2MPB02 LED driver"); MODULE_LICENSE("GPL");