18584cb82SDonggeun Kim /* 28584cb82SDonggeun Kim * leds-max8997.c - LED class driver for MAX8997 LEDs. 38584cb82SDonggeun Kim * 48584cb82SDonggeun Kim * Copyright (C) 2011 Samsung Electronics 58584cb82SDonggeun Kim * Donggeun Kim <dg77.kim@samsung.com> 68584cb82SDonggeun Kim * 78584cb82SDonggeun Kim * This program is free software; you can redistribute it and/or modify 88584cb82SDonggeun Kim * it under the terms of the GNU General Public License version 2 as 98584cb82SDonggeun Kim * published by the Free Software Foundation. 108584cb82SDonggeun Kim * 118584cb82SDonggeun Kim */ 128584cb82SDonggeun Kim 138584cb82SDonggeun Kim #include <linux/module.h> 148584cb82SDonggeun Kim #include <linux/err.h> 158584cb82SDonggeun Kim #include <linux/slab.h> 168584cb82SDonggeun Kim #include <linux/leds.h> 178584cb82SDonggeun Kim #include <linux/mfd/max8997.h> 188584cb82SDonggeun Kim #include <linux/mfd/max8997-private.h> 198584cb82SDonggeun Kim #include <linux/platform_device.h> 208584cb82SDonggeun Kim 218584cb82SDonggeun Kim #define MAX8997_LED_FLASH_SHIFT 3 228584cb82SDonggeun Kim #define MAX8997_LED_FLASH_CUR_MASK 0xf8 238584cb82SDonggeun Kim #define MAX8997_LED_MOVIE_SHIFT 4 248584cb82SDonggeun Kim #define MAX8997_LED_MOVIE_CUR_MASK 0xf0 258584cb82SDonggeun Kim 268584cb82SDonggeun Kim #define MAX8997_LED_FLASH_MAX_BRIGHTNESS 0x1f 278584cb82SDonggeun Kim #define MAX8997_LED_MOVIE_MAX_BRIGHTNESS 0xf 288584cb82SDonggeun Kim #define MAX8997_LED_NONE_MAX_BRIGHTNESS 0 298584cb82SDonggeun Kim 308584cb82SDonggeun Kim #define MAX8997_LED0_FLASH_MASK 0x1 318584cb82SDonggeun Kim #define MAX8997_LED0_FLASH_PIN_MASK 0x5 328584cb82SDonggeun Kim #define MAX8997_LED0_MOVIE_MASK 0x8 338584cb82SDonggeun Kim #define MAX8997_LED0_MOVIE_PIN_MASK 0x28 348584cb82SDonggeun Kim 358584cb82SDonggeun Kim #define MAX8997_LED1_FLASH_MASK 0x2 368584cb82SDonggeun Kim #define MAX8997_LED1_FLASH_PIN_MASK 0x6 378584cb82SDonggeun Kim #define MAX8997_LED1_MOVIE_MASK 0x10 388584cb82SDonggeun Kim #define MAX8997_LED1_MOVIE_PIN_MASK 0x30 398584cb82SDonggeun Kim 408584cb82SDonggeun Kim #define MAX8997_LED_BOOST_ENABLE_MASK (1 << 6) 418584cb82SDonggeun Kim 428584cb82SDonggeun Kim struct max8997_led { 438584cb82SDonggeun Kim struct max8997_dev *iodev; 448584cb82SDonggeun Kim struct led_classdev cdev; 458584cb82SDonggeun Kim bool enabled; 468584cb82SDonggeun Kim int id; 478584cb82SDonggeun Kim enum max8997_led_mode led_mode; 488584cb82SDonggeun Kim struct mutex mutex; 498584cb82SDonggeun Kim }; 508584cb82SDonggeun Kim 518584cb82SDonggeun Kim static void max8997_led_set_mode(struct max8997_led *led, 528584cb82SDonggeun Kim enum max8997_led_mode mode) 538584cb82SDonggeun Kim { 548584cb82SDonggeun Kim int ret; 558584cb82SDonggeun Kim struct i2c_client *client = led->iodev->i2c; 56eb18618bSAxel Lin u8 mask = 0, val; 578584cb82SDonggeun Kim 588584cb82SDonggeun Kim switch (mode) { 598584cb82SDonggeun Kim case MAX8997_FLASH_MODE: 60eb18618bSAxel Lin mask = MAX8997_LED1_FLASH_MASK | MAX8997_LED0_FLASH_MASK; 61eb18618bSAxel Lin val = led->id ? 628584cb82SDonggeun Kim MAX8997_LED1_FLASH_MASK : MAX8997_LED0_FLASH_MASK; 638584cb82SDonggeun Kim led->cdev.max_brightness = MAX8997_LED_FLASH_MAX_BRIGHTNESS; 648584cb82SDonggeun Kim break; 658584cb82SDonggeun Kim case MAX8997_MOVIE_MODE: 66eb18618bSAxel Lin mask = MAX8997_LED1_MOVIE_MASK | MAX8997_LED0_MOVIE_MASK; 67eb18618bSAxel Lin val = led->id ? 688584cb82SDonggeun Kim MAX8997_LED1_MOVIE_MASK : MAX8997_LED0_MOVIE_MASK; 698584cb82SDonggeun Kim led->cdev.max_brightness = MAX8997_LED_MOVIE_MAX_BRIGHTNESS; 708584cb82SDonggeun Kim break; 718584cb82SDonggeun Kim case MAX8997_FLASH_PIN_CONTROL_MODE: 72eb18618bSAxel Lin mask = MAX8997_LED1_FLASH_PIN_MASK | 73eb18618bSAxel Lin MAX8997_LED0_FLASH_PIN_MASK; 74eb18618bSAxel Lin val = led->id ? 758584cb82SDonggeun Kim MAX8997_LED1_FLASH_PIN_MASK : MAX8997_LED0_FLASH_PIN_MASK; 768584cb82SDonggeun Kim led->cdev.max_brightness = MAX8997_LED_FLASH_MAX_BRIGHTNESS; 778584cb82SDonggeun Kim break; 788584cb82SDonggeun Kim case MAX8997_MOVIE_PIN_CONTROL_MODE: 79eb18618bSAxel Lin mask = MAX8997_LED1_MOVIE_PIN_MASK | 80eb18618bSAxel Lin MAX8997_LED0_MOVIE_PIN_MASK; 81eb18618bSAxel Lin val = led->id ? 828584cb82SDonggeun Kim MAX8997_LED1_MOVIE_PIN_MASK : MAX8997_LED0_MOVIE_PIN_MASK; 838584cb82SDonggeun Kim led->cdev.max_brightness = MAX8997_LED_MOVIE_MAX_BRIGHTNESS; 848584cb82SDonggeun Kim break; 858584cb82SDonggeun Kim default: 868584cb82SDonggeun Kim led->cdev.max_brightness = MAX8997_LED_NONE_MAX_BRIGHTNESS; 878584cb82SDonggeun Kim break; 888584cb82SDonggeun Kim } 898584cb82SDonggeun Kim 908584cb82SDonggeun Kim if (mask) { 91eb18618bSAxel Lin ret = max8997_update_reg(client, MAX8997_REG_LEN_CNTL, val, 92eb18618bSAxel Lin mask); 938584cb82SDonggeun Kim if (ret) 948584cb82SDonggeun Kim dev_err(led->iodev->dev, 958584cb82SDonggeun Kim "failed to update register(%d)\n", ret); 968584cb82SDonggeun Kim } 978584cb82SDonggeun Kim 988584cb82SDonggeun Kim led->led_mode = mode; 998584cb82SDonggeun Kim } 1008584cb82SDonggeun Kim 1018584cb82SDonggeun Kim static void max8997_led_enable(struct max8997_led *led, bool enable) 1028584cb82SDonggeun Kim { 1038584cb82SDonggeun Kim int ret; 1048584cb82SDonggeun Kim struct i2c_client *client = led->iodev->i2c; 1058584cb82SDonggeun Kim u8 val = 0, mask = MAX8997_LED_BOOST_ENABLE_MASK; 1068584cb82SDonggeun Kim 1078584cb82SDonggeun Kim if (led->enabled == enable) 1088584cb82SDonggeun Kim return; 1098584cb82SDonggeun Kim 1108584cb82SDonggeun Kim val = enable ? MAX8997_LED_BOOST_ENABLE_MASK : 0; 1118584cb82SDonggeun Kim 1128584cb82SDonggeun Kim ret = max8997_update_reg(client, MAX8997_REG_BOOST_CNTL, val, mask); 1138584cb82SDonggeun Kim if (ret) 1148584cb82SDonggeun Kim dev_err(led->iodev->dev, 1158584cb82SDonggeun Kim "failed to update register(%d)\n", ret); 1168584cb82SDonggeun Kim 1178584cb82SDonggeun Kim led->enabled = enable; 1188584cb82SDonggeun Kim } 1198584cb82SDonggeun Kim 1208584cb82SDonggeun Kim static void max8997_led_set_current(struct max8997_led *led, 1218584cb82SDonggeun Kim enum led_brightness value) 1228584cb82SDonggeun Kim { 1238584cb82SDonggeun Kim int ret; 1248584cb82SDonggeun Kim struct i2c_client *client = led->iodev->i2c; 1258584cb82SDonggeun Kim u8 val = 0, mask = 0, reg = 0; 1268584cb82SDonggeun Kim 1278584cb82SDonggeun Kim switch (led->led_mode) { 1288584cb82SDonggeun Kim case MAX8997_FLASH_MODE: 1298584cb82SDonggeun Kim case MAX8997_FLASH_PIN_CONTROL_MODE: 1308584cb82SDonggeun Kim val = value << MAX8997_LED_FLASH_SHIFT; 1318584cb82SDonggeun Kim mask = MAX8997_LED_FLASH_CUR_MASK; 1328584cb82SDonggeun Kim reg = led->id ? MAX8997_REG_FLASH2_CUR : MAX8997_REG_FLASH1_CUR; 1338584cb82SDonggeun Kim break; 1348584cb82SDonggeun Kim case MAX8997_MOVIE_MODE: 1358584cb82SDonggeun Kim case MAX8997_MOVIE_PIN_CONTROL_MODE: 1368584cb82SDonggeun Kim val = value << MAX8997_LED_MOVIE_SHIFT; 1378584cb82SDonggeun Kim mask = MAX8997_LED_MOVIE_CUR_MASK; 1388584cb82SDonggeun Kim reg = MAX8997_REG_MOVIE_CUR; 1398584cb82SDonggeun Kim break; 1408584cb82SDonggeun Kim default: 1418584cb82SDonggeun Kim break; 1428584cb82SDonggeun Kim } 1438584cb82SDonggeun Kim 1448584cb82SDonggeun Kim if (mask) { 1458584cb82SDonggeun Kim ret = max8997_update_reg(client, reg, val, mask); 1468584cb82SDonggeun Kim if (ret) 1478584cb82SDonggeun Kim dev_err(led->iodev->dev, 1488584cb82SDonggeun Kim "failed to update register(%d)\n", ret); 1498584cb82SDonggeun Kim } 1508584cb82SDonggeun Kim } 1518584cb82SDonggeun Kim 1528584cb82SDonggeun Kim static void max8997_led_brightness_set(struct led_classdev *led_cdev, 1538584cb82SDonggeun Kim enum led_brightness value) 1548584cb82SDonggeun Kim { 1558584cb82SDonggeun Kim struct max8997_led *led = 1568584cb82SDonggeun Kim container_of(led_cdev, struct max8997_led, cdev); 1578584cb82SDonggeun Kim 1588584cb82SDonggeun Kim if (value) { 1598584cb82SDonggeun Kim max8997_led_set_current(led, value); 1608584cb82SDonggeun Kim max8997_led_enable(led, true); 1618584cb82SDonggeun Kim } else { 1628584cb82SDonggeun Kim max8997_led_set_current(led, value); 1638584cb82SDonggeun Kim max8997_led_enable(led, false); 1648584cb82SDonggeun Kim } 1658584cb82SDonggeun Kim } 1668584cb82SDonggeun Kim 1678584cb82SDonggeun Kim static ssize_t max8997_led_show_mode(struct device *dev, 1688584cb82SDonggeun Kim struct device_attribute *attr, char *buf) 1698584cb82SDonggeun Kim { 1708584cb82SDonggeun Kim struct led_classdev *led_cdev = dev_get_drvdata(dev); 1718584cb82SDonggeun Kim struct max8997_led *led = 1728584cb82SDonggeun Kim container_of(led_cdev, struct max8997_led, cdev); 1738584cb82SDonggeun Kim ssize_t ret = 0; 1748584cb82SDonggeun Kim 1758584cb82SDonggeun Kim mutex_lock(&led->mutex); 1768584cb82SDonggeun Kim 1778584cb82SDonggeun Kim switch (led->led_mode) { 1788584cb82SDonggeun Kim case MAX8997_FLASH_MODE: 1798584cb82SDonggeun Kim ret += sprintf(buf, "FLASH\n"); 1808584cb82SDonggeun Kim break; 1818584cb82SDonggeun Kim case MAX8997_MOVIE_MODE: 1828584cb82SDonggeun Kim ret += sprintf(buf, "MOVIE\n"); 1838584cb82SDonggeun Kim break; 1848584cb82SDonggeun Kim case MAX8997_FLASH_PIN_CONTROL_MODE: 1858584cb82SDonggeun Kim ret += sprintf(buf, "FLASH_PIN_CONTROL\n"); 1868584cb82SDonggeun Kim break; 1878584cb82SDonggeun Kim case MAX8997_MOVIE_PIN_CONTROL_MODE: 1888584cb82SDonggeun Kim ret += sprintf(buf, "MOVIE_PIN_CONTROL\n"); 1898584cb82SDonggeun Kim break; 1908584cb82SDonggeun Kim default: 1918584cb82SDonggeun Kim ret += sprintf(buf, "NONE\n"); 1928584cb82SDonggeun Kim break; 1938584cb82SDonggeun Kim } 1948584cb82SDonggeun Kim 1958584cb82SDonggeun Kim mutex_unlock(&led->mutex); 1968584cb82SDonggeun Kim 1978584cb82SDonggeun Kim return ret; 1988584cb82SDonggeun Kim } 1998584cb82SDonggeun Kim 2008584cb82SDonggeun Kim static ssize_t max8997_led_store_mode(struct device *dev, 2018584cb82SDonggeun Kim struct device_attribute *attr, 2028584cb82SDonggeun Kim const char *buf, size_t size) 2038584cb82SDonggeun Kim { 2048584cb82SDonggeun Kim struct led_classdev *led_cdev = dev_get_drvdata(dev); 2058584cb82SDonggeun Kim struct max8997_led *led = 2068584cb82SDonggeun Kim container_of(led_cdev, struct max8997_led, cdev); 2078584cb82SDonggeun Kim enum max8997_led_mode mode; 2088584cb82SDonggeun Kim 2098584cb82SDonggeun Kim mutex_lock(&led->mutex); 2108584cb82SDonggeun Kim 2118584cb82SDonggeun Kim if (!strncmp(buf, "FLASH_PIN_CONTROL", 17)) 2128584cb82SDonggeun Kim mode = MAX8997_FLASH_PIN_CONTROL_MODE; 2138584cb82SDonggeun Kim else if (!strncmp(buf, "MOVIE_PIN_CONTROL", 17)) 2148584cb82SDonggeun Kim mode = MAX8997_MOVIE_PIN_CONTROL_MODE; 2158584cb82SDonggeun Kim else if (!strncmp(buf, "FLASH", 5)) 2168584cb82SDonggeun Kim mode = MAX8997_FLASH_MODE; 2178584cb82SDonggeun Kim else if (!strncmp(buf, "MOVIE", 5)) 2188584cb82SDonggeun Kim mode = MAX8997_MOVIE_MODE; 2198584cb82SDonggeun Kim else 2208584cb82SDonggeun Kim mode = MAX8997_NONE; 2218584cb82SDonggeun Kim 2228584cb82SDonggeun Kim max8997_led_set_mode(led, mode); 2238584cb82SDonggeun Kim 2248584cb82SDonggeun Kim mutex_unlock(&led->mutex); 2258584cb82SDonggeun Kim 2268584cb82SDonggeun Kim return size; 2278584cb82SDonggeun Kim } 2288584cb82SDonggeun Kim 2298584cb82SDonggeun Kim static DEVICE_ATTR(mode, 0644, max8997_led_show_mode, max8997_led_store_mode); 2308584cb82SDonggeun Kim 23135c16499SJohan Hovold static struct attribute *max8997_attrs[] = { 23235c16499SJohan Hovold &dev_attr_mode.attr, 23335c16499SJohan Hovold NULL 23435c16499SJohan Hovold }; 23535c16499SJohan Hovold ATTRIBUTE_GROUPS(max8997); 23635c16499SJohan Hovold 23798ea1ea2SBill Pemberton static int max8997_led_probe(struct platform_device *pdev) 2388584cb82SDonggeun Kim { 2398584cb82SDonggeun Kim struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); 2408584cb82SDonggeun Kim struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev); 2418584cb82SDonggeun Kim struct max8997_led *led; 2428584cb82SDonggeun Kim char name[20]; 2438584cb82SDonggeun Kim int ret = 0; 2448584cb82SDonggeun Kim 2458584cb82SDonggeun Kim if (pdata == NULL) { 2468584cb82SDonggeun Kim dev_err(&pdev->dev, "no platform data\n"); 2478584cb82SDonggeun Kim return -ENODEV; 2488584cb82SDonggeun Kim } 2498584cb82SDonggeun Kim 2507f13bbf7SSachin Kamat led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); 2517f13bbf7SSachin Kamat if (led == NULL) 2527f13bbf7SSachin Kamat return -ENOMEM; 2538584cb82SDonggeun Kim 2548584cb82SDonggeun Kim led->id = pdev->id; 2558584cb82SDonggeun Kim snprintf(name, sizeof(name), "max8997-led%d", pdev->id); 2568584cb82SDonggeun Kim 2578584cb82SDonggeun Kim led->cdev.name = name; 2588584cb82SDonggeun Kim led->cdev.brightness_set = max8997_led_brightness_set; 2598584cb82SDonggeun Kim led->cdev.flags |= LED_CORE_SUSPENDRESUME; 2608584cb82SDonggeun Kim led->cdev.brightness = 0; 26135c16499SJohan Hovold led->cdev.groups = max8997_groups; 2628584cb82SDonggeun Kim led->iodev = iodev; 2638584cb82SDonggeun Kim 2648584cb82SDonggeun Kim /* initialize mode and brightness according to platform_data */ 2658584cb82SDonggeun Kim if (pdata->led_pdata) { 2668584cb82SDonggeun Kim u8 mode = 0, brightness = 0; 2678584cb82SDonggeun Kim 2688584cb82SDonggeun Kim mode = pdata->led_pdata->mode[led->id]; 2698584cb82SDonggeun Kim brightness = pdata->led_pdata->brightness[led->id]; 2708584cb82SDonggeun Kim 271*7a5de56dSColin Ian King max8997_led_set_mode(led, mode); 2728584cb82SDonggeun Kim 2738584cb82SDonggeun Kim if (brightness > led->cdev.max_brightness) 2748584cb82SDonggeun Kim brightness = led->cdev.max_brightness; 2758584cb82SDonggeun Kim max8997_led_set_current(led, brightness); 2768584cb82SDonggeun Kim led->cdev.brightness = brightness; 2778584cb82SDonggeun Kim } else { 2788584cb82SDonggeun Kim max8997_led_set_mode(led, MAX8997_NONE); 2798584cb82SDonggeun Kim max8997_led_set_current(led, 0); 2808584cb82SDonggeun Kim } 2818584cb82SDonggeun Kim 2828584cb82SDonggeun Kim mutex_init(&led->mutex); 2838584cb82SDonggeun Kim 2841c430f90SAmitoj Kaur Chawla ret = devm_led_classdev_register(&pdev->dev, &led->cdev); 2858584cb82SDonggeun Kim if (ret < 0) 2867f13bbf7SSachin Kamat return ret; 2878584cb82SDonggeun Kim 2888584cb82SDonggeun Kim return 0; 2898584cb82SDonggeun Kim } 2908584cb82SDonggeun Kim 2918584cb82SDonggeun Kim static struct platform_driver max8997_led_driver = { 2928584cb82SDonggeun Kim .driver = { 2938584cb82SDonggeun Kim .name = "max8997-led", 2948584cb82SDonggeun Kim }, 2958584cb82SDonggeun Kim .probe = max8997_led_probe, 2968584cb82SDonggeun Kim }; 2978584cb82SDonggeun Kim 2987fafefb8SSachin Kamat module_platform_driver(max8997_led_driver); 2998584cb82SDonggeun Kim 3008584cb82SDonggeun Kim MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); 3018584cb82SDonggeun Kim MODULE_DESCRIPTION("MAX8997 LED driver"); 3028584cb82SDonggeun Kim MODULE_LICENSE("GPL"); 3038584cb82SDonggeun Kim MODULE_ALIAS("platform:max8997-led"); 304