/* * leds-max8997.c - LED class driver for MAX8997 LEDs. * * Copyright (C) 2011 Samsung Electronics * Donggeun Kim <dg77.kim@samsung.com> * * 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/module.h> #include <linux/err.h> #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/leds.h> #include <linux/mfd/max8997.h> #include <linux/mfd/max8997-private.h> #include <linux/platform_device.h> #define MAX8997_LED_FLASH_SHIFT 3 #define MAX8997_LED_FLASH_CUR_MASK 0xf8 #define MAX8997_LED_MOVIE_SHIFT 4 #define MAX8997_LED_MOVIE_CUR_MASK 0xf0 #define MAX8997_LED_FLASH_MAX_BRIGHTNESS 0x1f #define MAX8997_LED_MOVIE_MAX_BRIGHTNESS 0xf #define MAX8997_LED_NONE_MAX_BRIGHTNESS 0 #define MAX8997_LED0_FLASH_MASK 0x1 #define MAX8997_LED0_FLASH_PIN_MASK 0x5 #define MAX8997_LED0_MOVIE_MASK 0x8 #define MAX8997_LED0_MOVIE_PIN_MASK 0x28 #define MAX8997_LED1_FLASH_MASK 0x2 #define MAX8997_LED1_FLASH_PIN_MASK 0x6 #define MAX8997_LED1_MOVIE_MASK 0x10 #define MAX8997_LED1_MOVIE_PIN_MASK 0x30 #define MAX8997_LED_BOOST_ENABLE_MASK (1 << 6) struct max8997_led { struct max8997_dev *iodev; struct led_classdev cdev; bool enabled; int id; enum max8997_led_mode led_mode; struct mutex mutex; }; static void max8997_led_set_mode(struct max8997_led *led, enum max8997_led_mode mode) { int ret; struct i2c_client *client = led->iodev->i2c; u8 mask = 0, val; switch (mode) { case MAX8997_FLASH_MODE: mask = MAX8997_LED1_FLASH_MASK | MAX8997_LED0_FLASH_MASK; val = led->id ? MAX8997_LED1_FLASH_MASK : MAX8997_LED0_FLASH_MASK; led->cdev.max_brightness = MAX8997_LED_FLASH_MAX_BRIGHTNESS; break; case MAX8997_MOVIE_MODE: mask = MAX8997_LED1_MOVIE_MASK | MAX8997_LED0_MOVIE_MASK; val = led->id ? MAX8997_LED1_MOVIE_MASK : MAX8997_LED0_MOVIE_MASK; led->cdev.max_brightness = MAX8997_LED_MOVIE_MAX_BRIGHTNESS; break; case MAX8997_FLASH_PIN_CONTROL_MODE: mask = MAX8997_LED1_FLASH_PIN_MASK | MAX8997_LED0_FLASH_PIN_MASK; val = led->id ? MAX8997_LED1_FLASH_PIN_MASK : MAX8997_LED0_FLASH_PIN_MASK; led->cdev.max_brightness = MAX8997_LED_FLASH_MAX_BRIGHTNESS; break; case MAX8997_MOVIE_PIN_CONTROL_MODE: mask = MAX8997_LED1_MOVIE_PIN_MASK | MAX8997_LED0_MOVIE_PIN_MASK; val = led->id ? MAX8997_LED1_MOVIE_PIN_MASK : MAX8997_LED0_MOVIE_PIN_MASK; led->cdev.max_brightness = MAX8997_LED_MOVIE_MAX_BRIGHTNESS; break; default: led->cdev.max_brightness = MAX8997_LED_NONE_MAX_BRIGHTNESS; break; } if (mask) { ret = max8997_update_reg(client, MAX8997_REG_LEN_CNTL, val, mask); if (ret) dev_err(led->iodev->dev, "failed to update register(%d)\n", ret); } led->led_mode = mode; } static void max8997_led_enable(struct max8997_led *led, bool enable) { int ret; struct i2c_client *client = led->iodev->i2c; u8 val = 0, mask = MAX8997_LED_BOOST_ENABLE_MASK; if (led->enabled == enable) return; val = enable ? MAX8997_LED_BOOST_ENABLE_MASK : 0; ret = max8997_update_reg(client, MAX8997_REG_BOOST_CNTL, val, mask); if (ret) dev_err(led->iodev->dev, "failed to update register(%d)\n", ret); led->enabled = enable; } static void max8997_led_set_current(struct max8997_led *led, enum led_brightness value) { int ret; struct i2c_client *client = led->iodev->i2c; u8 val = 0, mask = 0, reg = 0; switch (led->led_mode) { case MAX8997_FLASH_MODE: case MAX8997_FLASH_PIN_CONTROL_MODE: val = value << MAX8997_LED_FLASH_SHIFT; mask = MAX8997_LED_FLASH_CUR_MASK; reg = led->id ? MAX8997_REG_FLASH2_CUR : MAX8997_REG_FLASH1_CUR; break; case MAX8997_MOVIE_MODE: case MAX8997_MOVIE_PIN_CONTROL_MODE: val = value << MAX8997_LED_MOVIE_SHIFT; mask = MAX8997_LED_MOVIE_CUR_MASK; reg = MAX8997_REG_MOVIE_CUR; break; default: break; } if (mask) { ret = max8997_update_reg(client, reg, val, mask); if (ret) dev_err(led->iodev->dev, "failed to update register(%d)\n", ret); } } static void max8997_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness value) { struct max8997_led *led = container_of(led_cdev, struct max8997_led, cdev); if (value) { max8997_led_set_current(led, value); max8997_led_enable(led, true); } else { max8997_led_set_current(led, value); max8997_led_enable(led, false); } } static ssize_t max8997_led_show_mode(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct max8997_led *led = container_of(led_cdev, struct max8997_led, cdev); ssize_t ret = 0; mutex_lock(&led->mutex); switch (led->led_mode) { case MAX8997_FLASH_MODE: ret += sprintf(buf, "FLASH\n"); break; case MAX8997_MOVIE_MODE: ret += sprintf(buf, "MOVIE\n"); break; case MAX8997_FLASH_PIN_CONTROL_MODE: ret += sprintf(buf, "FLASH_PIN_CONTROL\n"); break; case MAX8997_MOVIE_PIN_CONTROL_MODE: ret += sprintf(buf, "MOVIE_PIN_CONTROL\n"); break; default: ret += sprintf(buf, "NONE\n"); break; } mutex_unlock(&led->mutex); return ret; } static ssize_t max8997_led_store_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct max8997_led *led = container_of(led_cdev, struct max8997_led, cdev); enum max8997_led_mode mode; mutex_lock(&led->mutex); if (!strncmp(buf, "FLASH_PIN_CONTROL", 17)) mode = MAX8997_FLASH_PIN_CONTROL_MODE; else if (!strncmp(buf, "MOVIE_PIN_CONTROL", 17)) mode = MAX8997_MOVIE_PIN_CONTROL_MODE; else if (!strncmp(buf, "FLASH", 5)) mode = MAX8997_FLASH_MODE; else if (!strncmp(buf, "MOVIE", 5)) mode = MAX8997_MOVIE_MODE; else mode = MAX8997_NONE; max8997_led_set_mode(led, mode); mutex_unlock(&led->mutex); return size; } static DEVICE_ATTR(mode, 0644, max8997_led_show_mode, max8997_led_store_mode); static int max8997_led_probe(struct platform_device *pdev) { struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev); struct max8997_led *led; char name[20]; int ret = 0; if (pdata == NULL) { dev_err(&pdev->dev, "no platform data\n"); return -ENODEV; } led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); if (led == NULL) return -ENOMEM; led->id = pdev->id; snprintf(name, sizeof(name), "max8997-led%d", pdev->id); led->cdev.name = name; led->cdev.brightness_set = max8997_led_brightness_set; led->cdev.flags |= LED_CORE_SUSPENDRESUME; led->cdev.brightness = 0; led->iodev = iodev; /* initialize mode and brightness according to platform_data */ if (pdata->led_pdata) { u8 mode = 0, brightness = 0; mode = pdata->led_pdata->mode[led->id]; brightness = pdata->led_pdata->brightness[led->id]; max8997_led_set_mode(led, pdata->led_pdata->mode[led->id]); if (brightness > led->cdev.max_brightness) brightness = led->cdev.max_brightness; max8997_led_set_current(led, brightness); led->cdev.brightness = brightness; } else { max8997_led_set_mode(led, MAX8997_NONE); max8997_led_set_current(led, 0); } mutex_init(&led->mutex); platform_set_drvdata(pdev, led); ret = led_classdev_register(&pdev->dev, &led->cdev); if (ret < 0) return ret; ret = device_create_file(led->cdev.dev, &dev_attr_mode); if (ret != 0) { dev_err(&pdev->dev, "failed to create file: %d\n", ret); led_classdev_unregister(&led->cdev); return ret; } return 0; } static int max8997_led_remove(struct platform_device *pdev) { struct max8997_led *led = platform_get_drvdata(pdev); device_remove_file(led->cdev.dev, &dev_attr_mode); led_classdev_unregister(&led->cdev); return 0; } static struct platform_driver max8997_led_driver = { .driver = { .name = "max8997-led", .owner = THIS_MODULE, }, .probe = max8997_led_probe, .remove = max8997_led_remove, }; module_platform_driver(max8997_led_driver); MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); MODULE_DESCRIPTION("MAX8997 LED driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:max8997-led");