/* * RGB-LED device driver for SM5705 * * Copyright (C) 2015 Silicon Mitus * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_OF #include #endif enum sm5705_rgb_color_index { LED_R = 0x0, LED_G, LED_B, LED_MAX, }; enum se_led_pattern_index { PATTERN_OFF, CHARGING, CHARGING_ERR, MISSED_NOTI, LOW_BATTERY, FULLY_CHARGED, POWERING, }; #define LED_MAX_CURRENT 0xFF struct sm5705_rgb { struct device *dev; struct i2c_client *i2c; struct led_classdev led[LED_MAX]; struct device *led_dev; unsigned int delay_on_times_ms; unsigned int delay_off_times_ms; unsigned char en_lowpower_mode; }; /* device configuartion parameters */ static unsigned char normal_powermode_current = 0x28; static unsigned char low_powermode_current = 0x05; static unsigned char br_ratio_r = 100; static unsigned char br_ratio_g = 100; static unsigned char br_ratio_b = 100; static int gpio_vdd = -1; //extern unsigned int lcdtype; extern struct class *sec_class; /** * SM5705 RGB-LED device control functions */ static unsigned char calc_delaytime_offset_to_ms(struct sm5705_rgb *sm5705_rgb, unsigned int ms) { unsigned char time; if (ms > 7500) { dev_dbg(sm5705_rgb->dev, "SM5705 delay time limit = 7.5sec, correct input value (ms=%d)\n", ms); time = 0xF; } else { time = ms / 500; } return time; } static unsigned char calc_steptime_offset_to_ms(struct sm5705_rgb *sm5705_rgb, unsigned int ms) { unsigned char time; if (ms > 60) { dev_dbg(sm5705_rgb->dev, "SM5705 step time limit = 60ms, correct input value (ms=%d)\n", ms); time = 0xF; } else { time = ms / 4; } return time; } static int sm5705_rgb_set_LEDx_enable(struct sm5705_rgb *sm5705_rgb, int index, bool mode, bool enable) { unsigned char reg_val; int ret; if(gpio_vdd > 0) { gpio_direction_output(gpio_vdd, enable ? 1 : 0); msleep(5); } ret = sm5705_read_reg(sm5705_rgb->i2c, SM5705_REG_CNTLMODEONOFF, ®_val); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to read REG:SM5705_REG_CNTLMODEONOFF\n", __func__); return ret; } if (mode) { reg_val |= (1 << (4 + index)); } else { reg_val &= ~(1 << (4 + index)); } if (enable) { reg_val |= (1 << (0 + index)); } else { reg_val &= ~(1 << (0 + index)); } ret = sm5705_write_reg(sm5705_rgb->i2c, SM5705_REG_CNTLMODEONOFF, reg_val); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to write REG:SM5705_REG_CNTLMODEONOFF\n", __func__); return ret; } dev_info(sm5705_rgb->dev, "%s: REG:SM5705_REG_CNTLMODEONOFF = 0x%x\n", __func__, reg_val); return 0; } static int sm5705_rgb_set_LEDx_current(struct sm5705_rgb *sm5705_rgb, int index, unsigned char cur_value) { int ret; ret = sm5705_write_reg(sm5705_rgb->i2c, SM5705_REG_RLEDCURRENT + index, cur_value); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to write LED[%d] CURRENT REG (value=%d)\n", __func__, index, cur_value); return ret; } dev_info(sm5705_rgb->dev, "%s: led current is %d\n", __func__, cur_value); return 0; } static int sm5705_rgb_get_LEDx_current(struct sm5705_rgb *sm5705_rgb, int index) { unsigned char reg_val; int ret; /* check led on/off state, if led off than current must be 0 */ ret = sm5705_read_reg(sm5705_rgb->i2c, SM5705_REG_CNTLMODEONOFF, ®_val); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to read REG:SM5705_REG_CNTLMODEONOFF\n", __func__); return ret; } if (!(reg_val & (1 << (0 + index)))) { return 0; } ret = sm5705_read_reg(sm5705_rgb->i2c, SM5705_REG_RLEDCURRENT + index, ®_val); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to read LED[%d] CURRENT REG\n", __func__, index); return ret; } return reg_val; } static int sm5705_rgb_set_LEDx_slopeduty(struct sm5705_rgb *sm5705_rgb, int index, unsigned char max_duty, unsigned char mid_duty, unsigned char min_duty) { unsigned char reg_val; int ret; reg_val = ((max_duty & 0xF) << 4) | (mid_duty & 0xF); ret = sm5705_write_reg(sm5705_rgb->i2c, SM5705_REG_RLEDCNTL1 + (4 * index), reg_val); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to write LED[%d] LEDCNTL1 REG (value=%d)\n", __func__, index, reg_val); return ret; } ret = sm5705_update_reg(sm5705_rgb->i2c, SM5705_REG_RLEDCNTL2 + (index * 4), (min_duty & 0xF), 0x0F); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to update LED[%d] MINDUTY REG (value=%d)\n", __func__, index, min_duty); return ret; } return 0; } static int sm5705_rgb_set_LEDx_slopemode(struct sm5705_rgb *sm5705_rgb, u32 index, u32 delay_ms, u32 delay_on_time_ms, u32 delay_off_time_ms, u32 step_on_time_ms, u32 step_off_time_ms) { unsigned char delay, tt1, tt2, dt1, dt2, dt3, dt4; int ret; delay = calc_delaytime_offset_to_ms(sm5705_rgb, delay_ms); tt1 = calc_delaytime_offset_to_ms(sm5705_rgb, delay_on_time_ms); tt2 = calc_delaytime_offset_to_ms(sm5705_rgb, delay_off_time_ms); dt1 = dt2 = calc_steptime_offset_to_ms(sm5705_rgb, step_on_time_ms); dt3 = dt4 = calc_steptime_offset_to_ms(sm5705_rgb, step_off_time_ms); dev_info(sm5705_rgb->dev, "%s: LED[%d] slopemode (delay:0x%x, tt1:0x%x, tt2:0x%x, dt1:0x%x, dt2:0x%x, dt3:0x%x, dt4:0x%x)\n", __func__, index, delay, tt1, tt2, dt1, dt2, dt3, dt4); ret = sm5705_update_reg(sm5705_rgb->i2c, SM5705_REG_RLEDCNTL2 + (index * 4), (delay & 0xF), 0xF0); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to update LED[%d] START DELAY REG (value=%d)\n", __func__, index, delay); return ret; } ret = sm5705_write_reg(sm5705_rgb->i2c, SM5705_REG_DIMSLPRLEDCNTL + index, (((tt2 & 0xF) << 4) | (tt1 & 0xF))); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to write LED[%d] DIMSLPLEDCNTL REG (value=%d)\n", __func__, index, (((tt2 & 0xF) << 4) | (tt1 & 0xF))); return ret; } ret = sm5705_write_reg(sm5705_rgb->i2c, SM5705_REG_RLEDCNTL3 + (index * 4), (((dt2 & 0xF) << 4) | (dt1 & 0xF))); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to write LED[%d] LEDCNTL3\n", __func__, index); return ret; } ret = sm5705_write_reg(sm5705_rgb->i2c, SM5705_REG_RLEDCNTL4 + (index * 4), (((dt4 & 0xF) << 4) | (dt3 & 0xF))); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to write LED[%d] LEDCNTL3\n", __func__, index); return ret; } return 0; } static int sm5705_rgb_reset(struct sm5705_rgb *sm5705_rgb) { int i, ret; /* RGB current & start delay reset */ for (i=0; i < LED_MAX; ++i) { ret = sm5705_write_reg(sm5705_rgb->i2c, SM5705_REG_RLEDCURRENT + i, 0); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to write LED[%d] CURRENT REG\n", __func__, i); return ret; } ret = sm5705_update_reg(sm5705_rgb->i2c, SM5705_REG_RLEDCNTL2 + (i * 4), 0, 0xF0); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to update LED[%d] START DELAY REG\n", __func__, i); return ret; } } /* RGB LED OFF */ ret = sm5705_write_reg(sm5705_rgb->i2c, SM5705_REG_CNTLMODEONOFF, 0); if (IS_ERR_VALUE(ret)) { dev_err(sm5705_rgb->dev, "%s: fail to write REG:SM5705_REG_CNTLMODEONOFF\n", __func__); return ret; } return 0; } /** * SM5705 RGB-LED SAMSUNG specific led device control functions */ static ssize_t store_led_r(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev); unsigned int brightness; int ret; dev_dbg(dev, "%s\n", __func__); ret = kstrtouint(buf, 0, &brightness); if (IS_ERR_VALUE(ret)) { dev_err(dev, "%s: fail to get brightness.\n", __func__); goto out; } ret = sm5705_rgb_set_LEDx_current(sm5705_rgb, LED_R, brightness); if (IS_ERR_VALUE(ret)) { goto out; } ret = sm5705_rgb_set_LEDx_enable(sm5705_rgb, LED_R, 0, (bool)brightness); if (IS_ERR_VALUE(ret)) { goto out; } dev_dbg(dev, "%s:current=0x%x, constant %s\n", __func__, brightness, (brightness) ? "ON" : "OFF"); out: return count; } static ssize_t store_led_g(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev); unsigned int brightness; int ret; dev_dbg(dev, "%s\n", __func__); ret = kstrtouint(buf, 0, &brightness); if (IS_ERR_VALUE(ret)) { dev_err(dev, "%s: fail to get brightness.\n", __func__); goto out; } ret = sm5705_rgb_set_LEDx_current(sm5705_rgb, LED_G, brightness); if (IS_ERR_VALUE(ret)) { goto out; } ret = sm5705_rgb_set_LEDx_enable(sm5705_rgb, LED_G, 0, (bool)brightness); if (IS_ERR_VALUE(ret)) { goto out; } dev_dbg(dev, "%s: current=0x%x, constant %s\n", __func__, brightness, (brightness) ? "ON" : "OFF"); out: return count; } static ssize_t store_led_b(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev); unsigned int brightness; int ret; dev_dbg(dev, "%s\n", __func__); ret = kstrtouint(buf, 0, &brightness); if (IS_ERR_VALUE(ret)) { dev_err(dev, "%s: fail to get brightness.\n", __func__); goto out; } ret = sm5705_rgb_set_LEDx_current(sm5705_rgb, LED_B, brightness); if (IS_ERR_VALUE(ret)) { goto out; } ret = sm5705_rgb_set_LEDx_enable(sm5705_rgb, LED_B, 0, (bool)brightness); if (IS_ERR_VALUE(ret)) { goto out; } dev_dbg(dev, "%s: current=0x%x, constant %s\n", __func__, brightness, (brightness) ? "ON" : "OFF"); out: return count; } static ssize_t show_sm5705_rgb_brightness(struct device *dev, struct device_attribute *attr, char *buf) { return snprintf(buf, 4, "%d\n", normal_powermode_current); } static ssize_t store_sm5705_rgb_brightness(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev); u8 brightness; int ret; ret = kstrtou8(buf, 0, &brightness); if (IS_ERR_VALUE(ret)) { dev_err(dev, "%s: fail to get brightness.\n", __func__); goto out; } sm5705_rgb->en_lowpower_mode = 0; if(brightness > LED_MAX_CURRENT) normal_powermode_current = LED_MAX_CURRENT; else normal_powermode_current = brightness; dev_info(dev, "%s: store brightness = 0x%x\n", __func__, brightness); out: return count; } static ssize_t show_sm5705_rgb_lowpower(struct device *dev, struct device_attribute *attr, char *buf) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev); return snprintf(buf, 4, "%d\n", sm5705_rgb->en_lowpower_mode); } static ssize_t store_sm5705_rgb_lowpower(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev); int ret; ret = kstrtou8(buf, 0, &sm5705_rgb->en_lowpower_mode); if (IS_ERR_VALUE(ret)) { dev_err(dev, "%s : fail to get led_lowpower_mode.\n", __func__); goto out; } dev_info(dev, "%s: led_lowpower mode set to %i\n", __func__, sm5705_rgb->en_lowpower_mode); out: return count; } static unsigned char calc_led_current_from_brightness(struct sm5705_rgb *sm5705_rgb, int index, unsigned char brightness) { unsigned char cur_value; cur_value = normal_powermode_current * brightness / LED_MAX_CURRENT; if (!cur_value) { cur_value = 1; } switch (index) { case LED_R: cur_value = (cur_value * br_ratio_r) / 100; break; case LED_G: cur_value = (cur_value * br_ratio_g) / 100; break; case LED_B: cur_value = (cur_value * br_ratio_b) / 100; break; } if (!cur_value) { cur_value = 1; } return cur_value; } static ssize_t store_sm5705_rgb_blink(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev); unsigned int led_brightness; unsigned char led_br[3]; unsigned char led_current; int i, ret; ret = sscanf(buf, "0x%8x %5d %5d", &led_brightness, &sm5705_rgb->delay_on_times_ms, &sm5705_rgb->delay_off_times_ms); if (!ret) { dev_err(dev, "%s: fail to get led_blink value.\n", __func__); return count; } dev_info(dev, "%s: led_brightness=0x%x, delay_on_times=%dms, delay_off_time=%dms\n", __func__, led_brightness, sm5705_rgb->delay_on_times_ms, sm5705_rgb->delay_off_times_ms); led_br[0] = ((led_brightness & 0xFF0000) >> 16); led_br[1] = ((led_brightness & 0x00FF00) >> 8); led_br[2] = ((led_brightness & 0x0000FF) >> 0); sm5705_rgb_reset(sm5705_rgb); for (i=0; i < 3; ++i) { if (led_br[i]) { led_current = calc_led_current_from_brightness(sm5705_rgb, i, led_br[i]); sm5705_rgb_set_LEDx_slopeduty(sm5705_rgb, i, 0xF, 0xF, 0x0); sm5705_rgb_set_LEDx_slopemode(sm5705_rgb, i, 0, sm5705_rgb->delay_on_times_ms, sm5705_rgb->delay_off_times_ms, 4, 4); sm5705_rgb_set_LEDx_current(sm5705_rgb, i, led_current); sm5705_rgb_set_LEDx_enable(sm5705_rgb, i, sm5705_rgb->delay_off_times_ms?1:0, 1); dev_info(dev, "%s: led[%d] slope mode ON (MAX:0x%x, MID:0x%x, MIN:0x%x, delay:%d)\n", __func__, i, 0xF, 0xF, 0x0, 0); } } return count; } static ssize_t store_sm5705_rgb_pattern(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev); unsigned char led_current; unsigned int mode; int ret; ret = sscanf(buf, "%1d", &mode); if (!ret) { dev_err(dev, "%s: fail to get led_pattern mode.\n", __func__); return count; } led_current = (sm5705_rgb->en_lowpower_mode) ? low_powermode_current : normal_powermode_current; dev_info(dev, "%s: pattern mode=%d led_current=0x%x(lowpower=%d)\n", __func__, mode, led_current, sm5705_rgb->en_lowpower_mode); ret = sm5705_rgb_reset(sm5705_rgb); if (IS_ERR_VALUE(ret)) { goto out; } switch (mode) { case CHARGING: /* LED_R constant mode ON */ led_current = led_current * br_ratio_r / 100; sm5705_rgb_set_LEDx_current(sm5705_rgb, LED_R, led_current); sm5705_rgb_set_LEDx_enable(sm5705_rgb, LED_R, 0, 1); break; case CHARGING_ERR: /* LED_R slope mode ON (500ms to 500ms) */ led_current = led_current * br_ratio_r / 100; sm5705_rgb_set_LEDx_slopeduty(sm5705_rgb, LED_R, 0xF, 0xF, 0x0); sm5705_rgb_set_LEDx_slopemode(sm5705_rgb, LED_R, 0, 500, 500, 4, 4); sm5705_rgb_set_LEDx_current(sm5705_rgb, LED_R, led_current); sm5705_rgb_set_LEDx_enable(sm5705_rgb, LED_R, 1, 1); break; case MISSED_NOTI: /* LED_B slope mode ON (500ms to 5000ms) */ led_current = led_current * br_ratio_b / 100; sm5705_rgb_set_LEDx_slopeduty(sm5705_rgb, LED_B, 0xF, 0xF, 0x0); sm5705_rgb_set_LEDx_slopemode(sm5705_rgb, LED_B, 0, 500, 5000, 4, 4); sm5705_rgb_set_LEDx_current(sm5705_rgb, LED_B, led_current); sm5705_rgb_set_LEDx_enable(sm5705_rgb, LED_B, 1, 1); break; case LOW_BATTERY: /* LED_R slope mode ON (500ms to 5000ms) */ led_current = led_current * br_ratio_r / 100; sm5705_rgb_set_LEDx_slopeduty(sm5705_rgb, LED_R, 0xF, 0xF, 0x0); sm5705_rgb_set_LEDx_slopemode(sm5705_rgb, LED_R, 0, 500, 5000, 4, 4); sm5705_rgb_set_LEDx_current(sm5705_rgb, LED_R, led_current); sm5705_rgb_set_LEDx_enable(sm5705_rgb, LED_R, 1, 1); break; case FULLY_CHARGED: /* LED_G constant mode ON */ led_current = led_current * br_ratio_g / 100; sm5705_rgb_set_LEDx_current(sm5705_rgb, LED_G, led_current); sm5705_rgb_set_LEDx_enable(sm5705_rgb, LED_G, 0, 1); break; case POWERING: /* LED_G & LED_B slope mode ON (1000ms to 1000ms) */ sm5705_rgb_set_LEDx_slopeduty(sm5705_rgb, LED_G, 0x8, 0x4, 0x1); sm5705_rgb_set_LEDx_slopemode(sm5705_rgb, LED_G, 0, 1000, 1000, 12, 12); led_current = led_current * br_ratio_g / 100; sm5705_rgb_set_LEDx_current(sm5705_rgb, LED_G, led_current); sm5705_rgb_set_LEDx_slopeduty(sm5705_rgb, LED_B, 0xF, 0xE, 0xC); sm5705_rgb_set_LEDx_slopemode(sm5705_rgb, LED_B, 0, 1000, 1000, 28, 28); led_current = led_current * br_ratio_b / br_ratio_g; sm5705_rgb_set_LEDx_current(sm5705_rgb, LED_B, led_current); sm5705_rgb_set_LEDx_enable(sm5705_rgb, LED_G, 1, 1); sm5705_rgb_set_LEDx_enable(sm5705_rgb, LED_B, 1, 1); break; case PATTERN_OFF: default: break; } out: 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_sm5705_rgb_pattern); static DEVICE_ATTR(led_blink, 0660, NULL, store_sm5705_rgb_blink); static DEVICE_ATTR(led_brightness, 0660, show_sm5705_rgb_brightness, store_sm5705_rgb_brightness); static DEVICE_ATTR(led_lowpower, 0660, show_sm5705_rgb_lowpower, store_sm5705_rgb_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, }; /** * SM5705 RGB-LED common led-class device control functions */ static int get_rgb_index_from_led_cdev(struct sm5705_rgb *sm5705_rgb, struct led_classdev *led_cdev) { int i; for (i=0; i < LED_MAX; ++i) { if (led_cdev == &sm5705_rgb->led[i]) { return i; } } dev_err(sm5705_rgb->dev, "%s: invalid led_cdev=%p\n", __func__, led_cdev); return -EINVAL; } static void sm5705_rgb_brightness_set(struct led_classdev *led_cdev, unsigned int brightness) { const struct device *parent = led_cdev->dev->parent; struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(parent); struct device *dev = led_cdev->dev; int index, ret; dev_dbg(dev,"%s\n", __func__); index = get_rgb_index_from_led_cdev(sm5705_rgb, led_cdev); if (IS_ERR_VALUE(index)) { return; } ret = sm5705_rgb_set_LEDx_current(sm5705_rgb, index, brightness); if (IS_ERR_VALUE(ret)) { return; } ret = sm5705_rgb_set_LEDx_enable(sm5705_rgb, index, 0, (bool)brightness); if (IS_ERR_VALUE(ret)) { return; } dev_dbg(dev, "%s: led[%d] current=0x%x, constant %s\n", __func__, index, brightness, (brightness) ? "ON" : "OFF"); } static unsigned int sm5705_rgb_brightness_get(struct led_classdev *led_cdev) { const struct device *parent = led_cdev->dev->parent; struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(parent); struct device *dev = led_cdev->dev; int index, cur_value; dev_dbg(dev, "%s\n", __func__); index = get_rgb_index_from_led_cdev(sm5705_rgb, led_cdev); if (IS_ERR_VALUE(index)) { return 0; } cur_value = sm5705_rgb_get_LEDx_current(sm5705_rgb, index); dev_dbg(dev, "%s: led[%d] get current : 0x%x\n", __func__, index, cur_value); return cur_value; } static ssize_t led_delay_on_show(struct device *dev, struct device_attribute *attr, char *buf) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev->parent); return sprintf(buf, "%d\n", sm5705_rgb->delay_on_times_ms); } static ssize_t led_delay_on_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev->parent); unsigned int time; if (kstrtouint(buf, 0, &time)) { dev_err(dev, "can not write led_delay_on (buf:%s)\n", buf); goto out; } sm5705_rgb->delay_on_times_ms = time; out: return count; } static ssize_t led_delay_off_show(struct device *dev, struct device_attribute *attr, char *buf) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev->parent); return sprintf(buf, "%d\n", sm5705_rgb->delay_off_times_ms); } static ssize_t led_delay_off_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev->parent); unsigned int time; if (kstrtouint(buf, 0, &time)) { dev_err(dev, "can not write led_delay_off (buf:%s)\n", buf); goto out; } sm5705_rgb->delay_off_times_ms = time; out: return count; } static ssize_t led_blink_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev->parent); unsigned long blink_set; int index; dev_info(dev, ":%s\n", __func__); if (strict_strtoul(buf, 0, &blink_set)) { goto out; } index = get_rgb_index_from_led_cdev(sm5705_rgb, led_cdev); if (IS_ERR_VALUE(index)) { goto out; } if (!blink_set) { sm5705_rgb->delay_on_times_ms = LED_OFF; sm5705_rgb_brightness_set(led_cdev, LED_OFF); } sm5705_rgb_set_LEDx_slopeduty(sm5705_rgb, index, 0xF, 0xF, 0x0); sm5705_rgb_set_LEDx_slopemode(sm5705_rgb, index, 0, sm5705_rgb->delay_on_times_ms, sm5705_rgb->delay_off_times_ms, 4, 4); sm5705_rgb_set_LEDx_current(sm5705_rgb, index, normal_powermode_current); sm5705_rgb_set_LEDx_enable(sm5705_rgb, index, 1, 1); dev_info(dev, "%s: delay_on_time:%d, delay_off_time:%d, current:0x%x\n", __func__, sm5705_rgb->delay_on_times_ms, sm5705_rgb->delay_off_times_ms, normal_powermode_current); out: return count; } /* sysfs attribute nodes */ static DEVICE_ATTR(delay_on, 0640, led_delay_on_show, led_delay_on_store); static DEVICE_ATTR(delay_off, 0640, led_delay_off_show, led_delay_off_store); static DEVICE_ATTR(blink, 0640, NULL, led_blink_store); static struct attribute *led_class_attrs[] = { &dev_attr_delay_on.attr, &dev_attr_delay_off.attr, &dev_attr_blink.attr, NULL, }; static struct attribute_group common_led_attr_group = { .attrs = led_class_attrs, }; /** * SM5705 RGB-LED device driver management functions */ #ifdef CONFIG_OF static inline void _decide_octa(char *octa, unsigned char octa_color) { switch(octa_color) { case 0: strcpy(octa, "_bk"); break; case 1: strcpy(octa, "_wt"); break; case 2: strcpy(octa, "_gd"); break; default: break; } } static void make_property_string(char *dst, const char *src, const char *octa) { strcpy(dst, src); strcat(dst, octa); } static int sm5705_rgb_parse_dt(struct device *dev, unsigned char octa_color, char **leds_name) { struct device_node *nproot = dev->parent->of_node; struct device_node *np; char property_str[255] = {0, }; char octa[16] = {0, }; int i, ret, temp; np = of_find_node_by_name(nproot, "rgb"); if (unlikely(np == NULL)) { dev_err(dev, "fail to find rgb node\n"); return -ENOENT; } for (i = 0; i < LED_MAX; i++) { ret = of_property_read_string_index(np, "rgb-name", i, (const char **)&leds_name[i]); dev_info(dev, "rgb-name[%d] string: ""%s""\n", i, leds_name[i]); if (IS_ERR_VALUE(ret)) { return -ENOENT; } } gpio_vdd = of_get_named_gpio(np, "rgb,vdd-gpio", 0); if(gpio_vdd < 0){ dev_info(dev, "don't support gpio to lift up power supply!\n"); } /* device type property is not necessary now */ #if 0 ret = of_property_read_u32(np, "device_type", (unsigned int *)&i); if (IS_ERR_VALUE(ret)) { return -ENOENT; } #endif _decide_octa(octa, octa_color); /* parsing dt:rgb-normal_powermode_current */ make_property_string(property_str, "normal_powermode_current", octa); ret = of_property_read_u32(np, property_str, &temp); if (IS_ERR_VALUE(ret)) { dev_err(dev, "can't parsing [%s] in RGB dt\n", property_str); } else { normal_powermode_current = (unsigned char)temp; } /* parsing dt:rgb-low_powermode_current */ make_property_string(property_str, "low_powermode_current", octa); ret = of_property_read_u32(np, property_str, &temp); if (IS_ERR_VALUE(ret)) { dev_err(dev, "can't parsing [%s] in RGB dt\n", property_str); } else { low_powermode_current = (unsigned char)temp; } /* parsing dt:rgb-br_ratio_r */ make_property_string(property_str, "br_ratio_r", octa); ret = of_property_read_u32(np, property_str, &temp); if (IS_ERR_VALUE(ret)) { dev_err(dev, "can't parsing [%s] in RGB dt\n", property_str); } else { br_ratio_r = (unsigned char)temp; } /* parsing dt:rgb-br_ratio_g */ make_property_string(property_str, "br_ratio_g", octa); ret = of_property_read_u32(np, property_str, &temp); if (IS_ERR_VALUE(ret)) { dev_err(dev, "can't parsing [%s] in RGB dt\n", property_str); } else { br_ratio_g = (unsigned char)temp; } /* parsing dt:rgb-br_ratio_b */ make_property_string(property_str, "br_ratio_b", octa); ret = of_property_read_u32(np, property_str, &temp); if (IS_ERR_VALUE(ret)) { dev_err(dev, "can't parsing [%s] in RGB dt\n", property_str); } else { br_ratio_b = (unsigned char)temp; } dev_info(dev, "SM5705 RGB-LED device configuration info\n"); dev_info(dev, "- normal_powermode_current = %d\n", normal_powermode_current); return 0; } #endif static inline int _register_led_commonclass_dev(struct device *dev, struct sm5705_rgb *sm5705_rgb, char **leds_name) { int i, ret; for (i=0; i < LED_MAX; ++i) { sm5705_rgb->led[i].name = leds_name[i]; sm5705_rgb->led[i].max_brightness = LED_MAX_CURRENT; sm5705_rgb->led[i].brightness_set = sm5705_rgb_brightness_set; sm5705_rgb->led[i].brightness_get = sm5705_rgb_brightness_get; ret = led_classdev_register(dev, &sm5705_rgb->led[i]); if (IS_ERR_VALUE(ret)) { dev_err(dev, "unable to register led_classdev[%d] (ret=%d)\n", i, ret); goto led_classdev_register_err; } ret = sysfs_create_group(&sm5705_rgb->led[i].dev->kobj, &common_led_attr_group); if (IS_ERR_VALUE(ret)) { dev_err(dev, "can not register sysfs attribute for led[%d]\n", i); led_classdev_unregister(&sm5705_rgb->led[i]); goto led_classdev_register_err; } } return 0; led_classdev_register_err: do { led_classdev_unregister(&sm5705_rgb->led[i]); sysfs_remove_group(&sm5705_rgb->led[i].dev->kobj, &common_led_attr_group); } while (--i); return ret; } static inline void _unregister_led_commonclass_dev(struct sm5705_rgb *sm5705_rgb) { int i; for (i=0; i < LED_MAX; ++i) { led_classdev_unregister(&sm5705_rgb->led[i]); sysfs_remove_group(&sm5705_rgb->led[i].dev->kobj, &common_led_attr_group); } } extern int get_lcd_attached(char *mode); static int sm5705_rgb_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct sm5705_rgb *sm5705_rgb; struct sm5705_dev *sm5705_dev = dev_get_drvdata(dev->parent); unsigned char octa_color = (get_lcd_attached("GET") & 0x0F0000) >> 16; char *leds_name[LED_MAX]; int ret; sm5705_rgb = devm_kzalloc(dev, sizeof(struct sm5705_rgb), GFP_KERNEL); if (unlikely(!sm5705_rgb)) { dev_err(dev, "fail to allocate memory for sm5705_rgb\n"); return -ENOMEM; } dev_info(dev," %s: lcdtype=0x%x, octa_color=0x%x\n", __func__, get_lcd_attached("GET"), octa_color); #ifdef CONFIG_OF ret = sm5705_rgb_parse_dt(dev, octa_color, leds_name); if (IS_ERR_VALUE(ret)) { dev_err(dev, "fail to parse dt for sm5705 rgb-led (ret=%d)\n", ret); goto rgb_dt_parse_err; } #endif sm5705_rgb->led_dev = device_create(sec_class, NULL, 0, sm5705_rgb, "led"); if (unlikely(!sm5705_rgb->led_dev)) { dev_err(dev, "fail to create device for samsung specific led\n"); goto rgb_dt_parse_err; } ret = sysfs_create_group(&sm5705_rgb->led_dev->kobj, &sec_led_attr_group); if (IS_ERR_VALUE(ret)) { dev_err(dev, "fail to create sysfs group for samsung specific led\n"); goto sec_device_create_err; } sm5705_rgb->dev = dev; sm5705_rgb->i2c = sm5705_dev->i2c; platform_set_drvdata(pdev, sm5705_rgb); #if 0 ret = _register_led_commonclass_dev(dev, sm5705_rgb, leds_name); if (IS_ERR_VALUE(ret)) { goto sec_device_create_err; } #endif if (gpio_vdd > 0) { ret = gpio_request(gpio_vdd, "sm5705-rgb_vdd_supply"); if(ret < 0){ dev_err(dev, "unable to request rgb_vdd_supply\n"); } } #if defined(CONFIG_LEDS_USE_ED28) && defined(CONFIG_SEC_FACTORY) if( lcdtype == 0 && jig_status == false) { /* LED_R constant mode ON */ sm5705_rgb_set_LEDx_current(sm5705_rgb, LED_R, normal_powermode_current); sm5705_rgb_set_LEDx_enable(sm5705_rgb, LED_R, 0, 1); } #endif dev_info(dev, "RGB-LED device driver probe done\n"); dev_info(dev, "normal_powermode_current: 0x%x\n", normal_powermode_current); dev_info(dev, "low_powermode_current: 0x%x\n", low_powermode_current); dev_info(dev, "br_ratio_r: %d\n", (int)br_ratio_r); dev_info(dev, "br_ratio_g: %d\n", (int)br_ratio_g); dev_info(dev, "br_ratio_b: %d\n", (int)br_ratio_b); return 0; //sec_sysfs_create_group_err: // _unregister_led_commonclass_dev(sm5705_rgb); sec_device_create_err: device_destroy(sec_class, sm5705_rgb->led_dev->devt); rgb_dt_parse_err: devm_kfree(dev, sm5705_rgb); return ret; } static int sm5705_rgb_remove(struct platform_device *pdev) { struct sm5705_rgb *sm5705_rgb = platform_get_drvdata(pdev); int i; for (i = 0; i < 4; ++i) { led_classdev_unregister(&sm5705_rgb->led[i]); } return 0; } static void sm5705_rgb_shutdown(struct device *dev) { struct sm5705_rgb *sm5705_rgb = dev_get_drvdata(dev); dev_dbg(dev, "%s\n", __func__); if (!sm5705_rgb->i2c) return; sm5705_rgb_reset(sm5705_rgb); sysfs_remove_group(&sm5705_rgb->led_dev->kobj, &sec_led_attr_group); //_unregister_led_commonclass_dev(sm5705_rgb); devm_kfree(dev, sm5705_rgb); } static struct platform_driver sm5705_rgbled_driver = { .driver = { .name = "leds-sm5705-rgb", .owner = THIS_MODULE, .shutdown = sm5705_rgb_shutdown, }, .probe = sm5705_rgb_probe, .remove = sm5705_rgb_remove, }; static int __init sm5705_rgb_init(void) { return platform_driver_register(&sm5705_rgbled_driver); } module_init(sm5705_rgb_init); static void __exit sm5705_rgb_exit(void) { platform_driver_unregister(&sm5705_rgbled_driver); } module_exit(sm5705_rgb_exit); MODULE_ALIAS("platform:sm5705-rgb"); MODULE_DESCRIPTION("SM5705 RGB driver"); MODULE_LICENSE("GPL v2"); MODULE_VERSION("1.0");