/* * Copyright (c) 2017, The Linux Foundation. All rights reserved. * * Version: v1.0.0 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #include #include #include #include #include #include #include #include #include "leds-aw2016.h" /* register address */ #define AW2016_REG_RESET 0x00 #define AW2016_REG_GCR1 0x01 #define AW2016_REG_STATUS 0x02 #define AW2016_REG_PATST 0x03 #define AW2016_REG_GCR2 0x04 #define AW2016_REG_LEDEN 0x30 #define AW2016_REG_LCFG1 0x31 #define AW2016_REG_LCFG2 0x32 #define AW2016_REG_LCFG3 0x33 #define AW2016_REG_PWM1 0x34 #define AW2016_REG_PWM2 0x35 #define AW2016_REG_PWM3 0x36 #define AW2016_REG_LED1T0 0x37 #define AW2016_REG_LED1T1 0x38 #define AW2016_REG_LED1T2 0x39 #define AW2016_REG_LED2T0 0x3A #define AW2016_REG_LED2T1 0x3B #define AW2016_REG_LED2T2 0x3C #define AW2016_REG_LED3T0 0x3D #define AW2016_REG_LED3T1 0x3E #define AW2016_REG_LED3T2 0x3F /* register bits */ #define AW2016_CHIPID 0x09 #define AW2016_CHIP_RESET_MASK 0x55 #define AW2016_CHIP_DISABLE_MASK 0x00 #define AW2016_CHIP_ENABLE_MASK 0x01 #define AW2016_CHARGE_DISABLE_MASK 0x02 #define AW2016_LED_BREATH_MODE_MASK 0x10 #define AW2016_LED_MANUAL_MODE_MASK 0x00 #define AW2016_LED_BREATHE_PWM_MASK 0xFF #define AW2016_LED_MANUAL_PWM_MASK 0xFF #define AW2016_LED_FADEIN_MODE_MASK 0x20 #define AW2016_LED_FADEOUT_MODE_MASK 0x40 #define MAX_RISE_TIME_MS 15 #define MAX_HOLD_TIME_MS 15 #define MAX_FALL_TIME_MS 15 #define MAX_OFF_TIME_MS 15 /* aw2016 register read/write access*/ #define REG_NONE_ACCESS 0 #define REG_RD_ACCESS 1 << 0 #define REG_WR_ACCESS 1 << 1 #define AW2016_REG_MAX 0x7F #define AW2016_NAME "aw2016_led" const unsigned char aw2016_reg_access[AW2016_REG_MAX] = { [AW2016_REG_RESET] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_GCR1] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_STATUS] = REG_RD_ACCESS, [AW2016_REG_PATST] = REG_RD_ACCESS, [AW2016_REG_GCR2] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LEDEN] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LCFG1] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LCFG2] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LCFG3] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_PWM1] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_PWM2] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_PWM3] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LED1T0] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LED1T1] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LED1T2] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LED2T0] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LED2T1] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LED2T2] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LED3T0] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LED3T1] = REG_RD_ACCESS|REG_WR_ACCESS, [AW2016_REG_LED3T2] = REG_RD_ACCESS|REG_WR_ACCESS, }; struct aw2016_led { struct i2c_client *client; struct led_classdev cdev; struct aw2016_platform_data *pdata; struct work_struct brightness_work; struct work_struct blink_work; struct mutex lock; int num_leds; int id; int blinking; }; int twi_id = -1; char * leds_str[]={"led_b","led_g","led_r"}; static int aw2016_write(struct aw2016_led *led, u8 reg, u8 val) { int ret = -EINVAL, retry_times = 0; do { ret = i2c_smbus_write_byte_data(led->client, reg, val); retry_times ++; if(retry_times == 5) break; }while (ret < 0); return ret; } static int aw2016_read(struct aw2016_led *led, u8 reg, u8 *val) { int ret = -EINVAL, retry_times = 0; do{ ret = i2c_smbus_read_byte_data(led->client, reg); retry_times ++; if(retry_times == 5) break; }while (ret < 0); if (ret < 0) return ret; *val = ret; return 0; } static void aw2016_brightness_work(struct work_struct *work) { struct aw2016_led *led = container_of(work, struct aw2016_led, brightness_work); u8 val; mutex_lock(&led->pdata->led->lock); /* enable aw2016 if disabled */ aw2016_read(led, AW2016_REG_GCR1, &val); if (!(val&AW2016_CHIP_ENABLE_MASK)) { aw2016_write(led, AW2016_REG_GCR1, AW2016_CHARGE_DISABLE_MASK | AW2016_CHIP_ENABLE_MASK); msleep(2); } if (led->cdev.brightness > 0) { if (led->cdev.brightness > led->cdev.max_brightness) led->cdev.brightness = led->cdev.max_brightness; aw2016_write(led, AW2016_REG_GCR2, led->pdata->imax); aw2016_write(led, AW2016_REG_LCFG1 + led->id, (AW2016_LED_MANUAL_MODE_MASK | led->pdata->led_current)); aw2016_write(led, AW2016_REG_PWM1 + led->id, led->cdev.brightness); aw2016_read(led, AW2016_REG_LEDEN, &val); aw2016_write(led, AW2016_REG_LEDEN, val | (1 << led->id)); } else { aw2016_read(led, AW2016_REG_LEDEN, &val); aw2016_write(led, AW2016_REG_LEDEN, val & (~(1 << led->id))); } /* * If value in AW2016_REG_LEDEN is 0, it means the RGB leds are * all off. So we need to power it off. */ aw2016_read(led, AW2016_REG_LEDEN, &val); if (val == 0) { aw2016_write(led, AW2016_REG_GCR1, AW2016_CHARGE_DISABLE_MASK | AW2016_CHIP_DISABLE_MASK); mutex_unlock(&led->pdata->led->lock); return; } mutex_unlock(&led->pdata->led->lock); } static void aw2016_blink_work(struct work_struct *work) { struct aw2016_led *led = container_of(work, struct aw2016_led, blink_work); u8 val; mutex_lock(&led->pdata->led->lock); /* enable aw2016 if disabled */ aw2016_read(led, AW2016_REG_GCR1, &val); if (!(val&AW2016_CHIP_ENABLE_MASK)) { aw2016_write(led, AW2016_REG_GCR1, AW2016_CHARGE_DISABLE_MASK | AW2016_CHIP_ENABLE_MASK); msleep(2); } led->cdev.brightness = led->blinking ? led->cdev.max_brightness : 0; if (led->blinking > 0) { aw2016_write(led, AW2016_REG_GCR2, led->pdata->imax); aw2016_write(led, AW2016_REG_LCFG1 + led->id, (AW2016_LED_BREATH_MODE_MASK | led->pdata->led_current)); aw2016_write(led, AW2016_REG_PWM1 + led->id, led->cdev.brightness); aw2016_write(led, AW2016_REG_LED1T0 + led->id*3, (led->pdata->rise_time_ms << 4 | led->pdata->hold_time_ms)); aw2016_write(led, AW2016_REG_LED1T1 + led->id*3, (led->pdata->fall_time_ms << 4 | led->pdata->off_time_ms)); aw2016_read(led, AW2016_REG_LEDEN, &val); aw2016_write(led, AW2016_REG_LEDEN, val | (1 << led->id)); } else { aw2016_read(led, AW2016_REG_LEDEN, &val); aw2016_write(led, AW2016_REG_LEDEN, val & (~(1 << led->id))); } /* * If value in AW2016_REG_LEDEN is 0, it means the RGB leds are * all off. So we need to power it off. */ aw2016_read(led, AW2016_REG_LEDEN, &val); if (val == 0) { aw2016_write(led, AW2016_REG_GCR1, AW2016_CHARGE_DISABLE_MASK | AW2016_CHIP_DISABLE_MASK); } mutex_unlock(&led->pdata->led->lock); } static void aw2016_set_brightness(struct led_classdev *cdev, enum led_brightness brightness) { struct aw2016_led *led = container_of(cdev, struct aw2016_led, cdev); led->cdev.brightness = brightness; schedule_work(&led->brightness_work); } static ssize_t aw2016_store_blink(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { unsigned long blinking; struct led_classdev *led_cdev = dev_get_drvdata(dev); struct aw2016_led *led = container_of(led_cdev, struct aw2016_led, cdev); ssize_t ret = -EINVAL; ret = kstrtoul(buf, 10, &blinking); if (ret) return ret; led->blinking = (int)blinking; schedule_work(&led->blink_work); return len; } static ssize_t aw2016_led_time_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct aw2016_led *led = container_of(led_cdev, struct aw2016_led, cdev); return snprintf(buf, PAGE_SIZE, "%d %d %d %d\n", led->pdata->rise_time_ms, led->pdata->hold_time_ms, led->pdata->fall_time_ms, led->pdata->off_time_ms); } static ssize_t aw2016_led_time_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct aw2016_led *led = container_of(led_cdev, struct aw2016_led, cdev); int rc, rise_time_ms, hold_time_ms, fall_time_ms, off_time_ms; rc = sscanf(buf, "%d %d %d %d", &rise_time_ms, &hold_time_ms, &fall_time_ms, &off_time_ms); mutex_lock(&led->pdata->led->lock); led->pdata->rise_time_ms = (rise_time_ms > MAX_RISE_TIME_MS) ? MAX_RISE_TIME_MS : rise_time_ms; led->pdata->hold_time_ms = (hold_time_ms > MAX_HOLD_TIME_MS) ? MAX_HOLD_TIME_MS : hold_time_ms; led->pdata->fall_time_ms = (fall_time_ms > MAX_FALL_TIME_MS) ? MAX_FALL_TIME_MS : fall_time_ms; led->pdata->off_time_ms = (off_time_ms > MAX_OFF_TIME_MS) ? MAX_OFF_TIME_MS : off_time_ms; led->blinking = 1; mutex_unlock(&led->pdata->led->lock); schedule_work(&led->blink_work); return len; } static ssize_t aw2016_reg_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct aw2016_led *led = container_of(led_cdev, struct aw2016_led, cdev); unsigned char i, reg_val; ssize_t len = 0; for(i=0; i 0; cnt --) { aw2016_read(led, AW2016_REG_RESET, &val); dev_notice(&led->client->dev,"aw2016 chip id %0x",val); if (val == AW2016_CHIPID) return 0; } return -EINVAL; } static int aw2016_led_err_handle(struct aw2016_led *led_array, int parsed_leds) { int i; /* * If probe fails, cannot free resource of all LEDs, only free * resources of LEDs which have allocated these resource really. */ for (i = 0; i < parsed_leds; i++) { sysfs_remove_group(&led_array[i].cdev.dev->kobj, &aw2016_led_attr_group); led_classdev_unregister(&led_array[i].cdev); cancel_work_sync(&led_array[i].brightness_work); cancel_work_sync(&led_array[i].blink_work); devm_kfree(&led_array->client->dev, led_array[i].pdata); led_array[i].pdata = NULL; } return i; } static int aw2016_led_parse_child_node(struct aw2016_led *led_array,int led_count) { struct aw2016_led *led; struct device_node *temp; struct aw2016_platform_data *pdata; int rc = 0, parsed_leds; for(parsed_leds=0;parsed_ledsclient = led_array->client; pdata = devm_kzalloc(&led->client->dev, sizeof(struct aw2016_platform_data), GFP_KERNEL); if (!pdata) { dev_err(&led->client->dev, "Failed to allocate memory\n"); goto free_err; } pdata->led = led_array; led->pdata = pdata; rc = of_property_read_string(temp, "led_name", &led->cdev.name); if (rc < 0) { dev_err(&led->client->dev, "Failure reading led name, rc = %d\n", rc); goto free_pdata; } rc = of_property_read_u32(temp, "id", &led->id); if (rc < 0) { dev_err(&led->client->dev, "Failure reading id, rc = %d\n", rc); goto free_pdata; } rc = of_property_read_u32(temp, "imax", &led->pdata->imax); if (rc < 0) { dev_err(&led->client->dev, "Failure reading imax, rc = %d\n", rc); goto free_pdata; } rc = of_property_read_u32(temp, "led-current", &led->pdata->led_current); if (rc < 0) { dev_err(&led->client->dev, "Failure reading led-current, rc = %d\n", rc); goto free_pdata; } rc = of_property_read_u32(temp, "max-brightness", &led->cdev.max_brightness); if (rc < 0) { dev_err(&led->client->dev, "Failure reading max-brightness, rc = %d\n", rc); goto free_pdata; } rc = of_property_read_u32(temp, "rise-time-ms", &led->pdata->rise_time_ms); if (rc < 0) { dev_err(&led->client->dev, "Failure reading rise-time-ms, rc = %d\n", rc); goto free_pdata; } rc = of_property_read_u32(temp, "hold-time-ms", &led->pdata->hold_time_ms); if (rc < 0) { dev_err(&led->client->dev, "Failure reading hold-time-ms, rc = %d\n", rc); goto free_pdata; } rc = of_property_read_u32(temp, "fall-time-ms", &led->pdata->fall_time_ms); if (rc < 0) { dev_err(&led->client->dev, "Failure reading fall-time-ms, rc = %d\n", rc); goto free_pdata; } rc = of_property_read_u32(temp, "off-time-ms", &led->pdata->off_time_ms); if (rc < 0) { dev_err(&led->client->dev, "Failure reading off-time-ms, rc = %d\n", rc); goto free_pdata; } INIT_WORK(&led->brightness_work, aw2016_brightness_work); INIT_WORK(&led->blink_work, aw2016_blink_work); led->cdev.brightness_set = aw2016_set_brightness; rc = led_classdev_register(&led->client->dev, &led->cdev); if (rc) { dev_err(&led->client->dev, "unable to register led %d,rc=%d\n", led->id, rc); goto free_pdata; } rc = sysfs_create_group(&led->cdev.dev->kobj, &aw2016_led_attr_group); if (rc) { dev_err(&led->client->dev, "led sysfs rc: %d\n", rc); goto free_class; } } return 0; free_class: aw2016_led_err_handle(led_array, parsed_leds); led_classdev_unregister(&led_array[parsed_leds].cdev); cancel_work_sync(&led_array[parsed_leds].brightness_work); cancel_work_sync(&led_array[parsed_leds].blink_work); devm_kfree(&led->client->dev, led_array[parsed_leds].pdata); led_array[parsed_leds].pdata = NULL; return rc; free_pdata: aw2016_led_err_handle(led_array, parsed_leds); devm_kfree(&led->client->dev, led_array[parsed_leds].pdata); return rc; free_err: aw2016_led_err_handle(led_array, parsed_leds); return rc; } static int aw2016_led_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct aw2016_led *led_array; int ret = -EINVAL, num_leds = 0; printk("======%s,%d\n",__func__,__LINE__); num_leds = sizeof(leds_str)/sizeof(char *); printk("======%s,%d,num_leds=%d\n",__func__,__LINE__,num_leds); if (!num_leds) return -EINVAL; led_array = devm_kzalloc(&client->dev, (sizeof(struct aw2016_led) * num_leds), GFP_KERNEL); if (!led_array) return -ENOMEM; led_array->client = client; led_array->num_leds = num_leds; mutex_init(&led_array->lock); ret = aw2016_led_parse_child_node(led_array,num_leds); if (ret) { dev_err(&client->dev, "parsed node error\n"); goto free_led_arry; } i2c_set_clientdata(client, led_array); ret = aw2016_check_chipid(led_array); if (ret) { dev_err(&client->dev, "Check chip id error\n"); goto fail_parsed_node; } return 0; fail_parsed_node: aw2016_led_err_handle(led_array, num_leds); free_led_arry: mutex_destroy(&led_array->lock); devm_kfree(&client->dev, led_array); led_array = NULL; return ret; } static int aw2016_led_remove(struct i2c_client *client) { struct aw2016_led *led_array = i2c_get_clientdata(client); int i, parsed_leds = led_array->num_leds; for (i = 0; i < parsed_leds; i++) { sysfs_remove_group(&led_array[i].cdev.dev->kobj, &aw2016_led_attr_group); led_classdev_unregister(&led_array[i].cdev); cancel_work_sync(&led_array[i].brightness_work); cancel_work_sync(&led_array[i].blink_work); devm_kfree(&client->dev, led_array[i].pdata); led_array[i].pdata = NULL; } mutex_destroy(&led_array->lock); devm_kfree(&client->dev, led_array); led_array = NULL; return 0; } static void aw2016_led_shutdown(struct i2c_client *client) { struct aw2016_led *led = i2c_get_clientdata(client); aw2016_write(led, AW2016_REG_RESET, AW2016_CHIP_RESET_MASK); } static int aw2016_led_detect(struct i2c_client *client, struct i2c_board_info *info) { struct i2c_adapter *adapter = client->adapter; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return -ENODEV; if ( twi_id == adapter->nr) { strlcpy(info->type, AW2016_NAME, I2C_NAME_SIZE); return 0; } else { return -ENODEV; } } static const struct i2c_device_id aw2016_led_id[] = { {AW2016_NAME, 0}, {}, }; MODULE_DEVICE_TABLE(i2c, aw2016_led_id); static struct of_device_id aw2016_match_table[] = { { .compatible = "awinic,aw2016_led",}, { }, }; /* Addresses to scan */ static const unsigned short normal_i2c[2] = {0x64,I2C_CLIENT_END}; static struct i2c_driver aw2016_led_driver = { .class = I2C_CLASS_HWMON, .probe = aw2016_led_probe, .remove = aw2016_led_remove, .shutdown = aw2016_led_shutdown, .driver = { .name = AW2016_NAME, .owner = THIS_MODULE, .of_match_table = of_match_ptr(aw2016_match_table), }, .id_table = aw2016_led_id, .detect = aw2016_led_detect, .address_list = normal_i2c, }; static int __init aw2016_led_init(void) { struct device_node *node; const char *name; int ret; node = of_find_node_by_name(NULL, "leds"); if (!node) { pr_err("ERROR! get leds_para failed, func:%s, line:%d\n", __func__, __LINE__); return -EINVAL; } if (!of_device_is_available(node)) { pr_err("%s: leds is not used\n", __func__); return -EINVAL; } ret = of_property_read_u32(node, "leds_twi_id", &twi_id); if (ret) { pr_err("get twi_id is fail, %d\n", ret); return -EINVAL; } ret = of_property_read_string(node, "leds_name", &name); if (ret){ pr_err("get leds_name is fail, %d\n", ret); return -EINVAL; } if(strcmp(name,AW2016_NAME)){ return -EINVAL; } return i2c_add_driver(&aw2016_led_driver); } module_init(aw2016_led_init); static void __exit aw2016_led_exit(void) { i2c_del_driver(&aw2016_led_driver); } module_exit(aw2016_led_exit); MODULE_AUTHOR(""); MODULE_DESCRIPTION("AWINIC AW2016 LED driver"); MODULE_LICENSE("GPL v2");