373 lines
		
	
	
	
		
			9 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			373 lines
		
	
	
	
		
			9 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * 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_clear_mode(struct max8997_led *led, | ||
|  | 			enum max8997_led_mode mode) | ||
|  | { | ||
|  | 	struct i2c_client *client = led->iodev->i2c; | ||
|  | 	u8 val = 0, mask = 0; | ||
|  | 	int ret; | ||
|  | 
 | ||
|  | 	switch (mode) { | ||
|  | 	case MAX8997_FLASH_MODE: | ||
|  | 		mask = led->id ? | ||
|  | 		      MAX8997_LED1_FLASH_MASK : MAX8997_LED0_FLASH_MASK; | ||
|  | 		break; | ||
|  | 	case MAX8997_MOVIE_MODE: | ||
|  | 		mask = led->id ? | ||
|  | 		      MAX8997_LED1_MOVIE_MASK : MAX8997_LED0_MOVIE_MASK; | ||
|  | 		break; | ||
|  | 	case MAX8997_FLASH_PIN_CONTROL_MODE: | ||
|  | 		mask = led->id ? | ||
|  | 		      MAX8997_LED1_FLASH_PIN_MASK : MAX8997_LED0_FLASH_PIN_MASK; | ||
|  | 		break; | ||
|  | 	case MAX8997_MOVIE_PIN_CONTROL_MODE: | ||
|  | 		mask = led->id ? | ||
|  | 		      MAX8997_LED1_MOVIE_PIN_MASK : MAX8997_LED0_MOVIE_PIN_MASK; | ||
|  | 		break; | ||
|  | 	default: | ||
|  | 		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); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | 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; | ||
|  | 
 | ||
|  | 	/* First, clear the previous mode */ | ||
|  | 	max8997_led_clear_mode(led, led->led_mode); | ||
|  | 
 | ||
|  | 	switch (mode) { | ||
|  | 	case MAX8997_FLASH_MODE: | ||
|  | 		mask = 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 = 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 = 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 = 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, mask, 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 __devinit 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 = kzalloc(sizeof(*led), GFP_KERNEL); | ||
|  | 	if (led == NULL) { | ||
|  | 		ret = -ENOMEM; | ||
|  | 		goto err_mem; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	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) | ||
|  | 		goto err_led; | ||
|  | 
 | ||
|  | 	ret = device_create_file(led->cdev.dev, &dev_attr_mode); | ||
|  | 	if (ret != 0) { | ||
|  | 		dev_err(&pdev->dev, | ||
|  | 			"failed to create file: %d\n", ret); | ||
|  | 		goto err_file; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | 
 | ||
|  | err_file: | ||
|  | 	led_classdev_unregister(&led->cdev); | ||
|  | err_led: | ||
|  | 	kfree(led); | ||
|  | err_mem: | ||
|  | 	return ret; | ||
|  | } | ||
|  | 
 | ||
|  | static int __devexit 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); | ||
|  | 	kfree(led); | ||
|  | 
 | ||
|  | 	return 0; | ||
|  | } | ||
|  | 
 | ||
|  | static struct platform_driver max8997_led_driver = { | ||
|  | 	.driver = { | ||
|  | 		.name  = "max8997-led", | ||
|  | 		.owner = THIS_MODULE, | ||
|  | 	}, | ||
|  | 	.probe  = max8997_led_probe, | ||
|  | 	.remove = __devexit_p(max8997_led_remove), | ||
|  | }; | ||
|  | 
 | ||
|  | static int __init max8997_led_init(void) | ||
|  | { | ||
|  | 	return platform_driver_register(&max8997_led_driver); | ||
|  | } | ||
|  | module_init(max8997_led_init); | ||
|  | 
 | ||
|  | static void __exit max8997_led_exit(void) | ||
|  | { | ||
|  | 	platform_driver_unregister(&max8997_led_driver); | ||
|  | } | ||
|  | module_exit(max8997_led_exit); | ||
|  | 
 | ||
|  | MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); | ||
|  | MODULE_DESCRIPTION("MAX8997 LED driver"); | ||
|  | MODULE_LICENSE("GPL"); | ||
|  | MODULE_ALIAS("platform:max8997-led"); |