| /* |
| * leds-sm5713-rgb.c - Service-LED device driver for SM5713 |
| * |
| * Copyright (C) 2017 Samsung Electronics Co.Ltd |
| * |
| * 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. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/of.h> |
| #include <linux/leds.h> |
| #include <linux/of_gpio.h> |
| #include <linux/sec_class.h> |
| #include <linux/mfd/sm5713.h> |
| #include <linux/mfd/sm5713-private.h> |
| |
| #define SM5713_LED_CH_MAX 3 |
| |
| extern unsigned int lcdtype; |
| |
| enum { |
| PATTERN_OFF, |
| CHARGING, |
| CHARGING_ERR, |
| MISSED_NOTI, |
| LOW_BATTERY, |
| FULLY_CHARGED, |
| POWERING, |
| }; |
| |
| enum { |
| CLED_MODE_ALWAYS = 0, |
| CLED_MODE_DIMM = 1, |
| }; |
| |
| struct sm5713_rgb_platform_data { |
| u8 normal_powermode_current; |
| u8 low_powermode_current; |
| int br_ratio_r; |
| int br_ratio_g; |
| int br_ratio_b; |
| int br_ratio_low_r; |
| int br_ratio_low_g; |
| int br_ratio_low_b; |
| int gpio_vdd; |
| |
| u8 index_r; |
| u8 index_g; |
| u8 index_b; |
| }; |
| |
| struct sm5713_rgb_data { |
| struct device *dev; |
| struct i2c_client *i2c; |
| |
| struct sm5713_rgb_platform_data pdata; |
| |
| struct led_classdev led[SM5713_LED_CH_MAX]; |
| struct device *led_dev; |
| |
| unsigned int delay_on_times_ms; |
| unsigned int delay_off_times_ms; |
| |
| unsigned char en_lowpower_mode; |
| int ratio_r; |
| int ratio_g; |
| int ratio_b; |
| u8 brightness; |
| }; |
| |
| static u8 __get_on_time_offset(u32 ms) |
| { |
| u8 offset; |
| |
| if (ms <= 200) { |
| offset = 0x0; |
| } else if (ms <= 300) { |
| offset = 0x1; |
| } else if (ms <= 400) { |
| offset = 0x2; |
| } else if (ms <= 500) { |
| offset = 0x3; |
| } else if (ms <= 600) { |
| offset = 0x4; |
| } else if (ms <= 700) { |
| offset = 0x5; |
| } else if (ms <= 800) { |
| offset = 0x6; |
| } else if (ms <= 900) { |
| offset = 0x7; |
| } else if (ms <= 1000) { |
| offset = 0x8; |
| } else if (ms <= 1300) { |
| offset = 0x9; |
| } else if (ms <= 1600) { |
| offset = 0xA; |
| } else if (ms <= 1800) { |
| offset = 0xB; |
| } else if (ms <= 2000) { |
| offset = 0xC; |
| } else if (ms <= 2500) { |
| offset = 0xD; |
| } else if (ms <= 3000) { |
| offset = 0xE; |
| } else { |
| offset = 0xF; |
| } |
| |
| return offset; |
| } |
| |
| static u8 __get_off_time_offset(u32 ms) |
| { |
| u8 offset; |
| |
| if (ms <= 200) { |
| offset = 0x0; |
| } else if (ms <= 400) { |
| offset = 0x1; |
| } else if (ms <= 500) { |
| offset = 0x2; |
| } else if (ms <= 700) { |
| offset = 0x3; |
| } else if (ms <= 900) { |
| offset = 0x4; |
| } else if (ms <= 1000) { |
| offset = 0x5; |
| } else if (ms <= 1400) { |
| offset = 0x6; |
| } else if (ms <= 1800) { |
| offset = 0x7; |
| } else if (ms <= 2200) { |
| offset = 0x8; |
| } else if (ms <= 2600) { |
| offset = 0x9; |
| } else if (ms <= 3000) { |
| offset = 0xA; |
| } else if (ms <= 4000) { |
| offset = 0xB; |
| } else if (ms <= 5000) { |
| offset = 0xC; |
| } else if (ms <= 6000) { |
| offset = 0xD; |
| } else if (ms <= 8000) { |
| offset = 0xE; |
| } else { |
| offset = 0xF; |
| } |
| |
| return offset; |
| } |
| |
| static void color_led_set_mode(struct sm5713_rgb_data *rgb, u8 index, u8 mode) |
| { |
| u8 offset = 4 + index; |
| |
| sm5713_update_reg(rgb->i2c, SM5713_CHG_REG_LED123MODE, (mode << offset), (0x1 << offset)); |
| } |
| |
| static void color_led_set_enable(struct sm5713_rgb_data *rgb, u8 index, bool enable) |
| { |
| u8 offset = 0 + index; |
| |
| sm5713_update_reg(rgb->i2c, SM5713_CHG_REG_LED123MODE, (enable << offset), (enable << offset)); |
| } |
| |
| static void color_led_do_reset(struct sm5713_rgb_data *rgb) |
| { |
| sm5713_write_reg(rgb->i2c, SM5713_CHG_REG_LED123MODE, 0x0); |
| } |
| |
| static void color_led_set_brightness(struct sm5713_rgb_data *rgb, u8 index, u8 brightness) |
| { |
| if (brightness > 0) |
| dev_info(rgb->dev, "%s: index(%d) brightness(%d.%dmA))\n", __func__, index, brightness/10, brightness%10); |
| else |
| dev_info(rgb->dev, "%s: index(%d) brightness(0mA))\n", __func__, index); |
| sm5713_write_reg(rgb->i2c, SM5713_CHG_REG_LED1CNTL1 + (index * 3), brightness); |
| } |
| |
| #if defined(CONFIG_LEDS_SM5713_RGB_WORKAROUND) |
| static void color_led_set_high_brightness(struct sm5713_rgb_data *rgb, u8 index, u8 brightness) |
| { |
| u8 br_temp = 100; /* 10.0mA */ |
| unsigned long br_udelay = 1000; /* 1ms */ |
| |
| if (brightness == 0) |
| return; |
| |
| dev_info(rgb->dev, "%s: index(%d) brightness(%d.%dmA))\n", __func__, index, br_temp/10, br_temp%10); |
| sm5713_write_reg(rgb->i2c, SM5713_CHG_REG_LED1CNTL1 + (index * 3), br_temp); |
| |
| usleep_range(br_udelay, br_udelay + 10); |
| |
| dev_info(rgb->dev, "%s: index(%d) brightness(%d.%dmA))\n", __func__, index, brightness/10, brightness%10); |
| sm5713_write_reg(rgb->i2c, SM5713_CHG_REG_LED1CNTL1 + (index * 3), brightness); |
| } |
| #endif |
| |
| static void color_led_set_dimm_ctrl(struct sm5713_rgb_data *rgb, u8 index, u8 ramp_up, u8 ramp_down, u8 on_time, u8 off_time) |
| { |
| u8 reg; |
| |
| reg = ((ramp_up & 0xf) << 4) | (ramp_down & 0xf); |
| sm5713_write_reg(rgb->i2c, SM5713_CHG_REG_LED1CNTL2 + (index * 3), reg); |
| reg = ((on_time & 0xf) << 4) | (off_time & 0xf); |
| sm5713_write_reg(rgb->i2c, SM5713_CHG_REG_LED1CNTL3 + (index * 3), reg); |
| } |
| |
| #define PRINT_RGB_REG_NUM 11 |
| static void color_led_print_reg(struct sm5713_rgb_data *rgb) |
| { |
| u8 regs[PRINT_RGB_REG_NUM] = {0x0, }; |
| int i; |
| |
| sm5713_bulk_read(rgb->i2c, SM5713_CHG_REG_LED123MODE, PRINT_RGB_REG_NUM, regs); |
| |
| pr_info("sm5713-rgb: print regmap\n"); |
| for (i = 0; i < PRINT_RGB_REG_NUM; ++i) { |
| pr_info("0x%02x:0x%02x ", SM5713_CHG_REG_LED123MODE + i, regs[i]); |
| if (i % 6 == 0) |
| pr_info("\n"); |
| } |
| } |
| |
| /** |
| * sysfs:sec_class service_led attribute control support |
| */ |
| |
| static ssize_t store_led_r(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) |
| { |
| struct sm5713_rgb_data *rgb = dev_get_drvdata(dev); |
| u32 brightness; |
| int ret; |
| |
| ret = kstrtouint(buf, 0, &brightness); |
| if (ret) { |
| dev_err(dev, "%s: failed get brightness.\n", __func__); |
| return ret; |
| } |
| |
| color_led_set_brightness(rgb, rgb->pdata.index_r, brightness); |
| color_led_set_mode(rgb, rgb->pdata.index_r, CLED_MODE_ALWAYS); |
| color_led_set_enable(rgb, rgb->pdata.index_r, (brightness > 0 ? 1 : 0)); |
| #if defined(CONFIG_LEDS_SM5713_RGB_WORKAROUND) |
| color_led_set_high_brightness(rgb, rgb->pdata.index_r, brightness); |
| #endif |
| |
| dev_dbg(dev, "%s: curr=0x%x, mode=always LED-%s\n", __func__, brightness, (brightness) ? "ON" : "OFF"); |
| |
| return count; |
| } |
| |
| static ssize_t store_led_g(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) |
| { |
| struct sm5713_rgb_data *rgb = dev_get_drvdata(dev); |
| u32 brightness; |
| int ret; |
| |
| ret = kstrtouint(buf, 0, &brightness); |
| if (ret) { |
| dev_err(dev, "%s: failed get brightness.\n", __func__); |
| return ret; |
| } |
| |
| color_led_set_brightness(rgb, rgb->pdata.index_g, brightness); |
| color_led_set_mode(rgb, rgb->pdata.index_g, CLED_MODE_ALWAYS); |
| color_led_set_enable(rgb, rgb->pdata.index_g, (brightness > 0 ? 1 : 0)); |
| |
| dev_dbg(dev, "%s: curr=0x%x, mode=always LED-%s\n", __func__, brightness, (brightness) ? "ON" : "OFF"); |
| |
| return count; |
| } |
| |
| static ssize_t store_led_b(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) |
| { |
| struct sm5713_rgb_data *rgb = dev_get_drvdata(dev); |
| u32 brightness; |
| int ret; |
| |
| ret = kstrtouint(buf, 0, &brightness); |
| if (ret) { |
| dev_err(dev, "%s: failed get brightness.\n", __func__); |
| return ret; |
| } |
| |
| color_led_set_brightness(rgb, rgb->pdata.index_b, brightness); |
| color_led_set_mode(rgb, rgb->pdata.index_b, CLED_MODE_ALWAYS); |
| color_led_set_enable(rgb, rgb->pdata.index_b, (brightness > 0 ? 1 : 0)); |
| |
| dev_dbg(dev, "%s: curr=0x%x, mode=always LED-%s\n", __func__, brightness, (brightness) ? "ON" : "OFF"); |
| |
| return count; |
| |
| } |
| |
| static ssize_t show_led_brightness(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct sm5713_rgb_data *rgb = dev_get_drvdata(dev); |
| |
| return snprintf(buf, 4, "%d\n", rgb->brightness); |
| } |
| |
| static ssize_t store_led_brightness(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) |
| { |
| struct sm5713_rgb_data *rgb = dev_get_drvdata(dev); |
| u8 brightness; |
| int ret; |
| |
| ret = kstrtou8(buf, 0, &brightness); |
| if (ret) { |
| dev_err(dev, "%s: failed get brightness.\n", __func__); |
| return ret; |
| } |
| |
| rgb->en_lowpower_mode = 0; |
| rgb->brightness = brightness; |
| dev_info(dev, "%s: store brightness = 0x%x\n", __func__, brightness); |
| |
| return count; |
| } |
| |
| static ssize_t show_led_lowpower(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct sm5713_rgb_data *rgb = dev_get_drvdata(dev); |
| |
| return snprintf(buf, 4, "%d\n", rgb->en_lowpower_mode); |
| } |
| |
| static ssize_t store_led_lowpower(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) |
| { |
| struct sm5713_rgb_data *rgb = dev_get_drvdata(dev); |
| u8 temp; |
| int ret; |
| |
| ret = kstrtou8(buf, 0, &temp); |
| if (ret) { |
| dev_err(dev, "%s : failed get led_lowpower_mode.\n", __func__); |
| return ret; |
| } |
| |
| rgb->en_lowpower_mode = temp; |
| dev_info(dev, "%s: led_lowpower mode = %d\n", __func__, rgb->en_lowpower_mode); |
| |
| rgb->brightness = !temp ? rgb->pdata.normal_powermode_current : rgb->pdata.low_powermode_current; |
| |
| if (temp) { /* low power mode */ |
| rgb->ratio_r = rgb->pdata.br_ratio_low_r; |
| rgb->ratio_g = rgb->pdata.br_ratio_low_g; |
| rgb->ratio_b = rgb->pdata.br_ratio_low_b; |
| } else { /* normal power mode */ |
| rgb->ratio_r = rgb->pdata.br_ratio_r; |
| rgb->ratio_g = rgb->pdata.br_ratio_g; |
| rgb->ratio_b = rgb->pdata.br_ratio_b; |
| } |
| |
| return count; |
| } |
| |
| static u8 calc_led_br(struct sm5713_rgb_data *rgb, u8 brightness, int ratio) |
| { |
| u8 br_curr; |
| |
| br_curr = rgb->brightness * brightness * ratio / 0xff / 100; |
| |
| br_curr = (br_curr > 0) ? br_curr : 1; |
| |
| return br_curr; |
| } |
| |
| static ssize_t store_led_blink(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) |
| { |
| struct sm5713_rgb_data *rgb = dev_get_drvdata(dev); |
| u32 brightness, on_time, off_time; |
| u8 led_r, led_g, led_b, br_curr, t_on, t_off; |
| int ret; |
| |
| ret = sscanf(buf, "0x%8x %5d %5d", &brightness, &on_time, &off_time); |
| if (!ret) { |
| dev_err(dev, "%s: failed get led_blink value.\n", __func__); |
| return ret; |
| } |
| led_r = ((brightness & 0xFF0000) >> 16); |
| led_g = ((brightness & 0x00FF00) >> 8); |
| led_b = ((brightness & 0x0000FF) >> 0); |
| t_on = __get_on_time_offset(on_time); |
| t_off = __get_off_time_offset(off_time); |
| |
| |
| dev_info(dev, "%s: RGB=0x%02x:0x%02x:0x%02x, on_t=%d(0x%x), off_t=%d(0x%x)\n", |
| __func__, led_r, led_g, led_b, on_time, t_on, off_time, t_off); |
| |
| color_led_do_reset(rgb); |
| |
| if (led_r) { |
| br_curr = calc_led_br(rgb, led_r, rgb->ratio_r); |
| color_led_set_brightness(rgb, rgb->pdata.index_r, br_curr); |
| if (t_off > 0) { |
| color_led_set_mode(rgb, rgb->pdata.index_r, CLED_MODE_DIMM); |
| color_led_set_dimm_ctrl(rgb, rgb->pdata.index_r, 0, 0, t_on, t_off); |
| } |
| color_led_set_enable(rgb, rgb->pdata.index_r, 1); |
| #if defined(CONFIG_LEDS_SM5713_RGB_WORKAROUND) |
| color_led_set_high_brightness(rgb, rgb->pdata.index_r, br_curr); |
| #endif |
| } |
| |
| if (led_g) { |
| br_curr = calc_led_br(rgb, led_g, rgb->ratio_g); |
| color_led_set_brightness(rgb, rgb->pdata.index_g, br_curr); |
| if (t_off > 0) { |
| color_led_set_mode(rgb, rgb->pdata.index_g, CLED_MODE_DIMM); |
| color_led_set_dimm_ctrl(rgb, rgb->pdata.index_g, 0, 0, t_on, t_off); |
| } |
| color_led_set_enable(rgb, rgb->pdata.index_g, 1); |
| } |
| |
| if (led_b) { |
| br_curr = calc_led_br(rgb, led_b, rgb->ratio_b); |
| color_led_set_brightness(rgb, rgb->pdata.index_b, br_curr); |
| if (t_off > 0) { |
| color_led_set_mode(rgb, rgb->pdata.index_b, CLED_MODE_DIMM); |
| color_led_set_dimm_ctrl(rgb, rgb->pdata.index_b, 0, 0, t_on, t_off); |
| } |
| color_led_set_enable(rgb, rgb->pdata.index_b, 1); |
| } |
| |
| color_led_print_reg(rgb); |
| |
| return count; |
| } |
| |
| static ssize_t store_led_pattern(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) |
| { |
| struct sm5713_rgb_data *rgb = dev_get_drvdata(dev); |
| int ret, mode; |
| u8 br_curr; |
| |
| ret = sscanf(buf, "%1d", &mode); |
| if (!ret) { |
| dev_err(dev, "%s: failed get led_pattern mode.\n", __func__); |
| return ret; |
| } |
| rgb->brightness = rgb->en_lowpower_mode ? rgb->pdata.low_powermode_current : rgb->pdata.normal_powermode_current; |
| |
| dev_info(dev, "%s: pattern=%d\n", __func__, mode); |
| |
| color_led_do_reset(rgb); |
| |
| switch (mode) { |
| case CHARGING: |
| /* LED_R constant mode ON */ |
| br_curr = rgb->brightness * rgb->ratio_r / 100; |
| color_led_set_brightness(rgb, rgb->pdata.index_r, br_curr); |
| color_led_set_enable(rgb, rgb->pdata.index_r, 1); |
| #if defined(CONFIG_LEDS_SM5713_RGB_WORKAROUND) |
| color_led_set_high_brightness(rgb, rgb->pdata.index_r, br_curr); |
| #endif |
| break; |
| case CHARGING_ERR: |
| /* LED_R slope mode ON (500ms to 500ms) */ |
| br_curr = rgb->brightness * rgb->ratio_r / 100; |
| color_led_set_brightness(rgb, rgb->pdata.index_r, br_curr); |
| color_led_set_mode(rgb, rgb->pdata.index_r, CLED_MODE_DIMM); |
| color_led_set_dimm_ctrl(rgb, rgb->pdata.index_r, 0, 0, 0x3, 0x2); |
| color_led_set_enable(rgb, rgb->pdata.index_r, 1); |
| #if defined(CONFIG_LEDS_SM5713_RGB_WORKAROUND) |
| color_led_set_high_brightness(rgb, rgb->pdata.index_r, br_curr); |
| #endif |
| break; |
| case MISSED_NOTI: |
| /* LED_B slope mode ON (500ms to 5000ms) */ |
| br_curr = rgb->brightness * rgb->ratio_b / 100; |
| color_led_set_brightness(rgb, rgb->pdata.index_b, br_curr); |
| color_led_set_mode(rgb, rgb->pdata.index_b, CLED_MODE_DIMM); |
| color_led_set_dimm_ctrl(rgb, rgb->pdata.index_b, 0, 0, 0x3, 0xc); |
| color_led_set_enable(rgb, rgb->pdata.index_b, 1); |
| break; |
| case LOW_BATTERY: |
| /* LED_R slope mode ON (500ms to 5000ms) */ |
| br_curr = rgb->brightness * rgb->ratio_r / 100; |
| color_led_set_brightness(rgb, rgb->pdata.index_r, br_curr); |
| color_led_set_mode(rgb, rgb->pdata.index_r, CLED_MODE_DIMM); |
| color_led_set_dimm_ctrl(rgb, rgb->pdata.index_r, 0, 0, 0x3, 0xc); |
| color_led_set_enable(rgb, rgb->pdata.index_r, 1); |
| #if defined(CONFIG_LEDS_SM5713_RGB_WORKAROUND) |
| color_led_set_high_brightness(rgb, rgb->pdata.index_r, br_curr); |
| #endif |
| break; |
| case FULLY_CHARGED: |
| /* LED_G constant mode ON */ |
| br_curr = rgb->brightness * rgb->ratio_g / 100; |
| color_led_set_brightness(rgb, rgb->pdata.index_g, br_curr); |
| color_led_set_enable(rgb, rgb->pdata.index_g, 1); |
| break; |
| case POWERING: |
| /* LED_G & LED_B slope mode ON (1000ms to 1000ms) */ |
| br_curr = rgb->brightness * rgb->ratio_g / 100; |
| color_led_set_brightness(rgb, rgb->pdata.index_g, br_curr); |
| color_led_set_mode(rgb, rgb->pdata.index_g, CLED_MODE_DIMM); |
| color_led_set_dimm_ctrl(rgb, rgb->pdata.index_g, 0xf, 0xf, 0x8, 0x5); /* Ramp up/down time = 1024ms */ |
| br_curr = rgb->brightness * rgb->ratio_b / 100; |
| color_led_set_brightness(rgb, rgb->pdata.index_b, br_curr); |
| #if 0 |
| color_led_set_mode(rgb, rgb->pdata.index_b, CLED_MODE_DIMM); |
| color_led_set_dimm_ctrl(rgb, rgb->pdata.index_b, 0x2, 0x2, 0x8, 0x5); /* Ramp up/down time = 4ms */ |
| #endif |
| color_led_set_enable(rgb, rgb->pdata.index_g, 1); |
| color_led_set_enable(rgb, rgb->pdata.index_b, 1); |
| break; |
| case PATTERN_OFF: |
| break; |
| default: |
| break; |
| } |
| |
| color_led_print_reg(rgb); |
| |
| return count; |
| } |
| |
| /* SAMSUNG specific attribute nodes */ |
| static DEVICE_ATTR(led_r, 0660, NULL, store_led_r); |
| static DEVICE_ATTR(led_g, 0660, NULL, store_led_g); |
| static DEVICE_ATTR(led_b, 0660, NULL, store_led_b); |
| static DEVICE_ATTR(led_pattern, 0660, NULL, store_led_pattern); |
| static DEVICE_ATTR(led_blink, 0660, NULL, store_led_blink); |
| static DEVICE_ATTR(led_brightness, 0660, show_led_brightness, store_led_brightness); |
| static DEVICE_ATTR(led_lowpower, 0660, show_led_lowpower, store_led_lowpower); |
| |
| static struct attribute *sec_led_attributes[] = { |
| &dev_attr_led_r.attr, |
| &dev_attr_led_g.attr, |
| &dev_attr_led_b.attr, |
| &dev_attr_led_pattern.attr, |
| &dev_attr_led_blink.attr, |
| &dev_attr_led_brightness.attr, |
| &dev_attr_led_lowpower.attr, |
| NULL, |
| }; |
| |
| static struct attribute_group sec_led_attr_group = { |
| .attrs = sec_led_attributes, |
| }; |
| |
| #ifdef CONFIG_OF |
| #if 0 |
| static inline void _decide_octa(char *octa, unsigned char octa_color) |
| { |
| switch (octa_color) { |
| case 1: |
| strcpy(octa, "_bk"); break; |
| case 2: |
| strcpy(octa, "_wt"); break; |
| default: |
| break; |
| } |
| } |
| |
| static void make_property_string(char *dst, const char *src, const char *octa) |
| { |
| strcpy(dst, src); |
| strcat(dst, octa); |
| } |
| #endif |
| |
| static int sm5713_rgb_parse_dt(struct sm5713_rgb_data *rgb, unsigned char octa_color, char **leds_name) |
| { |
| struct device_node *nproot = rgb->dev->parent->of_node; |
| struct device_node *np; |
| int i, ret, temp; |
| |
| np = of_find_node_by_name(nproot, "sm5713_rgb"); |
| if (unlikely(np == NULL)) { |
| dev_err(rgb->dev, "failed find rgb node\n"); |
| return -ENOENT; |
| } |
| |
| for (i = 0; i < SM5713_LED_CH_MAX; ++i) { |
| ret = of_property_read_string_index(np, "rgb-name", i, (const char **)&leds_name[i]); |
| if (!(strcmp(leds_name[i], "red")) || !(strcmp(leds_name[i], "led_r"))) { |
| rgb->pdata.index_r = i; |
| } |
| if (!(strcmp(leds_name[i], "green")) || !(strcmp(leds_name[i], "led_g"))) { |
| rgb->pdata.index_g = i; |
| } |
| if (!(strcmp(leds_name[i], "blue")) || !(strcmp(leds_name[i], "led_b"))) { |
| rgb->pdata.index_b = i; |
| } |
| |
| dev_info(rgb->dev, "rgb-name[%d] string: ""%s""\n", i, leds_name[i]); |
| |
| |
| if (ret) { |
| return -ENOENT; |
| } |
| } |
| |
| rgb->pdata.gpio_vdd = of_get_named_gpio(np, "rgb,vdd-gpio", 0); |
| if (rgb->pdata.gpio_vdd < 0) { |
| dev_info(rgb->dev, "don't support gpio to lift up power supply!\n"); |
| } |
| |
| /* _decide_octa(octa, octa_color); */ |
| |
| /* parsing dt:rgb-normal_powermode_current */ |
| ret = of_property_read_u32(np, "normal_powermode_current", &temp); |
| if (ret) { |
| dev_err(rgb->dev, "can't parsing [%s] in RGB dt\n", "normal_powermode_current"); |
| } else { |
| rgb->pdata.normal_powermode_current = temp & 0xff; |
| } |
| |
| /* parsing dt:rgb-low_powermode_current */ |
| ret = of_property_read_u32(np, "low_powermode_current", &temp); |
| if (ret) { |
| dev_err(rgb->dev, "can't parsing [%s] in RGB dt\n", "low_powermode_current"); |
| } else { |
| rgb->pdata.low_powermode_current = temp & 0xff; |
| } |
| |
| /* parsing dt:rgb-br_ratio_r */ |
| ret = of_property_read_u32(np, "br_ratio_r", &temp); |
| if (ret) { |
| dev_err(rgb->dev, "can't parsing [%s] in RGB dt\n", "br_ratio_r"); |
| } else { |
| rgb->pdata.br_ratio_r = temp; |
| } |
| |
| /* parsing dt:rgb-br_ratio_g */ |
| ret = of_property_read_u32(np, "br_ratio_g", &temp); |
| if (ret) { |
| dev_err(rgb->dev, "can't parsing [%s] in RGB dt\n", "br_ratio_g"); |
| } else { |
| rgb->pdata.br_ratio_g = temp; |
| } |
| |
| /* parsing dt:rgb-br_ratio_b */ |
| ret = of_property_read_u32(np, "br_ratio_b", &temp); |
| if (ret) { |
| dev_err(rgb->dev, "can't parsing [%s] in RGB dt\n", "br_ratio_b"); |
| } else { |
| rgb->pdata.br_ratio_b = temp; |
| } |
| |
| dev_info(rgb->dev, "n_pwr=0x%x, l_pwr=0x%x, rto_r=%d, rto_g=%d, rto_b=%d gpio=%d rgb_index=(%d:%d:%d) \n", |
| rgb->pdata.normal_powermode_current, rgb->pdata.low_powermode_current, |
| rgb->pdata.br_ratio_r, rgb->pdata.br_ratio_g, rgb->pdata.br_ratio_b, |
| rgb->pdata.gpio_vdd, rgb->pdata.index_r, rgb->pdata.index_g, rgb->pdata.index_b); |
| |
| |
| ret = of_property_read_u32(np, "br_ratio_low_r", &temp); |
| if (ret) { |
| dev_err(rgb->dev, "can't parsing [%s] in RGB dt\n", "br_ratio_low_r"); |
| } else { |
| rgb->pdata.br_ratio_low_r = temp; |
| } |
| |
| ret = of_property_read_u32(np, "br_ratio_low_g", &temp); |
| if (ret) { |
| dev_err(rgb->dev, "can't parsing [%s] in RGB dt\n", "br_ratio_low_g"); |
| } else { |
| rgb->pdata.br_ratio_low_g = temp; |
| } |
| |
| ret = of_property_read_u32(np, "br_ratio_low_b", &temp); |
| if (ret) { |
| dev_err(rgb->dev, "can't parsing [%s] in RGB dt\n", "br_ratio_low_b"); |
| } else { |
| rgb->pdata.br_ratio_low_b = temp; |
| } |
| |
| dev_info(rgb->dev, "rto_low_r=%d, rto_low_g=%d, rto_low_b=%d \n", |
| rgb->pdata.br_ratio_low_r, rgb->pdata.br_ratio_low_g, rgb->pdata.br_ratio_low_b ); |
| |
| return 0; |
| } |
| |
| #endif |
| |
| static int sm5713_rgb_probe(struct platform_device *pdev) |
| { |
| struct sm5713_dev *sm5713 = dev_get_drvdata(pdev->dev.parent); |
| struct sm5713_rgb_data *rgb; |
| unsigned char octa_color = (lcdtype & 0x0F0000) >> 16; |
| char *leds_name[SM5713_LED_CH_MAX]; |
| int ret; |
| |
| rgb = devm_kzalloc(&pdev->dev, sizeof(struct sm5713_rgb_data), GFP_KERNEL); |
| if (unlikely(!rgb)) { |
| dev_err(&pdev->dev, "%s: failed alloc_devmem\n", __func__); |
| return -ENOMEM; |
| } |
| rgb->i2c = sm5713->charger; |
| rgb->dev = &pdev->dev; |
| |
| dev_info(rgb->dev, " %s: lcdtype=0x%x, octa_color=0x%x\n", __func__, lcdtype, octa_color); |
| |
| #ifdef CONFIG_OF |
| ret = sm5713_rgb_parse_dt(rgb, octa_color, leds_name); |
| if (ret) { |
| dev_err(rgb->dev, "failed parse dt (ret=%d)\n", ret); |
| goto free_devm; |
| } |
| #else |
| |
| rgb->pdata.normal_powermode_current = 0x28; |
| rgb->pdata.low_powermode_current = 0x5; |
| rgb->pdata.br_ratio_r = 100; |
| rgb->pdata.br_ratio_g = 100; |
| rgb->pdata.br_ratio_b = 100; |
| rgb->pdata.br_ratio_low_r = 10; |
| rgb->pdata.br_ratio_low_g = 10; |
| rgb->pdata.br_ratio_low_b = 10; |
| rgb->pdata.gpio_vdd = -1; |
| rgb->pdata.index_r = 0; |
| rgb->pdata.index_g = 1; |
| rgb->pdata.index_b = 2; |
| #endif |
| |
| rgb->ratio_r = rgb->pdata.br_ratio_r; |
| rgb->ratio_g = rgb->pdata.br_ratio_g; |
| rgb->ratio_b = rgb->pdata.br_ratio_b; |
| rgb->brightness= rgb->pdata.normal_powermode_current; |
| |
| rgb->led_dev = sec_device_create(rgb, "led"); |
| if (unlikely(!rgb->led_dev)) { |
| dev_err(rgb->dev, "failed create sec_class led-dev\n"); |
| goto free_devm; |
| } |
| |
| ret = sysfs_create_group(&rgb->led_dev->kobj, &sec_led_attr_group); |
| if (ret) { |
| dev_err(rgb->dev, "failed create sysfs:sec_led_attr\n"); |
| goto free_device; |
| } |
| platform_set_drvdata(pdev, rgb); |
| |
| if (rgb->pdata.gpio_vdd > 0) { |
| ret = gpio_request(rgb->pdata.gpio_vdd, "sm5713-rgb_vdd_supply"); |
| if (ret < 0) { |
| dev_err(rgb->dev, "failed request_gpio(%d) used vdd_supply\n", rgb->pdata.gpio_vdd); |
| } |
| } |
| |
| #if defined(CONFIG_LEDS_USE_ED28) && defined(CONFIG_SEC_FACTORY) |
| if (lcdtype == 0 && jig_status == false) { |
| /* LED_R constant mode ON */ |
| color_led_set_brightness(rgb, rgb->pdata.index_r, rgb->pdata.normal_powermode_current); |
| color_led_set_mode(rgb, rgb->pdata.index_r, CLED_MODE_ALWAYS); |
| } |
| #endif |
| |
| dev_info(rgb->dev, "%s: probe done\n (rev=%d)", __func__, sm5713->pmic_rev); |
| |
| return 0; |
| |
| |
| free_device: |
| sec_device_destroy(rgb->led_dev->devt); |
| free_devm: |
| devm_kfree(&pdev->dev, rgb); |
| |
| return ret; |
| } |
| |
| static int sm5713_rgb_remove(struct platform_device *pdev) |
| { |
| struct sm5713_rgb_data *rgb = platform_get_drvdata(pdev); |
| |
| color_led_do_reset(rgb); |
| |
| sysfs_remove_group(&rgb->led_dev->kobj, &sec_led_attr_group); |
| |
| devm_kfree(&pdev->dev, rgb); |
| |
| return 0; |
| } |
| |
| static void sm5713_rgb_shutdown(struct platform_device *pdev) |
| { |
| struct sm5713_rgb_data *rgb = platform_get_drvdata(pdev); |
| |
| color_led_do_reset(rgb); |
| } |
| |
| static struct platform_driver sm5713_rgbled_driver = { |
| .driver = { |
| .name = "sm5713-rgb", |
| .owner = THIS_MODULE, |
| }, |
| .probe = sm5713_rgb_probe, |
| .remove = sm5713_rgb_remove, |
| .shutdown = sm5713_rgb_shutdown, |
| }; |
| |
| static int __init sm5713_rgb_init(void) |
| { |
| return platform_driver_register(&sm5713_rgbled_driver); |
| } |
| module_init(sm5713_rgb_init); |
| |
| static void __exit sm5713_rgb_exit(void) |
| { |
| platform_driver_unregister(&sm5713_rgbled_driver); |
| } |
| module_exit(sm5713_rgb_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Samsung Electronics"); |
| MODULE_DESCRIPTION("Flash-LED device driver for SM5713"); |
| |