| /* |
| * RGB-led driver for Maxim MAX77705 |
| * |
| * Copyright (C) 2013 Maxim Integrated Product |
| * |
| * 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/kernel.h> |
| #include <linux/module.h> |
| #include <linux/i2c.h> |
| #include <linux/gpio.h> |
| #include <linux/slab.h> |
| #include <linux/interrupt.h> |
| #include <linux/init.h> |
| #include <linux/device.h> |
| #include <linux/platform_device.h> |
| #include <linux/leds.h> |
| #include <linux/of.h> |
| #include <linux/mfd/max77705.h> |
| #include <linux/mfd/max77705-private.h> |
| #include <linux/leds-max77705-rgb.h> |
| #include <linux/fs.h> |
| #include <linux/uaccess.h> |
| #include <linux/regmap.h> |
| #include <linux/sec_class.h> |
| |
| #define SEC_LED_SPECIFIC |
| |
| /* Registers */ |
| |
| /* MAX77705_REG_LED0BRT */ |
| #define MAX77705_LED0BRT 0xFF |
| |
| /* MAX77705_REG_LED1BRT */ |
| #define MAX77705_LED1BRT 0xFF |
| |
| /* MAX77705_REG_LED2BRT */ |
| #define MAX77705_LED2BRT 0xFF |
| |
| /* MAX77705_REG_LED3BRT */ |
| #define MAX77705_LED3BRT 0xFF |
| |
| /* MAX77705_REG_LEDBLNK */ |
| #define MAX77705_LEDBLINKD 0xF0 |
| #define MAX77705_LEDBLINKP 0x0F |
| |
| /* MAX77705_REG_LEDRMP */ |
| #define MAX77705_RAMPUP 0xF0 |
| #define MAX77705_RAMPDN 0x0F |
| |
| #define LED_R_MASK 0x00FF0000 |
| #define LED_G_MASK 0x0000FF00 |
| #define LED_B_MASK 0x000000FF |
| #define LED_MAX_CURRENT 0xFF |
| |
| /* MAX77705_STATE*/ |
| #define LED_DISABLE 0 |
| #define LED_ALWAYS_ON 1 |
| #define LED_BLINK 2 |
| |
| #define LEDBLNK_ON(time) ((time < 100) ? 0 : \ |
| (time < 500) ? time/100-1 : \ |
| (time < 3250) ? (time-500)/250+4 : 15) |
| |
| #define LEDBLNK_OFF(time) ((time < 1) ? 0x00 : \ |
| (time < 500) ? 0x01 : \ |
| (time < 5000) ? time/500 : \ |
| (time < 8000) ? (time-5000)/1000+10 : \ |
| (time < 12000) ? (time-8000)/2000+13 : 15) |
| |
| #define OCTA_LEN 4 |
| #define RGB_BUFSIZE 30 |
| |
| static u8 led_dynamic_current = 0x14; |
| static u8 normal_powermode_current = 0x14; |
| static u8 low_powermode_current = 0x05; |
| |
| /* led_device_type */ |
| static unsigned int led_device_type; |
| |
| static unsigned int brightness_ratio_r = 100; |
| static unsigned int brightness_ratio_g = 100; |
| static unsigned int brightness_ratio_b = 100; |
| static unsigned int brightness_ratio_r_low = 20; |
| static unsigned int brightness_ratio_g_low = 20; |
| static unsigned int brightness_ratio_b_low = 20; |
| static u8 led_lowpower_mode; |
| static u8 led_pattern_num; |
| |
| static unsigned int octa_color; |
| |
| enum max77705_led_color { |
| WHITE, |
| RED, |
| GREEN, |
| BLUE, |
| }; |
| enum max77705_led_pattern { |
| PATTERN_OFF, |
| CHARGING, |
| CHARGING_ERR, |
| MISSED_NOTI, |
| LOW_BATTERY, |
| FULLY_CHARGED, |
| POWERING, |
| }; |
| |
| static struct device *led_dev; |
| |
| struct max77705_rgb { |
| struct led_classdev led[4]; |
| struct i2c_client *i2c; |
| unsigned int delay_on_times_ms; |
| unsigned int delay_off_times_ms; |
| }; |
| |
| static int max77705_rgb_number(struct led_classdev *led_cdev, |
| struct max77705_rgb **p) |
| { |
| const struct device *parent = led_cdev->dev->parent; |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(parent); |
| int i; |
| |
| *p = max77705_rgb; |
| |
| for (i = 0; i < 4; i++) { |
| if (led_cdev == &max77705_rgb->led[i]) { |
| pr_debug("leds-max77705-rgb: %s, %d\n", __func__, i); |
| return i; |
| } |
| } |
| |
| pr_err("leds-max77705-rgb: %s, can't find rgb number\n", __func__); |
| |
| return -ENODEV; |
| } |
| |
| static void max77705_rgb_set(struct led_classdev *led_cdev, |
| unsigned int brightness) |
| { |
| const struct device *parent = led_cdev->dev->parent; |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(parent); |
| struct device *dev; |
| int n; |
| int ret; |
| |
| ret = max77705_rgb_number(led_cdev, &max77705_rgb); |
| if (ret < 0) { |
| dev_err(led_cdev->dev, |
| "max77705_rgb_number() returns %d.\n", ret); |
| return; |
| } |
| |
| dev = led_cdev->dev; |
| n = ret; |
| |
| if (brightness == LED_OFF) { |
| /* Flash OFF */ |
| ret = max77705_update_reg(max77705_rgb->i2c, |
| MAX77705_RGBLED_REG_LEDEN, 0, 3 << (2*n)); |
| if (ret < 0) { |
| dev_err(dev, "can't write LEDEN : %d\n", ret); |
| return; |
| } |
| } else { |
| /* Set current */ |
| ret = max77705_write_reg(max77705_rgb->i2c, |
| MAX77705_RGBLED_REG_LED0BRT + n, brightness); |
| if (ret < 0) { |
| dev_err(dev, "can't write LEDxBRT : %d\n", ret); |
| return; |
| } |
| } |
| } |
| |
| static void max77705_rgb_set_state(struct led_classdev *led_cdev, |
| unsigned int brightness, unsigned int led_state) |
| { |
| const struct device *parent = led_cdev->dev->parent; |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(parent); |
| struct device *dev; |
| int n; |
| int ret; |
| |
| ret = max77705_rgb_number(led_cdev, &max77705_rgb); |
| |
| if (ret < 0) { |
| dev_err(led_cdev->dev, |
| "max77705_rgb_number() returns %d.\n", ret); |
| return; |
| } |
| |
| dev = led_cdev->dev; |
| n = ret; |
| |
| if (brightness != 0) { |
| /* apply brightness ratio for optimize each led brightness*/ |
| switch (n) { |
| case RED: |
| if (led_lowpower_mode == 1) |
| brightness = brightness * brightness_ratio_r_low / 100; |
| else |
| brightness = brightness * brightness_ratio_r / 100; |
| break; |
| case GREEN: |
| if (led_lowpower_mode == 1) |
| brightness = brightness * brightness_ratio_g_low / 100; |
| else |
| brightness = brightness * brightness_ratio_g / 100; |
| break; |
| case BLUE: |
| if (led_lowpower_mode == 1) |
| brightness = brightness * brightness_ratio_b_low / 100; |
| else |
| brightness = brightness * brightness_ratio_b / 100; |
| break; |
| } |
| |
| /* |
| * There is possibility that low_powermode_current is 0. |
| * ex) low_powermode_current is 1 & brightness_ratio_r is 90 |
| * brightness = 1 * 90 / 100 = 0.9 |
| * brightness is inteager, so brightness is 0. |
| * In this case, it is need to assign 1 of value. |
| */ |
| if (brightness == 0) |
| brightness = 1; |
| } |
| max77705_rgb_set(led_cdev, brightness); |
| |
| pr_info("leds-max77705-rgb: %s, led_num = %d, brightness = %d\n", __func__, ret, brightness); |
| |
| ret = max77705_update_reg(max77705_rgb->i2c, |
| MAX77705_RGBLED_REG_LEDEN, led_state << (2*n), 0x3 << 2*n); |
| if (ret < 0) { |
| dev_err(dev, "can't write FLASH_EN : %d\n", ret); |
| return; |
| } |
| } |
| |
| static unsigned int max77705_rgb_get(struct led_classdev *led_cdev) |
| { |
| const struct device *parent = led_cdev->dev->parent; |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(parent); |
| struct device *dev; |
| int n; |
| int ret; |
| u8 value; |
| |
| pr_info("leds-max77705-rgb: %s\n", __func__); |
| |
| ret = max77705_rgb_number(led_cdev, &max77705_rgb); |
| if (ret < 0) { |
| dev_err(led_cdev->dev, |
| "max77705_rgb_number() returns %d.\n", ret); |
| return 0; |
| } |
| n = ret; |
| |
| dev = led_cdev->dev; |
| |
| /* Get status */ |
| ret = max77705_read_reg(max77705_rgb->i2c, |
| MAX77705_RGBLED_REG_LEDEN, &value); |
| if (ret < 0) { |
| dev_err(dev, "can't read LEDEN : %d\n", ret); |
| return 0; |
| } |
| if (!(value & (3 << (2*n)))) |
| return LED_OFF; |
| |
| /* Get current */ |
| ret = max77705_read_reg(max77705_rgb->i2c, |
| MAX77705_RGBLED_REG_LED0BRT + n, &value); |
| if (ret < 0) { |
| dev_err(dev, "can't read LED0BRT : %d\n", ret); |
| return 0; |
| } |
| |
| return value; |
| } |
| |
| static int max77705_rgb_ramp(struct device *dev, int ramp_up, int ramp_down) |
| { |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(dev); |
| int value; |
| int ret; |
| |
| pr_info("leds-max77705-rgb: %s\n", __func__); |
| |
| if (ramp_up <= 800) { |
| ramp_up /= 100; |
| } else { |
| ramp_up = (ramp_up - 800) * 2 + 800; |
| ramp_up /= 100; |
| } |
| |
| if (ramp_down <= 800) { |
| ramp_down /= 100; |
| } else { |
| ramp_down = (ramp_down - 800) * 2 + 800; |
| ramp_down /= 100; |
| } |
| |
| value = (ramp_down) | (ramp_up << 4); |
| ret = max77705_write_reg(max77705_rgb->i2c, |
| MAX77705_RGBLED_REG_LEDRMP, value); |
| if (ret < 0) { |
| dev_err(dev, "can't write REG_LEDRMP : %d\n", ret); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static int max77705_rgb_blink(struct device *dev, |
| unsigned int delay_on, unsigned int delay_off) |
| { |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(dev); |
| int value; |
| int ret = 0; |
| |
| pr_info("leds-max77705-rgb: %s\n", __func__); |
| |
| value = (LEDBLNK_ON(delay_on) << 4) | LEDBLNK_OFF(delay_off); |
| ret = max77705_write_reg(max77705_rgb->i2c, |
| MAX77705_RGBLED_REG_LEDBLNK, value); |
| if (ret < 0) { |
| dev_err(dev, "can't write REG_LEDBLNK : %d\n", ret); |
| return -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_OF |
| static struct max77705_rgb_platform_data |
| *max77705_rgb_parse_dt(struct device *dev) |
| { |
| struct max77705_rgb_platform_data *pdata; |
| struct device_node *nproot = dev->parent->of_node; |
| struct device_node *np; |
| int ret; |
| int i; |
| int temp; |
| char octa[4] = {0, }; |
| char br_ratio_r[23] = "br_ratio_r"; |
| char br_ratio_g[23] = "br_ratio_g"; |
| char br_ratio_b[23] = "br_ratio_b"; |
| char br_ratio_r_low[23] = "br_ratio_r_low"; |
| char br_ratio_g_low[23] = "br_ratio_g_low"; |
| char br_ratio_b_low[23] = "br_ratio_b_low"; |
| char normal_po_cur[29] = "normal_powermode_current"; |
| char low_po_cur[26] = "low_powermode_current"; |
| |
| pr_info("leds-max77705-rgb: %s\n", __func__); |
| |
| pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); |
| if (unlikely(pdata == NULL)) |
| return ERR_PTR(-ENOMEM); |
| |
| np = of_find_node_by_name(nproot, "rgb"); |
| if (unlikely(np == NULL)) { |
| dev_err(dev, "rgb node not found\n"); |
| devm_kfree(dev, pdata); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| for (i = 0; i < 4; i++) { |
| ret = of_property_read_string_index(np, "rgb-name", i, |
| (const char **)&pdata->name[i]); |
| |
| pr_info("leds-max77705-rgb: %s, %s\n", __func__, pdata->name[i]); |
| |
| if (ret < 0) { |
| devm_kfree(dev, pdata); |
| return ERR_PTR(ret); |
| } |
| } |
| |
| /* get led_device_type value in dt */ |
| ret = of_property_read_u32(np, "led_device_type", &temp); |
| if (ret < 0) |
| pr_info("leds-max77705-rgb: %s, can't parsing led_device_type in dt\n", __func__); |
| else |
| led_device_type = (u8)temp; |
| pr_info("leds-max77705-rgb: %s, led_device_type = %x\n", __func__, led_device_type); |
| |
| /* STAR and STAR2 */ |
| if (led_device_type == 0) { |
| switch (octa_color) { |
| case 0: |
| strncpy(octa, "_uu", OCTA_LEN); |
| break; |
| case 1: |
| strncpy(octa, "_bk", OCTA_LEN); |
| break; |
| case 2: |
| strncpy(octa, "_wh", OCTA_LEN); |
| break; |
| case 3: |
| strncpy(octa, "_gd", OCTA_LEN); |
| break; |
| case 4: |
| strncpy(octa, "_sv", OCTA_LEN); |
| break; |
| case 5: |
| strncpy(octa, "_gr", OCTA_LEN); |
| break; |
| case 6: |
| strncpy(octa, "_bl", OCTA_LEN); |
| break; |
| case 7: |
| strncpy(octa, "_pg", OCTA_LEN); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| strncat(normal_po_cur, octa, strlen(octa)); |
| strncat(low_po_cur, octa, strlen(octa)); |
| strncat(br_ratio_r, octa, strlen(octa)); |
| strncat(br_ratio_g, octa, strlen(octa)); |
| strncat(br_ratio_b, octa, strlen(octa)); |
| strncat(br_ratio_r_low, octa, strlen(octa)); |
| strncat(br_ratio_g_low, octa, strlen(octa)); |
| strncat(br_ratio_b_low, octa, strlen(octa)); |
| |
| /* get normal_powermode_current value in dt */ |
| ret = of_property_read_u32(np, normal_po_cur, &temp); |
| if (ret < 0) |
| pr_info("leds-max77705-rgb: %s, can't parsing normal_powermode_current in dt\n", __func__); |
| else |
| normal_powermode_current = (u8)temp; |
| pr_info("leds-max77705-rgb: %s, normal_powermode_current = %x\n", __func__, normal_powermode_current); |
| |
| /* get low_powermode_current value in dt */ |
| ret = of_property_read_u32(np, low_po_cur, &temp); |
| if (ret < 0) |
| pr_info("leds-max77705-rgb: %s, can't parsing low_powermode_current in dt\n", __func__); |
| else |
| low_powermode_current = (u8)temp; |
| pr_info("leds-max77705-rgb: %s, low_powermode_current = %x\n", __func__, low_powermode_current); |
| |
| /* get led red brightness ratio */ |
| ret = of_property_read_u32(np, br_ratio_r, &temp); |
| if (ret < 0) |
| pr_info("leds-max77705-rgb: %s, can't parsing brightness_ratio_r in dt\n", __func__); |
| else |
| brightness_ratio_r = (int)temp; |
| pr_info("leds-max77705-rgb: %s, brightness_ratio_r = %x\n", __func__, brightness_ratio_r); |
| |
| /* get led green brightness ratio */ |
| ret = of_property_read_u32(np, br_ratio_g, &temp); |
| if (ret < 0) |
| pr_info("leds-max77705-rgb: %s, can't parsing brightness_ratio_g in dt\n", __func__); |
| else |
| brightness_ratio_g = (int)temp; |
| pr_info("leds-max77705-rgb: %s, brightness_ratio_g = %x\n", __func__, brightness_ratio_g); |
| |
| /* get led blue brightness ratio */ |
| ret = of_property_read_u32(np, br_ratio_b, &temp); |
| if (ret < 0) |
| pr_info("leds-max77705-rgb: %s, can't parsing brightness_ratio_b in dt\n", __func__); |
| else |
| brightness_ratio_b = (int)temp; |
| pr_info("leds-max77705-rgb: %s, brightness_ratio_b = %x\n", __func__, brightness_ratio_b); |
| |
| |
| /* get led red brightness ratio lowpower */ |
| ret = of_property_read_u32(np, br_ratio_r_low, &temp); |
| if (ret < 0) |
| pr_info("leds-max77705-rgb: %s, can't parsing brightness_ratio_r_low in dt\n", __func__); |
| else |
| brightness_ratio_r_low = (int)temp; |
| pr_info("leds-max77705-rgb: %s, brightness_ratio_r_low = %x\n", __func__, brightness_ratio_r_low); |
| |
| /* get led green brightness ratio lowpower*/ |
| ret = of_property_read_u32(np, br_ratio_g_low, &temp); |
| if (ret < 0) |
| pr_info("leds-max77705-rgb: %s, can't parsing brightness_ratio_g_low in dt\n", __func__); |
| else |
| brightness_ratio_g_low = (int)temp; |
| pr_info("leds-max77705-rgb: %s, brightness_ratio_g_low = %x\n", __func__, brightness_ratio_g_low); |
| |
| /* get led blue brightness ratio lowpower */ |
| ret = of_property_read_u32(np, br_ratio_b_low, &temp); |
| if (ret < 0) |
| pr_info("leds-max77705-rgb: %s, can't parsing brightness_ratio_b_low in dt\n", __func__); |
| else |
| brightness_ratio_b_low = (int)temp; |
| pr_info("leds-max77705-rgb: %s, brightness_ratio_b_low = %x\n", __func__, brightness_ratio_b_low); |
| |
| return pdata; |
| } |
| #endif |
| |
| static void max77705_rgb_reset(struct device *dev) |
| { |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(dev); |
| |
| max77705_rgb_set_state(&max77705_rgb->led[RED], LED_OFF, LED_DISABLE); |
| max77705_rgb_set_state(&max77705_rgb->led[GREEN], LED_OFF, LED_DISABLE); |
| max77705_rgb_set_state(&max77705_rgb->led[BLUE], LED_OFF, LED_DISABLE); |
| max77705_rgb_ramp(dev, 0, 0); |
| max77705_rgb_blink(dev, 0, 0); |
| } |
| |
| static ssize_t show_max77705_rgb_lowpower(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return snprintf(buf, RGB_BUFSIZE, "%d\n", led_lowpower_mode); |
| } |
| |
| static ssize_t store_max77705_rgb_lowpower(struct device *dev, |
| struct device_attribute *devattr, |
| const char *buf, size_t count) |
| { |
| int ret; |
| u8 led_lowpower; |
| |
| ret = kstrtou8(buf, 0, &led_lowpower); |
| if (ret != 0) { |
| dev_err(dev, "fail to get led_lowpower.\n"); |
| return count; |
| } |
| |
| led_lowpower_mode = led_lowpower; |
| |
| pr_info("leds-max77705-rgb: led_lowpower mode set to %i\n", led_lowpower); |
| |
| return count; |
| } |
| |
| static ssize_t show_max77705_rgb_brightness(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return snprintf(buf, RGB_BUFSIZE, "%d\n", led_dynamic_current); |
| } |
| |
| static ssize_t store_max77705_rgb_brightness(struct device *dev, |
| struct device_attribute *devattr, |
| const char *buf, size_t count) |
| { |
| int ret; |
| u8 brightness; |
| |
| pr_info("leds-max77705-rgb: %s\n", __func__); |
| |
| ret = kstrtou8(buf, 0, &brightness); |
| if (ret != 0) { |
| dev_err(dev, "fail to get led_brightness.\n"); |
| return count; |
| } |
| |
| led_lowpower_mode = 0; |
| |
| led_dynamic_current = brightness; |
| |
| dev_dbg(dev, "led brightness set to %i\n", brightness); |
| |
| return count; |
| } |
| |
| static ssize_t show_max77705_rgb_pattern(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return snprintf(buf, RGB_BUFSIZE, "%d\n", led_pattern_num); |
| } |
| |
| static ssize_t store_max77705_rgb_pattern(struct device *dev, |
| struct device_attribute *devattr, |
| const char *buf, size_t count) |
| { |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(dev); |
| unsigned int mode = 0; |
| int ret; |
| |
| ret = sscanf(buf, "%1d", &mode); |
| if (ret == 0) { |
| dev_err(dev, "fail to get led_pattern mode.\n"); |
| return count; |
| } |
| pr_info("leds-max77705-rgb: %s pattern=%d lowpower=%i\n", __func__, mode, led_lowpower_mode); |
| led_pattern_num = mode; |
| |
| /* Set all LEDs Off */ |
| max77705_rgb_reset(dev); |
| if (mode == PATTERN_OFF) |
| return count; |
| |
| /* Set to low power consumption mode */ |
| if (led_lowpower_mode == 1) |
| led_dynamic_current = low_powermode_current; |
| else |
| led_dynamic_current = normal_powermode_current; |
| |
| switch (mode) { |
| |
| case CHARGING: |
| max77705_rgb_set_state(&max77705_rgb->led[RED], led_dynamic_current, LED_ALWAYS_ON); |
| break; |
| case CHARGING_ERR: |
| max77705_rgb_blink(dev, 500, 500); |
| max77705_rgb_set_state(&max77705_rgb->led[RED], led_dynamic_current, LED_BLINK); |
| break; |
| case MISSED_NOTI: |
| max77705_rgb_blink(dev, 500, 5000); |
| max77705_rgb_set_state(&max77705_rgb->led[BLUE], led_dynamic_current, LED_BLINK); |
| break; |
| case LOW_BATTERY: |
| max77705_rgb_blink(dev, 500, 5000); |
| max77705_rgb_set_state(&max77705_rgb->led[RED], led_dynamic_current, LED_BLINK); |
| break; |
| case FULLY_CHARGED: |
| max77705_rgb_set_state(&max77705_rgb->led[GREEN], led_dynamic_current, LED_ALWAYS_ON); |
| break; |
| case POWERING: |
| max77705_rgb_ramp(dev, 800, 800); |
| max77705_rgb_blink(dev, 200, 200); |
| max77705_rgb_set_state(&max77705_rgb->led[BLUE], led_dynamic_current, LED_ALWAYS_ON); |
| max77705_rgb_set_state(&max77705_rgb->led[GREEN], led_dynamic_current, LED_BLINK); |
| break; |
| default: |
| break; |
| } |
| |
| return count; |
| } |
| |
| static ssize_t show_max77705_rgb_blink(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(dev); |
| unsigned int led_r_en = 0, led_g_en = 0, led_b_en = 0; |
| int ret = 0; |
| |
| led_r_en = max77705_rgb_get(&max77705_rgb->led[RED]); |
| led_g_en = max77705_rgb_get(&max77705_rgb->led[GREEN]); |
| led_b_en = max77705_rgb_get(&max77705_rgb->led[BLUE]); |
| |
| ret = !!(led_r_en | led_g_en | led_b_en); |
| |
| return snprintf(buf, RGB_BUFSIZE, "%d\n", ret); |
| } |
| |
| static ssize_t store_max77705_rgb_blink(struct device *dev, |
| struct device_attribute *devattr, |
| const char *buf, size_t count) |
| { |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(dev); |
| int led_brightness = 0; |
| int delay_on_time = 0; |
| int delay_off_time = 0; |
| u8 led_r_brightness = 0; |
| u8 led_g_brightness = 0; |
| u8 led_b_brightness = 0; |
| unsigned int led_total_br = 0; |
| unsigned int led_max_br = 0; |
| u8 led_en = 0; |
| int led_num = 0; |
| int ret; |
| |
| ret = sscanf(buf, "0x%8x %5d %5d", &led_brightness, |
| &delay_on_time, &delay_off_time); |
| if (ret == 0) { |
| dev_err(dev, "fail to get led_blink value.\n"); |
| return count; |
| } |
| |
| /* Set to low power consumption mode */ |
| if (led_lowpower_mode == 1) |
| led_dynamic_current = low_powermode_current; |
| else |
| led_dynamic_current = normal_powermode_current; |
| /*Reset led*/ |
| max77705_rgb_reset(dev); |
| |
| led_r_brightness = (led_brightness & LED_R_MASK) >> 16; |
| led_g_brightness = (led_brightness & LED_G_MASK) >> 8; |
| led_b_brightness = led_brightness & LED_B_MASK; |
| |
| /* In user case, LED current is restricted to less than tuning value */ |
| if (led_r_brightness != 0) { |
| led_r_brightness = (led_r_brightness * led_dynamic_current) / LED_MAX_CURRENT; |
| if (led_r_brightness == 0) |
| led_r_brightness = 1; |
| } |
| if (led_g_brightness != 0) { |
| led_g_brightness = (led_g_brightness * led_dynamic_current) / LED_MAX_CURRENT; |
| if (led_g_brightness == 0) |
| led_g_brightness = 1; |
| } |
| if (led_b_brightness != 0) { |
| led_b_brightness = (led_b_brightness * led_dynamic_current) / LED_MAX_CURRENT; |
| if (led_b_brightness == 0) |
| led_b_brightness = 1; |
| } |
| |
| led_total_br += led_r_brightness * brightness_ratio_r / 100; |
| led_total_br += led_g_brightness * brightness_ratio_g / 100; |
| led_total_br += led_b_brightness * brightness_ratio_b / 100; |
| |
| if (brightness_ratio_r >= brightness_ratio_g && |
| brightness_ratio_r >= brightness_ratio_b) { |
| led_max_br = normal_powermode_current * brightness_ratio_r / 100; |
| } else if (brightness_ratio_g >= brightness_ratio_r && |
| brightness_ratio_g >= brightness_ratio_b) { |
| led_max_br = normal_powermode_current * brightness_ratio_g / 100; |
| } else if (brightness_ratio_b >= brightness_ratio_r && |
| brightness_ratio_b >= brightness_ratio_g) { |
| led_max_br = normal_powermode_current * brightness_ratio_b / 100; |
| } |
| |
| /* Each color decreases according to the limit at the same rate. */ |
| if (led_total_br > led_max_br) { |
| if (led_r_brightness != 0) { |
| led_r_brightness = led_r_brightness * led_max_br / led_total_br; |
| if (led_r_brightness == 0) |
| led_r_brightness = 1; |
| } |
| if (led_g_brightness != 0) { |
| led_g_brightness = led_g_brightness * led_max_br / led_total_br; |
| if (led_g_brightness == 0) |
| led_g_brightness = 1; |
| } |
| if (led_b_brightness != 0) { |
| led_b_brightness = led_b_brightness * led_max_br / led_total_br; |
| if (led_b_brightness == 0) |
| led_b_brightness = 1; |
| } |
| } |
| |
| if (led_r_brightness) { |
| max77705_rgb_set_state(&max77705_rgb->led[RED], led_r_brightness, LED_DISABLE); |
| led_num = max77705_rgb_number(&max77705_rgb->led[RED], &max77705_rgb); |
| led_en |= LED_BLINK << 2*led_num; |
| } |
| |
| if (led_g_brightness) { |
| max77705_rgb_set_state(&max77705_rgb->led[GREEN], led_g_brightness, LED_DISABLE); |
| led_num = max77705_rgb_number(&max77705_rgb->led[GREEN], &max77705_rgb); |
| led_en |= LED_BLINK << 2*led_num; |
| } |
| |
| if (led_b_brightness) { |
| max77705_rgb_set_state(&max77705_rgb->led[BLUE], led_b_brightness, LED_DISABLE); |
| led_num = max77705_rgb_number(&max77705_rgb->led[BLUE], &max77705_rgb); |
| led_en |= LED_BLINK << 2*led_num; |
| } |
| |
| /*Set LED blink mode*/ |
| max77705_rgb_blink(dev, delay_on_time, delay_off_time); |
| |
| ret = max77705_update_reg(max77705_rgb->i2c, MAX77705_RGBLED_REG_LEDEN, led_en, 0xff); |
| if (ret < 0) |
| dev_err(dev, "can't write FLASH_EN : %d\n", ret); |
| |
| pr_info("leds-max77705-rgb: %s, delay_on_time: %d, delay_off_time: %d, color: 0x%x, lowpower: %i\n", |
| __func__, delay_on_time, delay_off_time, led_brightness, led_lowpower_mode); |
| |
| return count; |
| } |
| |
| static ssize_t store_led_r(struct device *dev, |
| struct device_attribute *devattr, |
| const char *buf, size_t count) |
| { |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(dev); |
| unsigned int brightness; |
| int ret; |
| |
| ret = kstrtouint(buf, 0, &brightness); |
| if (ret != 0) { |
| dev_err(dev, "fail to get brightness.\n"); |
| goto out; |
| } |
| if (brightness != 0) |
| max77705_rgb_set_state(&max77705_rgb->led[RED], brightness, LED_ALWAYS_ON); |
| else |
| max77705_rgb_set_state(&max77705_rgb->led[RED], LED_OFF, LED_DISABLE); |
| |
| out: |
| pr_info("leds-max77705-rgb: %s\n", __func__); |
| return count; |
| } |
| static ssize_t store_led_g(struct device *dev, |
| struct device_attribute *devattr, |
| const char *buf, size_t count) |
| { |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(dev); |
| unsigned int brightness; |
| int ret; |
| |
| ret = kstrtouint(buf, 0, &brightness); |
| if (ret != 0) { |
| dev_err(dev, "fail to get brightness.\n"); |
| goto out; |
| } |
| if (brightness != 0) |
| max77705_rgb_set_state(&max77705_rgb->led[GREEN], brightness, LED_ALWAYS_ON); |
| else |
| max77705_rgb_set_state(&max77705_rgb->led[GREEN], LED_OFF, LED_DISABLE); |
| |
| out: |
| pr_info("leds-max77705-rgb: %s\n", __func__); |
| return count; |
| } |
| static ssize_t store_led_b(struct device *dev, |
| struct device_attribute *devattr, |
| const char *buf, size_t count) |
| { |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(dev); |
| unsigned int brightness; |
| int ret; |
| |
| ret = kstrtouint(buf, 0, &brightness); |
| if (ret != 0) { |
| dev_err(dev, "fail to get brightness.\n"); |
| goto out; |
| } |
| if (brightness != 0) |
| max77705_rgb_set_state(&max77705_rgb->led[BLUE], brightness, LED_ALWAYS_ON); |
| else |
| max77705_rgb_set_state(&max77705_rgb->led[BLUE], LED_OFF, LED_DISABLE); |
| |
| out: |
| pr_info("leds-max77705-rgb: %s\n", __func__); |
| return count; |
| } |
| |
| /* Added for led common class */ |
| static ssize_t led_delay_on_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(dev->parent); |
| |
| return snprintf(buf, RGB_BUFSIZE, "%d\n", max77705_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 max77705_rgb *max77705_rgb = dev_get_drvdata(dev->parent); |
| unsigned int time; |
| |
| if (kstrtouint(buf, 0, &time)) { |
| dev_err(dev, "can not write led_delay_on\n"); |
| return count; |
| } |
| |
| max77705_rgb->delay_on_times_ms = time; |
| |
| return count; |
| } |
| |
| static ssize_t led_delay_off_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct max77705_rgb *max77705_rgb = dev_get_drvdata(dev->parent); |
| |
| return snprintf(buf, RGB_BUFSIZE, "%d\n", max77705_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 max77705_rgb *max77705_rgb = dev_get_drvdata(dev->parent); |
| unsigned int time; |
| |
| if (kstrtouint(buf, 0, &time)) { |
| dev_err(dev, "can not write led_delay_off\n"); |
| return count; |
| } |
| |
| max77705_rgb->delay_off_times_ms = time; |
| |
| 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 max77705_rgb *max77705_rgb = dev_get_drvdata(dev->parent); |
| unsigned int blink_set; |
| int ret; |
| |
| ret = sscanf(buf, "%1d", &blink_set); |
| if (!ret) { |
| dev_err(dev, "can not write led_blink\n"); |
| return count; |
| } |
| |
| if (!blink_set) { |
| max77705_rgb->delay_on_times_ms = LED_OFF; |
| max77705_rgb->delay_off_times_ms = LED_OFF; |
| } |
| |
| max77705_rgb_blink(dev->parent, |
| max77705_rgb->delay_on_times_ms, |
| max77705_rgb->delay_off_times_ms); |
| max77705_rgb_set_state(led_cdev, led_dynamic_current, LED_BLINK); |
| |
| pr_info("leds-max77705-rgb: %s\n", __func__); |
| return count; |
| } |
| |
| /* permission for sysfs node */ |
| 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, 0220, NULL, led_blink_store); |
| |
| #ifdef SEC_LED_SPECIFIC |
| /* below nodes is SAMSUNG specific nodes */ |
| static DEVICE_ATTR(led_r, 0220, NULL, store_led_r); |
| static DEVICE_ATTR(led_g, 0220, NULL, store_led_g); |
| static DEVICE_ATTR(led_b, 0220, NULL, store_led_b); |
| /* led_pattern node permission is 222 */ |
| /* To access sysfs node from other groups */ |
| static DEVICE_ATTR(led_pattern, 0660, show_max77705_rgb_pattern, store_max77705_rgb_pattern); |
| static DEVICE_ATTR(led_blink, 0660, show_max77705_rgb_blink, store_max77705_rgb_blink); |
| static DEVICE_ATTR(led_brightness, 0660, show_max77705_rgb_brightness, store_max77705_rgb_brightness); |
| static DEVICE_ATTR(led_lowpower, 0660, show_max77705_rgb_lowpower, store_max77705_rgb_lowpower); |
| #endif |
| |
| 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, |
| }; |
| |
| #ifdef SEC_LED_SPECIFIC |
| 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, |
| }; |
| #endif |
| static int max77705_rgb_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct max77705_rgb_platform_data *pdata; |
| struct max77705_rgb *max77705_rgb; |
| struct max77705_dev *max77705_dev = dev_get_drvdata(dev->parent); |
| char name[40] = {0,}, *p; |
| int i, ret; |
| |
| pr_info("leds-max77705-rgb: %s\n", __func__); |
| |
| octa_color = get_lcd_info("window_color"); |
| #ifdef CONFIG_OF |
| pdata = max77705_rgb_parse_dt(dev); |
| if (unlikely(IS_ERR(pdata))) |
| return PTR_ERR(pdata); |
| |
| led_dynamic_current = normal_powermode_current; |
| #else |
| pdata = dev_get_platdata(dev); |
| #endif |
| |
| pr_info("leds-max77705-rgb: %s : octa_color=%x led_device_type=%x\n", |
| __func__, octa_color, led_device_type); |
| max77705_rgb = devm_kzalloc(dev, sizeof(struct max77705_rgb), GFP_KERNEL); |
| if (unlikely(!max77705_rgb)) |
| return -ENOMEM; |
| |
| max77705_rgb->i2c = max77705_dev->i2c; |
| platform_set_drvdata(pdev, max77705_rgb); |
| |
| for (i = 0; i < 4; i++) { |
| ret = snprintf(name, RGB_BUFSIZE, "%s", pdata->name[i])+1; |
| if (ret < 1) |
| goto alloc_err_flash; |
| |
| p = devm_kzalloc(dev, ret, GFP_KERNEL); |
| if (unlikely(!p)) |
| goto alloc_err_flash; |
| |
| strncpy(p, name, strlen(name)); |
| max77705_rgb->led[i].name = p; |
| max77705_rgb->led[i].brightness_set = max77705_rgb_set; |
| max77705_rgb->led[i].brightness_get = max77705_rgb_get; |
| max77705_rgb->led[i].max_brightness = LED_MAX_CURRENT; |
| |
| ret = led_classdev_register(dev, &max77705_rgb->led[i]); |
| if (ret < 0) { |
| dev_err(dev, "unable to register RGB : %d\n", ret); |
| goto alloc_err_flash_plus; |
| } |
| ret = sysfs_create_group(&max77705_rgb->led[i].dev->kobj, |
| &common_led_attr_group); |
| if (ret) { |
| dev_err(dev, "can not register sysfs attribute\n"); |
| goto register_err_flash; |
| } |
| } |
| |
| led_dev = sec_device_create(max77705_rgb, "led"); |
| if (IS_ERR(led_dev)) { |
| dev_err(dev, "Failed to create device for samsung specific led\n"); |
| goto create_err_flash; |
| } |
| |
| ret = sysfs_create_group(&led_dev->kobj, &sec_led_attr_group); |
| if (ret < 0) { |
| dev_err(dev, "Failed to create sysfs group for samsung specific led\n"); |
| goto device_create_err; |
| } |
| |
| pr_info("leds-max77705-rgb: %s done\n", __func__); |
| |
| return 0; |
| |
| device_create_err: |
| sec_device_destroy(led_dev->devt); |
| create_err_flash: |
| sysfs_remove_group(&led_dev->kobj, &common_led_attr_group); |
| register_err_flash: |
| led_classdev_unregister(&max77705_rgb->led[i]); |
| alloc_err_flash_plus: |
| alloc_err_flash: |
| while (i--) |
| led_classdev_unregister(&max77705_rgb->led[i]); |
| |
| devm_kfree(dev, max77705_rgb); |
| return -ENOMEM; |
| } |
| |
| static int max77705_rgb_remove(struct platform_device *pdev) |
| { |
| struct max77705_rgb *max77705_rgb = platform_get_drvdata(pdev); |
| int i; |
| |
| sysfs_remove_group(&led_dev->kobj, &sec_led_attr_group); |
| sec_device_destroy(led_dev->devt); |
| for (i = 0; i < 4; i++) { |
| sysfs_remove_group(&max77705_rgb->led[i].dev->kobj, |
| &common_led_attr_group); |
| led_classdev_unregister(&max77705_rgb->led[i]); |
| } |
| |
| return 0; |
| } |
| |
| static void max77705_rgb_shutdown(struct platform_device *pdev) |
| { |
| struct max77705_rgb *max77705_rgb = platform_get_drvdata(pdev); |
| int i; |
| |
| if (!max77705_rgb->i2c) |
| return; |
| |
| max77705_rgb_reset(&pdev->dev); |
| |
| sysfs_remove_group(&led_dev->kobj, &sec_led_attr_group); |
| |
| for (i = 0; i < 4; i++) { |
| sysfs_remove_group(&max77705_rgb->led[i].dev->kobj, |
| &common_led_attr_group); |
| led_classdev_unregister(&max77705_rgb->led[i]); |
| } |
| devm_kfree(&pdev->dev, max77705_rgb); |
| } |
| |
| static struct platform_driver max77705_fled_driver = { |
| .driver = { |
| .name = "leds-max77705-rgb", |
| .owner = THIS_MODULE, |
| }, |
| .probe = max77705_rgb_probe, |
| .remove = max77705_rgb_remove, |
| .shutdown = max77705_rgb_shutdown, |
| }; |
| |
| static int __init max77705_rgb_init(void) |
| { |
| pr_info("leds-max77705-rgb: %s\n", __func__); |
| return platform_driver_register(&max77705_fled_driver); |
| } |
| module_init(max77705_rgb_init); |
| |
| static void __exit max77705_rgb_exit(void) |
| { |
| platform_driver_unregister(&max77705_fled_driver); |
| } |
| module_exit(max77705_rgb_exit); |
| |
| MODULE_ALIAS("platform:max77705-rgb"); |
| MODULE_AUTHOR("Jeongwoong Lee<jell.lee@samsung.com>"); |
| MODULE_DESCRIPTION("MAX77705 RGB driver"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_VERSION("1.0"); |