Loading Documentation/devicetree/bindings/leds/leds-aw2013.txt 0 → 100644 +75 −0 Original line number Diff line number Diff line Binding for RGB LEDs connected to AW2013. AWINIC AW2013 RGB LED driver is used to provide red/green/blue led blink or glowing to notify user for different system events, such as missed call, new sms, low battery. AW2013 RGB LED is connected through I2C. Required properties: - compatible : should be compatible = "awinic,aw2013" - reg : i2c slave address of the device - vdd-supply : Power supply needed to power up the device - vcc-supply : Power source required to power up i2c bus LED required sub-node properties: - aw2013,name : name of the LED - aw2013,id : id of the LED - aw2013,max-brightness: max brightness set of the LED - aw2013,max-current : max current set of the LED - aw2013,rise-time-ms : the rise time when led in breathe mode - aw2013,hold-time-ms : the hold time when led in breathe mode - aw2013,fall-time-ms : the fall time when led in breathe mode - aw2013,off-time-ms : the off time when led in breathe mode The definition of each time described as shown in figure: /-----------\ / | \ /| | |\ / | | | \----------- |hold_time_ms | | | | | rise_time_ms fall_time_ms | off_time_ms Example: aw2013@45 { compatible = "awinic,aw2013"; reg = <0x45>; vdd-supply = <&pm8909_l17>; vcc-supply = <&pm8909_l6>; aw2013,red { aw2013,name = "red"; aw2013,id = <0>; aw2013,max-brightness = <255>; aw2013,max-current = <1>; aw2013,rise-time-ms = <2>; aw2013,hold-time-ms = <1>; aw2013,fall-time-ms = <2>; aw2013,off-time-ms = <1>; }; aw2013,green { aw2013,name = "green"; aw2013,id = <1>; aw2013,max-brightness = <255>; aw2013,max-current = <1>; aw2013,rise-time-ms = <2>; aw2013,hold-time-ms = <1>; aw2013,fall-time-ms = <2>; aw2013,off-time-ms = <1>; }; aw2013,blue { aw2013,name = "blue"; aw2013,id = <2>; aw2013,max-brightness = <255>; aw2013,max-current = <1>; aw2013,rise-time-ms = <2>; aw2013,hold-time-ms = <1>; aw2013,fall-time-ms = <2>; aw2013,off-time-ms = <1>; }; }; Documentation/devicetree/bindings/vendor-prefixes.txt +1 −0 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ apm Applied Micro Circuits Corporation (APM) arm ARM Ltd. atmel Atmel Corporation avago Avago Technologies awinic AWINIC Technology Co.Ltd bosch Bosch Sensortec GmbH brcm Broadcom Corporation capella Capella Microsystems, Inc. Loading drivers/leds/Kconfig +7 −0 Original line number Diff line number Diff line Loading @@ -518,6 +518,13 @@ config LEDS_BLINKM This option enables support for the BlinkM RGB LED connected through I2C. Say Y to enable support for the BlinkM LED. config LEDS_AW2013 tristate "LED support for AW2013" depends on LEDS_CLASS && I2C help This option enables support for the AW2013 RGB LED connected through I2C. Say Y to enable support for the AW2013 LED. comment "LED Triggers" source "drivers/leds/trigger/Kconfig" Loading drivers/leds/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o obj-$(CONFIG_LEDS_MSM_GPIO_FLASH) += leds-msm-gpio-flash.o obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o Loading drivers/leds/leds-aw2013.c 0 → 100644 +751 −0 Original line number Diff line number Diff line /* * Copyright (c) 2015, The Linux Foundation. All rights reserved. * * 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 <linux/delay.h> #include <linux/i2c.h> #include <linux/init.h> #include <linux/leds.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/slab.h> #include <linux/regulator/consumer.h> #include <linux/leds-aw2013.h> #if defined(CONFIG_FB) #include <linux/notifier.h> #include <linux/fb.h> #endif /* register address */ #define AW_REG_RESET 0x00 #define AW_REG_GLOBAL_CONTROL 0x01 #define AW_REG_LED_STATUS 0x02 #define AW_REG_LED_ENABLE 0x30 #define AW_REG_LED_CONFIG_BASE 0x31 #define AW_REG_LED_BRIGHTNESS_BASE 0x34 #define AW_REG_TIMESET0_BASE 0x37 #define AW_REG_TIMESET1_BASE 0x38 /* register bits */ #define AW2013_CHIPID 0x33 #define AW_LED_MOUDLE_ENABLE_MASK 0x01 #define AW_LED_FADE_OFF_MASK 0x40 #define AW_LED_FADE_ON_MASK 0x20 #define AW_LED_BREATHE_MODE_MASK 0x10 #define AW_LED_RESET_MASK 0x55 #define AW_LED_RESET_DELAY 8 #define AW2013_VDD_MIN_UV 2600000 #define AW2013_VDD_MAX_UV 3300000 #define AW2013_VI2C_MIN_UV 1800000 #define AW2013_VI2C_MAX_UV 1800000 #define MAX_RISE_TIME_MS 7 #define MAX_HOLD_TIME_MS 5 #define MAX_FALL_TIME_MS 7 #define MAX_OFF_TIME_MS 5 struct aw2013_led { struct i2c_client *client; struct led_classdev cdev; struct aw2013_platform_data *pdata; struct work_struct brightness_work; struct mutex lock; struct regulator *vdd; struct regulator *vcc; #if defined(CONFIG_FB) struct notifier_block fb_notif; #endif int num_leds; int id; }; static int aw2013_write(struct aw2013_led *led, u8 reg, u8 val) { return i2c_smbus_write_byte_data(led->client, reg, val); } static int aw2013_read(struct aw2013_led *led, u8 reg, u8 *val) { s32 ret; ret = i2c_smbus_read_byte_data(led->client, reg); if (ret < 0) return ret; *val = ret; return 0; } static int aw2013_power_on(struct aw2013_led *led, bool on) { int rc; if (on) { rc = regulator_enable(led->vdd); if (rc) { dev_err(&led->client->dev, "Regulator vdd enable failed rc=%d\n", rc); return rc; } rc = regulator_enable(led->vcc); if (rc) { dev_err(&led->client->dev, "Regulator vcc enable failed rc=%d\n", rc); goto fail_enable_reg; } } else { rc = regulator_disable(led->vdd); if (rc) { dev_err(&led->client->dev, "Regulator vdd disable failed rc=%d\n", rc); return rc; } rc = regulator_disable(led->vcc); if (rc) { dev_err(&led->client->dev, "Regulator vcc disable failed rc=%d\n", rc); goto fail_disable_reg; } } return rc; fail_enable_reg: rc = regulator_disable(led->vdd); if (rc) dev_err(&led->client->dev, "Regulator vdd disable failed rc=%d\n", rc); return rc; fail_disable_reg: rc = regulator_enable(led->vdd); if (rc) dev_err(&led->client->dev, "Regulator vdd enable failed rc=%d\n", rc); return rc; } static int aw2013_power_init(struct aw2013_led *led, bool on) { int rc; if (on) { led->vdd = regulator_get(&led->client->dev, "vdd"); if (IS_ERR(led->vdd)) { rc = PTR_ERR(led->vdd); dev_err(&led->client->dev, "Regulator get failed vdd rc=%d\n", rc); return rc; } if (regulator_count_voltages(led->vdd) > 0) { rc = regulator_set_voltage(led->vdd, AW2013_VDD_MIN_UV, AW2013_VDD_MAX_UV); if (rc) { dev_err(&led->client->dev, "Regulator set_vtg failed vdd rc=%d\n", rc); goto reg_vdd_put; } } led->vcc = regulator_get(&led->client->dev, "vcc"); if (IS_ERR(led->vcc)) { rc = PTR_ERR(led->vcc); dev_err(&led->client->dev, "Regulator get failed vcc rc=%d\n", rc); goto reg_vdd_set_vtg; } if (regulator_count_voltages(led->vcc) > 0) { rc = regulator_set_voltage(led->vcc, AW2013_VI2C_MIN_UV, AW2013_VI2C_MAX_UV); if (rc) { dev_err(&led->client->dev, "Regulator set_vtg failed vcc rc=%d\n", rc); goto reg_vcc_put; } } } else { if (regulator_count_voltages(led->vdd) > 0) regulator_set_voltage(led->vdd, 0, AW2013_VDD_MAX_UV); regulator_put(led->vdd); if (regulator_count_voltages(led->vcc) > 0) regulator_set_voltage(led->vcc, 0, AW2013_VI2C_MAX_UV); regulator_put(led->vcc); } return 0; reg_vcc_put: regulator_put(led->vcc); reg_vdd_set_vtg: if (regulator_count_voltages(led->vdd) > 0) regulator_set_voltage(led->vdd, 0, AW2013_VDD_MAX_UV); reg_vdd_put: regulator_put(led->vdd); return rc; } static void aw2013_brightness_work(struct work_struct *work) { struct aw2013_led *led = container_of(work, struct aw2013_led, brightness_work); u8 val; mutex_lock(&led->pdata->led->lock); if (led->cdev.brightness > 0) { if (led->cdev.brightness > led->cdev.max_brightness) led->cdev.brightness = led->cdev.max_brightness; aw2013_write(led, AW_REG_GLOBAL_CONTROL, AW_LED_MOUDLE_ENABLE_MASK); aw2013_write(led, AW_REG_LED_CONFIG_BASE + led->id, led->pdata->max_current); aw2013_write(led, AW_REG_LED_BRIGHTNESS_BASE + led->id, led->cdev.brightness); aw2013_read(led, AW_REG_LED_ENABLE, &val); aw2013_write(led, AW_REG_LED_ENABLE, val | (1 << led->id)); } else { aw2013_read(led, AW_REG_LED_ENABLE, &val); aw2013_write(led, AW_REG_LED_ENABLE, val & (~(1 << led->id))); } mutex_unlock(&led->pdata->led->lock); } static void aw2013_led_blink_set(struct aw2013_led *led, unsigned long blinking) { u8 val; led->cdev.brightness = blinking ? led->cdev.max_brightness : 0; if (blinking > 0) { aw2013_write(led, AW_REG_GLOBAL_CONTROL, AW_LED_MOUDLE_ENABLE_MASK); aw2013_write(led, AW_REG_LED_CONFIG_BASE + led->id, AW_LED_FADE_OFF_MASK | AW_LED_FADE_ON_MASK | AW_LED_BREATHE_MODE_MASK | led->pdata->max_current); aw2013_write(led, AW_REG_LED_BRIGHTNESS_BASE + led->id, led->cdev.brightness); aw2013_write(led, AW_REG_TIMESET0_BASE + led->id * 3, led->pdata->rise_time_ms << 4 | led->pdata->hold_time_ms); aw2013_write(led, AW_REG_TIMESET1_BASE + led->id * 3, led->pdata->fall_time_ms << 4 | led->pdata->off_time_ms); aw2013_read(led, AW_REG_LED_ENABLE, &val); aw2013_write(led, AW_REG_LED_ENABLE, val | (1 << led->id)); } else { aw2013_read(led, AW_REG_LED_ENABLE, &val); aw2013_write(led, AW_REG_LED_ENABLE, val & (~(1 << led->id))); } } static void aw2013_set_brightness(struct led_classdev *cdev, enum led_brightness brightness) { struct aw2013_led *led = container_of(cdev, struct aw2013_led, cdev); led->cdev.brightness = brightness; schedule_work(&led->brightness_work); } static ssize_t aw2013_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 aw2013_led *led = container_of(led_cdev, struct aw2013_led, cdev); ssize_t ret = -EINVAL; ret = kstrtoul(buf, 10, &blinking); if (ret) return ret; mutex_lock(&led->pdata->led->lock); aw2013_led_blink_set(led, blinking); mutex_unlock(&led->pdata->led->lock); return len; } static ssize_t aw2013_led_time_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct aw2013_led *led = container_of(led_cdev, struct aw2013_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 aw2013_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 aw2013_led *led = container_of(led_cdev, struct aw2013_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; aw2013_led_blink_set(led, 1); mutex_unlock(&led->pdata->led->lock); return len; } static DEVICE_ATTR(blink, 0664, NULL, aw2013_store_blink); static DEVICE_ATTR(led_time, 0664, aw2013_led_time_show, aw2013_led_time_store); static struct attribute *aw2013_led_attributes[] = { &dev_attr_blink.attr, &dev_attr_led_time.attr, NULL, }; static struct attribute_group aw2013_led_attr_group = { .attrs = aw2013_led_attributes }; static int aw_2013_check_chipid(struct aw2013_led *led) { u8 val; aw2013_write(led, AW_REG_RESET, AW_LED_RESET_MASK); usleep(AW_LED_RESET_DELAY); aw2013_read(led, AW_REG_RESET, &val); if (val == AW2013_CHIPID) return 0; else return -EINVAL; } #ifdef CONFIG_PM static int aw2013_led_suspend(struct device *dev) { struct aw2013_led *led = dev_get_drvdata(dev); int ret; u8 val; aw2013_read(led, AW_REG_LED_ENABLE, &val); /* * If value in AW_REG_LED_ENABLE is 0, it means the RGB leds are * all off. So we need to power it off. * If value in AW_REG_LED_ENABLE is not 0, that means LEDs are * already turned on by upper layer, we keep them alive during * suspend so as to support screen-off notification LED. */ if (val == 0) { ret = aw2013_power_on(led, false); if (ret) { dev_err(dev, "power off failed"); return ret; } } return ret; } static int aw2013_led_resume(struct device *dev) { struct aw2013_led *led = dev_get_drvdata(dev); int ret; u8 val; aw2013_read(led, AW_REG_LED_ENABLE, &val); /* * If value in AW_REG_LED_ENABLE is not 0, it means at least * one of the RGB leds is on. So we do not need to power on again. */ if (val > 0) return ret; ret = aw2013_power_on(led, true); if (ret) { dev_err(dev, "power on failed"); return ret; } return ret; } #else static int aw2013_led_suspend(struct device *dev) { return 0; } static int aw2013_led_resume(struct device *dev) { return 0; } #endif #if (defined(CONFIG_PM) && !defined(CONFIG_FB)) static const struct dev_pm_ops aw2013_led_pm_ops = { .suspend = aw2013_led_suspend, .resume = aw2013_led_resume, }; #else static const struct dev_pm_ops aw2013_led_pm_ops = { }; #endif /* * If CONFIG_FB is defined, LEDs suspend/resume are triggered by framebuffer. * If the screen is off, LEDs go to suspend; if screen is on, LEDs go to * resume; based on user space definition, LEDs may blink when suspend, and * may be off when resume. */ #if defined(CONFIG_FB) static int fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data) { struct fb_event *evdata = data; int *blank; struct aw2013_led *led = container_of(self, struct aw2013_led, fb_notif); if (evdata && evdata->data && event == FB_EVENT_BLANK && led && led->client) { blank = evdata->data; if (*blank == FB_BLANK_UNBLANK) aw2013_led_resume(&led->client->dev); else if (*blank == FB_BLANK_POWERDOWN) aw2013_led_suspend(&led->client->dev); } return 0; } static int aw2013_set_suspend_callback(struct aw2013_led *led_array) { int ret; led_array->fb_notif.notifier_call = fb_notifier_callback; ret = fb_register_client(&led_array->fb_notif); if (ret) dev_err(&led_array->client->dev, "Unable to register fb_notifier: %d\n", ret); return ret; } #else static int aw2013_set_suspend_callback(struct aw2013_led *led_array) { return 0; } #endif static int aw2013_led_err_handle(struct aw2013_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, &aw2013_led_attr_group); led_classdev_unregister(&led_array[i].cdev); cancel_work_sync(&led_array[i].brightness_work); devm_kfree(&led_array->client->dev, led_array[i].pdata); led_array[i].pdata = NULL; } return i; } static int aw2013_led_parse_child_node(struct aw2013_led *led_array, struct device_node *node) { struct aw2013_led *led; struct device_node *temp; struct aw2013_platform_data *pdata; int rc = 0, parsed_leds = 0; for_each_child_of_node(node, temp) { led = &led_array[parsed_leds]; led->client = led_array->client; pdata = devm_kzalloc(&led->client->dev, sizeof(struct aw2013_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, "aw2013,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, "aw2013,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, "aw2013,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, "aw2013,max-current", &led->pdata->max_current); if (rc < 0) { dev_err(&led->client->dev, "Failure reading max-current, rc = %d\n", rc); goto free_pdata; } rc = of_property_read_u32(temp, "aw2013,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, "aw2013,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, "aw2013,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, "aw2013,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, aw2013_brightness_work); led->cdev.brightness_set = aw2013_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, &aw2013_led_attr_group); if (rc) { dev_err(&led->client->dev, "led sysfs rc: %d\n", rc); goto free_class; } parsed_leds++; } return 0; free_class: aw2013_led_err_handle(led_array, parsed_leds); led_classdev_unregister(&led_array[parsed_leds].cdev); cancel_work_sync(&led_array[parsed_leds].brightness_work); devm_kfree(&led->client->dev, led_array[parsed_leds].pdata); led_array[parsed_leds].pdata = NULL; return rc; free_pdata: aw2013_led_err_handle(led_array, parsed_leds); devm_kfree(&led->client->dev, led_array[parsed_leds].pdata); return rc; free_err: aw2013_led_err_handle(led_array, parsed_leds); return rc; } static int aw2013_led_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct aw2013_led *led_array; struct device_node *node; int ret, num_leds = 0; node = client->dev.of_node; if (node == NULL) return -EINVAL; num_leds = of_get_child_count(node); if (!num_leds) return -EINVAL; led_array = devm_kzalloc(&client->dev, (sizeof(struct aw2013_led) * num_leds), GFP_KERNEL); if (!led_array) { dev_err(&client->dev, "Unable to allocate memory\n"); return -ENOMEM; } led_array->client = client; led_array->num_leds = num_leds; mutex_init(&led_array->lock); ret = aw_2013_check_chipid(led_array); if (ret) { dev_err(&client->dev, "Check chip id error\n"); goto free_led_arry; } ret = aw2013_led_parse_child_node(led_array, node); if (ret) { dev_err(&client->dev, "parsed node error\n"); goto free_led_arry; } i2c_set_clientdata(client, led_array); ret = aw2013_power_init(led_array, true); if (ret) { dev_err(&client->dev, "power init failed"); goto fail_parsed_node; } ret = aw2013_power_on(led_array, true); if (ret) { dev_err(&client->dev, "power on failed"); goto pwr_deinit; } ret = aw2013_set_suspend_callback(led_array); if (ret) { dev_err(&client->dev, "set suspend callback failed"); goto pwr_deinit; } return 0; pwr_deinit: aw2013_power_init(led_array, false); fail_parsed_node: aw2013_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 aw2013_led_remove(struct i2c_client *client) { struct aw2013_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, &aw2013_led_attr_group); led_classdev_unregister(&led_array[i].cdev); cancel_work_sync(&led_array[i].brightness_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 const struct i2c_device_id aw2013_led_id[] = { {"aw2013_led", 0}, {}, }; MODULE_DEVICE_TABLE(i2c, aw2013_led_id); static struct of_device_id aw2013_match_table[] = { { .compatible = "awinic,aw2013",}, { }, }; static struct i2c_driver aw2013_led_driver = { .probe = aw2013_led_probe, .remove = aw2013_led_remove, .driver = { .name = "aw2013_led", .owner = THIS_MODULE, .of_match_table = of_match_ptr(aw2013_match_table), #ifdef CONFIG_PM .pm = &aw2013_led_pm_ops, #endif }, .id_table = aw2013_led_id, }; static int __init aw2013_led_init(void) { return i2c_add_driver(&aw2013_led_driver); } module_init(aw2013_led_init); static void __exit aw2013_led_exit(void) { i2c_del_driver(&aw2013_led_driver); } module_exit(aw2013_led_exit); MODULE_DESCRIPTION("AWINIC aw2013 LED driver"); MODULE_LICENSE("GPL v2"); Loading
Documentation/devicetree/bindings/leds/leds-aw2013.txt 0 → 100644 +75 −0 Original line number Diff line number Diff line Binding for RGB LEDs connected to AW2013. AWINIC AW2013 RGB LED driver is used to provide red/green/blue led blink or glowing to notify user for different system events, such as missed call, new sms, low battery. AW2013 RGB LED is connected through I2C. Required properties: - compatible : should be compatible = "awinic,aw2013" - reg : i2c slave address of the device - vdd-supply : Power supply needed to power up the device - vcc-supply : Power source required to power up i2c bus LED required sub-node properties: - aw2013,name : name of the LED - aw2013,id : id of the LED - aw2013,max-brightness: max brightness set of the LED - aw2013,max-current : max current set of the LED - aw2013,rise-time-ms : the rise time when led in breathe mode - aw2013,hold-time-ms : the hold time when led in breathe mode - aw2013,fall-time-ms : the fall time when led in breathe mode - aw2013,off-time-ms : the off time when led in breathe mode The definition of each time described as shown in figure: /-----------\ / | \ /| | |\ / | | | \----------- |hold_time_ms | | | | | rise_time_ms fall_time_ms | off_time_ms Example: aw2013@45 { compatible = "awinic,aw2013"; reg = <0x45>; vdd-supply = <&pm8909_l17>; vcc-supply = <&pm8909_l6>; aw2013,red { aw2013,name = "red"; aw2013,id = <0>; aw2013,max-brightness = <255>; aw2013,max-current = <1>; aw2013,rise-time-ms = <2>; aw2013,hold-time-ms = <1>; aw2013,fall-time-ms = <2>; aw2013,off-time-ms = <1>; }; aw2013,green { aw2013,name = "green"; aw2013,id = <1>; aw2013,max-brightness = <255>; aw2013,max-current = <1>; aw2013,rise-time-ms = <2>; aw2013,hold-time-ms = <1>; aw2013,fall-time-ms = <2>; aw2013,off-time-ms = <1>; }; aw2013,blue { aw2013,name = "blue"; aw2013,id = <2>; aw2013,max-brightness = <255>; aw2013,max-current = <1>; aw2013,rise-time-ms = <2>; aw2013,hold-time-ms = <1>; aw2013,fall-time-ms = <2>; aw2013,off-time-ms = <1>; }; };
Documentation/devicetree/bindings/vendor-prefixes.txt +1 −0 Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ apm Applied Micro Circuits Corporation (APM) arm ARM Ltd. atmel Atmel Corporation avago Avago Technologies awinic AWINIC Technology Co.Ltd bosch Bosch Sensortec GmbH brcm Broadcom Corporation capella Capella Microsystems, Inc. Loading
drivers/leds/Kconfig +7 −0 Original line number Diff line number Diff line Loading @@ -518,6 +518,13 @@ config LEDS_BLINKM This option enables support for the BlinkM RGB LED connected through I2C. Say Y to enable support for the BlinkM LED. config LEDS_AW2013 tristate "LED support for AW2013" depends on LEDS_CLASS && I2C help This option enables support for the AW2013 RGB LED connected through I2C. Say Y to enable support for the AW2013 LED. comment "LED Triggers" source "drivers/leds/trigger/Kconfig" Loading
drivers/leds/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o obj-$(CONFIG_LEDS_MSM_GPIO_FLASH) += leds-msm-gpio-flash.o obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o Loading
drivers/leds/leds-aw2013.c 0 → 100644 +751 −0 Original line number Diff line number Diff line /* * Copyright (c) 2015, The Linux Foundation. All rights reserved. * * 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 <linux/delay.h> #include <linux/i2c.h> #include <linux/init.h> #include <linux/leds.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/slab.h> #include <linux/regulator/consumer.h> #include <linux/leds-aw2013.h> #if defined(CONFIG_FB) #include <linux/notifier.h> #include <linux/fb.h> #endif /* register address */ #define AW_REG_RESET 0x00 #define AW_REG_GLOBAL_CONTROL 0x01 #define AW_REG_LED_STATUS 0x02 #define AW_REG_LED_ENABLE 0x30 #define AW_REG_LED_CONFIG_BASE 0x31 #define AW_REG_LED_BRIGHTNESS_BASE 0x34 #define AW_REG_TIMESET0_BASE 0x37 #define AW_REG_TIMESET1_BASE 0x38 /* register bits */ #define AW2013_CHIPID 0x33 #define AW_LED_MOUDLE_ENABLE_MASK 0x01 #define AW_LED_FADE_OFF_MASK 0x40 #define AW_LED_FADE_ON_MASK 0x20 #define AW_LED_BREATHE_MODE_MASK 0x10 #define AW_LED_RESET_MASK 0x55 #define AW_LED_RESET_DELAY 8 #define AW2013_VDD_MIN_UV 2600000 #define AW2013_VDD_MAX_UV 3300000 #define AW2013_VI2C_MIN_UV 1800000 #define AW2013_VI2C_MAX_UV 1800000 #define MAX_RISE_TIME_MS 7 #define MAX_HOLD_TIME_MS 5 #define MAX_FALL_TIME_MS 7 #define MAX_OFF_TIME_MS 5 struct aw2013_led { struct i2c_client *client; struct led_classdev cdev; struct aw2013_platform_data *pdata; struct work_struct brightness_work; struct mutex lock; struct regulator *vdd; struct regulator *vcc; #if defined(CONFIG_FB) struct notifier_block fb_notif; #endif int num_leds; int id; }; static int aw2013_write(struct aw2013_led *led, u8 reg, u8 val) { return i2c_smbus_write_byte_data(led->client, reg, val); } static int aw2013_read(struct aw2013_led *led, u8 reg, u8 *val) { s32 ret; ret = i2c_smbus_read_byte_data(led->client, reg); if (ret < 0) return ret; *val = ret; return 0; } static int aw2013_power_on(struct aw2013_led *led, bool on) { int rc; if (on) { rc = regulator_enable(led->vdd); if (rc) { dev_err(&led->client->dev, "Regulator vdd enable failed rc=%d\n", rc); return rc; } rc = regulator_enable(led->vcc); if (rc) { dev_err(&led->client->dev, "Regulator vcc enable failed rc=%d\n", rc); goto fail_enable_reg; } } else { rc = regulator_disable(led->vdd); if (rc) { dev_err(&led->client->dev, "Regulator vdd disable failed rc=%d\n", rc); return rc; } rc = regulator_disable(led->vcc); if (rc) { dev_err(&led->client->dev, "Regulator vcc disable failed rc=%d\n", rc); goto fail_disable_reg; } } return rc; fail_enable_reg: rc = regulator_disable(led->vdd); if (rc) dev_err(&led->client->dev, "Regulator vdd disable failed rc=%d\n", rc); return rc; fail_disable_reg: rc = regulator_enable(led->vdd); if (rc) dev_err(&led->client->dev, "Regulator vdd enable failed rc=%d\n", rc); return rc; } static int aw2013_power_init(struct aw2013_led *led, bool on) { int rc; if (on) { led->vdd = regulator_get(&led->client->dev, "vdd"); if (IS_ERR(led->vdd)) { rc = PTR_ERR(led->vdd); dev_err(&led->client->dev, "Regulator get failed vdd rc=%d\n", rc); return rc; } if (regulator_count_voltages(led->vdd) > 0) { rc = regulator_set_voltage(led->vdd, AW2013_VDD_MIN_UV, AW2013_VDD_MAX_UV); if (rc) { dev_err(&led->client->dev, "Regulator set_vtg failed vdd rc=%d\n", rc); goto reg_vdd_put; } } led->vcc = regulator_get(&led->client->dev, "vcc"); if (IS_ERR(led->vcc)) { rc = PTR_ERR(led->vcc); dev_err(&led->client->dev, "Regulator get failed vcc rc=%d\n", rc); goto reg_vdd_set_vtg; } if (regulator_count_voltages(led->vcc) > 0) { rc = regulator_set_voltage(led->vcc, AW2013_VI2C_MIN_UV, AW2013_VI2C_MAX_UV); if (rc) { dev_err(&led->client->dev, "Regulator set_vtg failed vcc rc=%d\n", rc); goto reg_vcc_put; } } } else { if (regulator_count_voltages(led->vdd) > 0) regulator_set_voltage(led->vdd, 0, AW2013_VDD_MAX_UV); regulator_put(led->vdd); if (regulator_count_voltages(led->vcc) > 0) regulator_set_voltage(led->vcc, 0, AW2013_VI2C_MAX_UV); regulator_put(led->vcc); } return 0; reg_vcc_put: regulator_put(led->vcc); reg_vdd_set_vtg: if (regulator_count_voltages(led->vdd) > 0) regulator_set_voltage(led->vdd, 0, AW2013_VDD_MAX_UV); reg_vdd_put: regulator_put(led->vdd); return rc; } static void aw2013_brightness_work(struct work_struct *work) { struct aw2013_led *led = container_of(work, struct aw2013_led, brightness_work); u8 val; mutex_lock(&led->pdata->led->lock); if (led->cdev.brightness > 0) { if (led->cdev.brightness > led->cdev.max_brightness) led->cdev.brightness = led->cdev.max_brightness; aw2013_write(led, AW_REG_GLOBAL_CONTROL, AW_LED_MOUDLE_ENABLE_MASK); aw2013_write(led, AW_REG_LED_CONFIG_BASE + led->id, led->pdata->max_current); aw2013_write(led, AW_REG_LED_BRIGHTNESS_BASE + led->id, led->cdev.brightness); aw2013_read(led, AW_REG_LED_ENABLE, &val); aw2013_write(led, AW_REG_LED_ENABLE, val | (1 << led->id)); } else { aw2013_read(led, AW_REG_LED_ENABLE, &val); aw2013_write(led, AW_REG_LED_ENABLE, val & (~(1 << led->id))); } mutex_unlock(&led->pdata->led->lock); } static void aw2013_led_blink_set(struct aw2013_led *led, unsigned long blinking) { u8 val; led->cdev.brightness = blinking ? led->cdev.max_brightness : 0; if (blinking > 0) { aw2013_write(led, AW_REG_GLOBAL_CONTROL, AW_LED_MOUDLE_ENABLE_MASK); aw2013_write(led, AW_REG_LED_CONFIG_BASE + led->id, AW_LED_FADE_OFF_MASK | AW_LED_FADE_ON_MASK | AW_LED_BREATHE_MODE_MASK | led->pdata->max_current); aw2013_write(led, AW_REG_LED_BRIGHTNESS_BASE + led->id, led->cdev.brightness); aw2013_write(led, AW_REG_TIMESET0_BASE + led->id * 3, led->pdata->rise_time_ms << 4 | led->pdata->hold_time_ms); aw2013_write(led, AW_REG_TIMESET1_BASE + led->id * 3, led->pdata->fall_time_ms << 4 | led->pdata->off_time_ms); aw2013_read(led, AW_REG_LED_ENABLE, &val); aw2013_write(led, AW_REG_LED_ENABLE, val | (1 << led->id)); } else { aw2013_read(led, AW_REG_LED_ENABLE, &val); aw2013_write(led, AW_REG_LED_ENABLE, val & (~(1 << led->id))); } } static void aw2013_set_brightness(struct led_classdev *cdev, enum led_brightness brightness) { struct aw2013_led *led = container_of(cdev, struct aw2013_led, cdev); led->cdev.brightness = brightness; schedule_work(&led->brightness_work); } static ssize_t aw2013_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 aw2013_led *led = container_of(led_cdev, struct aw2013_led, cdev); ssize_t ret = -EINVAL; ret = kstrtoul(buf, 10, &blinking); if (ret) return ret; mutex_lock(&led->pdata->led->lock); aw2013_led_blink_set(led, blinking); mutex_unlock(&led->pdata->led->lock); return len; } static ssize_t aw2013_led_time_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct aw2013_led *led = container_of(led_cdev, struct aw2013_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 aw2013_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 aw2013_led *led = container_of(led_cdev, struct aw2013_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; aw2013_led_blink_set(led, 1); mutex_unlock(&led->pdata->led->lock); return len; } static DEVICE_ATTR(blink, 0664, NULL, aw2013_store_blink); static DEVICE_ATTR(led_time, 0664, aw2013_led_time_show, aw2013_led_time_store); static struct attribute *aw2013_led_attributes[] = { &dev_attr_blink.attr, &dev_attr_led_time.attr, NULL, }; static struct attribute_group aw2013_led_attr_group = { .attrs = aw2013_led_attributes }; static int aw_2013_check_chipid(struct aw2013_led *led) { u8 val; aw2013_write(led, AW_REG_RESET, AW_LED_RESET_MASK); usleep(AW_LED_RESET_DELAY); aw2013_read(led, AW_REG_RESET, &val); if (val == AW2013_CHIPID) return 0; else return -EINVAL; } #ifdef CONFIG_PM static int aw2013_led_suspend(struct device *dev) { struct aw2013_led *led = dev_get_drvdata(dev); int ret; u8 val; aw2013_read(led, AW_REG_LED_ENABLE, &val); /* * If value in AW_REG_LED_ENABLE is 0, it means the RGB leds are * all off. So we need to power it off. * If value in AW_REG_LED_ENABLE is not 0, that means LEDs are * already turned on by upper layer, we keep them alive during * suspend so as to support screen-off notification LED. */ if (val == 0) { ret = aw2013_power_on(led, false); if (ret) { dev_err(dev, "power off failed"); return ret; } } return ret; } static int aw2013_led_resume(struct device *dev) { struct aw2013_led *led = dev_get_drvdata(dev); int ret; u8 val; aw2013_read(led, AW_REG_LED_ENABLE, &val); /* * If value in AW_REG_LED_ENABLE is not 0, it means at least * one of the RGB leds is on. So we do not need to power on again. */ if (val > 0) return ret; ret = aw2013_power_on(led, true); if (ret) { dev_err(dev, "power on failed"); return ret; } return ret; } #else static int aw2013_led_suspend(struct device *dev) { return 0; } static int aw2013_led_resume(struct device *dev) { return 0; } #endif #if (defined(CONFIG_PM) && !defined(CONFIG_FB)) static const struct dev_pm_ops aw2013_led_pm_ops = { .suspend = aw2013_led_suspend, .resume = aw2013_led_resume, }; #else static const struct dev_pm_ops aw2013_led_pm_ops = { }; #endif /* * If CONFIG_FB is defined, LEDs suspend/resume are triggered by framebuffer. * If the screen is off, LEDs go to suspend; if screen is on, LEDs go to * resume; based on user space definition, LEDs may blink when suspend, and * may be off when resume. */ #if defined(CONFIG_FB) static int fb_notifier_callback(struct notifier_block *self, unsigned long event, void *data) { struct fb_event *evdata = data; int *blank; struct aw2013_led *led = container_of(self, struct aw2013_led, fb_notif); if (evdata && evdata->data && event == FB_EVENT_BLANK && led && led->client) { blank = evdata->data; if (*blank == FB_BLANK_UNBLANK) aw2013_led_resume(&led->client->dev); else if (*blank == FB_BLANK_POWERDOWN) aw2013_led_suspend(&led->client->dev); } return 0; } static int aw2013_set_suspend_callback(struct aw2013_led *led_array) { int ret; led_array->fb_notif.notifier_call = fb_notifier_callback; ret = fb_register_client(&led_array->fb_notif); if (ret) dev_err(&led_array->client->dev, "Unable to register fb_notifier: %d\n", ret); return ret; } #else static int aw2013_set_suspend_callback(struct aw2013_led *led_array) { return 0; } #endif static int aw2013_led_err_handle(struct aw2013_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, &aw2013_led_attr_group); led_classdev_unregister(&led_array[i].cdev); cancel_work_sync(&led_array[i].brightness_work); devm_kfree(&led_array->client->dev, led_array[i].pdata); led_array[i].pdata = NULL; } return i; } static int aw2013_led_parse_child_node(struct aw2013_led *led_array, struct device_node *node) { struct aw2013_led *led; struct device_node *temp; struct aw2013_platform_data *pdata; int rc = 0, parsed_leds = 0; for_each_child_of_node(node, temp) { led = &led_array[parsed_leds]; led->client = led_array->client; pdata = devm_kzalloc(&led->client->dev, sizeof(struct aw2013_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, "aw2013,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, "aw2013,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, "aw2013,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, "aw2013,max-current", &led->pdata->max_current); if (rc < 0) { dev_err(&led->client->dev, "Failure reading max-current, rc = %d\n", rc); goto free_pdata; } rc = of_property_read_u32(temp, "aw2013,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, "aw2013,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, "aw2013,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, "aw2013,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, aw2013_brightness_work); led->cdev.brightness_set = aw2013_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, &aw2013_led_attr_group); if (rc) { dev_err(&led->client->dev, "led sysfs rc: %d\n", rc); goto free_class; } parsed_leds++; } return 0; free_class: aw2013_led_err_handle(led_array, parsed_leds); led_classdev_unregister(&led_array[parsed_leds].cdev); cancel_work_sync(&led_array[parsed_leds].brightness_work); devm_kfree(&led->client->dev, led_array[parsed_leds].pdata); led_array[parsed_leds].pdata = NULL; return rc; free_pdata: aw2013_led_err_handle(led_array, parsed_leds); devm_kfree(&led->client->dev, led_array[parsed_leds].pdata); return rc; free_err: aw2013_led_err_handle(led_array, parsed_leds); return rc; } static int aw2013_led_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct aw2013_led *led_array; struct device_node *node; int ret, num_leds = 0; node = client->dev.of_node; if (node == NULL) return -EINVAL; num_leds = of_get_child_count(node); if (!num_leds) return -EINVAL; led_array = devm_kzalloc(&client->dev, (sizeof(struct aw2013_led) * num_leds), GFP_KERNEL); if (!led_array) { dev_err(&client->dev, "Unable to allocate memory\n"); return -ENOMEM; } led_array->client = client; led_array->num_leds = num_leds; mutex_init(&led_array->lock); ret = aw_2013_check_chipid(led_array); if (ret) { dev_err(&client->dev, "Check chip id error\n"); goto free_led_arry; } ret = aw2013_led_parse_child_node(led_array, node); if (ret) { dev_err(&client->dev, "parsed node error\n"); goto free_led_arry; } i2c_set_clientdata(client, led_array); ret = aw2013_power_init(led_array, true); if (ret) { dev_err(&client->dev, "power init failed"); goto fail_parsed_node; } ret = aw2013_power_on(led_array, true); if (ret) { dev_err(&client->dev, "power on failed"); goto pwr_deinit; } ret = aw2013_set_suspend_callback(led_array); if (ret) { dev_err(&client->dev, "set suspend callback failed"); goto pwr_deinit; } return 0; pwr_deinit: aw2013_power_init(led_array, false); fail_parsed_node: aw2013_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 aw2013_led_remove(struct i2c_client *client) { struct aw2013_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, &aw2013_led_attr_group); led_classdev_unregister(&led_array[i].cdev); cancel_work_sync(&led_array[i].brightness_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 const struct i2c_device_id aw2013_led_id[] = { {"aw2013_led", 0}, {}, }; MODULE_DEVICE_TABLE(i2c, aw2013_led_id); static struct of_device_id aw2013_match_table[] = { { .compatible = "awinic,aw2013",}, { }, }; static struct i2c_driver aw2013_led_driver = { .probe = aw2013_led_probe, .remove = aw2013_led_remove, .driver = { .name = "aw2013_led", .owner = THIS_MODULE, .of_match_table = of_match_ptr(aw2013_match_table), #ifdef CONFIG_PM .pm = &aw2013_led_pm_ops, #endif }, .id_table = aw2013_led_id, }; static int __init aw2013_led_init(void) { return i2c_add_driver(&aw2013_led_driver); } module_init(aw2013_led_init); static void __exit aw2013_led_exit(void) { i2c_del_driver(&aw2013_led_driver); } module_exit(aw2013_led_exit); MODULE_DESCRIPTION("AWINIC aw2013 LED driver"); MODULE_LICENSE("GPL v2");