xref: /openbmc/linux/drivers/leds/leds-88pm860x.c (revision cbecf716ca618fd44feda6bd9a64a8179d031fc5)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
20a2f915bSHaojian Zhuang /*
30a2f915bSHaojian Zhuang  * LED driver for Marvell 88PM860x
40a2f915bSHaojian Zhuang  *
50a2f915bSHaojian Zhuang  * Copyright (C) 2009 Marvell International Ltd.
60a2f915bSHaojian Zhuang  *	Haojian Zhuang <haojian.zhuang@marvell.com>
70a2f915bSHaojian Zhuang  */
80a2f915bSHaojian Zhuang 
90a2f915bSHaojian Zhuang #include <linux/kernel.h>
102e57d567SHaojian Zhuang #include <linux/of.h>
110a2f915bSHaojian Zhuang #include <linux/platform_device.h>
120a2f915bSHaojian Zhuang #include <linux/i2c.h>
130a2f915bSHaojian Zhuang #include <linux/leds.h>
145a0e3ad6STejun Heo #include <linux/slab.h>
150a2f915bSHaojian Zhuang #include <linux/mfd/88pm860x.h>
1654f4dedbSPaul Gortmaker #include <linux/module.h>
170a2f915bSHaojian Zhuang 
180a2f915bSHaojian Zhuang #define LED_PWM_MASK		(0x1F)
190a2f915bSHaojian Zhuang #define LED_CURRENT_MASK	(0x07 << 5)
200a2f915bSHaojian Zhuang 
210a2f915bSHaojian Zhuang #define LED_BLINK_MASK		(0x7F)
220a2f915bSHaojian Zhuang 
23f5d59fc5SHaojian Zhuang #define LED_ON_CONTINUOUS	(0x0F << 3)
240a2f915bSHaojian Zhuang 
250a2f915bSHaojian Zhuang #define LED1_BLINK_EN		(1 << 1)
260a2f915bSHaojian Zhuang #define LED2_BLINK_EN		(1 << 2)
270a2f915bSHaojian Zhuang 
280a2f915bSHaojian Zhuang struct pm860x_led {
290a2f915bSHaojian Zhuang 	struct led_classdev cdev;
300a2f915bSHaojian Zhuang 	struct i2c_client *i2c;
310a2f915bSHaojian Zhuang 	struct pm860x_chip *chip;
320a2f915bSHaojian Zhuang 	struct mutex lock;
330a2f915bSHaojian Zhuang 	char name[MFD_NAME_SIZE];
340a2f915bSHaojian Zhuang 
350a2f915bSHaojian Zhuang 	int port;
360a2f915bSHaojian Zhuang 	int iset;
370a2f915bSHaojian Zhuang 	unsigned char brightness;
380a2f915bSHaojian Zhuang 	unsigned char current_brightness;
390a2f915bSHaojian Zhuang 
40894fc8f2SHaojian Zhuang 	int reg_control;
41894fc8f2SHaojian Zhuang 	int reg_blink;
42894fc8f2SHaojian Zhuang 	int blink_mask;
430a2f915bSHaojian Zhuang };
440a2f915bSHaojian Zhuang 
led_power_set(struct pm860x_chip * chip,int port,int on)45b3b97473SJett.Zhou static int led_power_set(struct pm860x_chip *chip, int port, int on)
46b3b97473SJett.Zhou {
47b3b97473SJett.Zhou 	int ret = -EINVAL;
48b3b97473SJett.Zhou 
49b3b97473SJett.Zhou 	switch (port) {
50894fc8f2SHaojian Zhuang 	case 0:
51894fc8f2SHaojian Zhuang 	case 1:
52894fc8f2SHaojian Zhuang 	case 2:
53b3b97473SJett.Zhou 		ret = on ? pm8606_osc_enable(chip, RGB1_ENABLE) :
54b3b97473SJett.Zhou 			pm8606_osc_disable(chip, RGB1_ENABLE);
55b3b97473SJett.Zhou 		break;
56894fc8f2SHaojian Zhuang 	case 3:
57894fc8f2SHaojian Zhuang 	case 4:
58894fc8f2SHaojian Zhuang 	case 5:
59b3b97473SJett.Zhou 		ret = on ? pm8606_osc_enable(chip, RGB2_ENABLE) :
60b3b97473SJett.Zhou 			pm8606_osc_disable(chip, RGB2_ENABLE);
61b3b97473SJett.Zhou 		break;
62b3b97473SJett.Zhou 	}
63b3b97473SJett.Zhou 	return ret;
64b3b97473SJett.Zhou }
65b3b97473SJett.Zhou 
pm860x_led_set(struct led_classdev * cdev,enum led_brightness value)668824fefbSAndrew Lunn static int pm860x_led_set(struct led_classdev *cdev,
678824fefbSAndrew Lunn 			   enum led_brightness value)
680a2f915bSHaojian Zhuang {
698824fefbSAndrew Lunn 	struct pm860x_led *led = container_of(cdev, struct pm860x_led, cdev);
70f5d59fc5SHaojian Zhuang 	struct pm860x_chip *chip;
713154c344SHaojian Zhuang 	unsigned char buf[3];
72894fc8f2SHaojian Zhuang 	int ret;
730a2f915bSHaojian Zhuang 
74f5d59fc5SHaojian Zhuang 	chip = led->chip;
75f5d59fc5SHaojian Zhuang 	mutex_lock(&led->lock);
768824fefbSAndrew Lunn 	led->brightness = value >> 3;
778824fefbSAndrew Lunn 
78f5d59fc5SHaojian Zhuang 	if ((led->current_brightness == 0) && led->brightness) {
79b3b97473SJett.Zhou 		led_power_set(chip, led->port, 1);
80f5d59fc5SHaojian Zhuang 		if (led->iset) {
81894fc8f2SHaojian Zhuang 			pm860x_set_bits(led->i2c, led->reg_control,
82f5d59fc5SHaojian Zhuang 					LED_CURRENT_MASK, led->iset);
83f5d59fc5SHaojian Zhuang 		}
84894fc8f2SHaojian Zhuang 		pm860x_set_bits(led->i2c, led->reg_blink,
853154c344SHaojian Zhuang 				LED_BLINK_MASK, LED_ON_CONTINUOUS);
86894fc8f2SHaojian Zhuang 		pm860x_set_bits(led->i2c, PM8606_WLED3B, led->blink_mask,
87894fc8f2SHaojian Zhuang 				led->blink_mask);
883154c344SHaojian Zhuang 	}
89894fc8f2SHaojian Zhuang 	pm860x_set_bits(led->i2c, led->reg_control, LED_PWM_MASK,
903154c344SHaojian Zhuang 			led->brightness);
913154c344SHaojian Zhuang 
923154c344SHaojian Zhuang 	if (led->brightness == 0) {
93894fc8f2SHaojian Zhuang 		pm860x_bulk_read(led->i2c, led->reg_control, 3, buf);
943154c344SHaojian Zhuang 		ret = buf[0] & LED_PWM_MASK;
953154c344SHaojian Zhuang 		ret |= buf[1] & LED_PWM_MASK;
963154c344SHaojian Zhuang 		ret |= buf[2] & LED_PWM_MASK;
973154c344SHaojian Zhuang 		if (ret == 0) {
983154c344SHaojian Zhuang 			/* unset current since no led is lighting */
99894fc8f2SHaojian Zhuang 			pm860x_set_bits(led->i2c, led->reg_control,
100f5d59fc5SHaojian Zhuang 					LED_CURRENT_MASK, 0);
101894fc8f2SHaojian Zhuang 			pm860x_set_bits(led->i2c, PM8606_WLED3B,
102894fc8f2SHaojian Zhuang 					led->blink_mask, 0);
103b3b97473SJett.Zhou 			led_power_set(chip, led->port, 0);
104f5d59fc5SHaojian Zhuang 		}
1053154c344SHaojian Zhuang 	}
106f5d59fc5SHaojian Zhuang 	led->current_brightness = led->brightness;
107f5d59fc5SHaojian Zhuang 	dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n",
108894fc8f2SHaojian Zhuang 		led->reg_control, led->brightness);
109f5d59fc5SHaojian Zhuang 	mutex_unlock(&led->lock);
1100a2f915bSHaojian Zhuang 
1118824fefbSAndrew Lunn 	return 0;
1120a2f915bSHaojian Zhuang }
1130a2f915bSHaojian Zhuang 
1142e57d567SHaojian Zhuang #ifdef CONFIG_OF
pm860x_led_dt_init(struct platform_device * pdev,struct pm860x_led * data)1152e57d567SHaojian Zhuang static int pm860x_led_dt_init(struct platform_device *pdev,
1162e57d567SHaojian Zhuang 			      struct pm860x_led *data)
1172e57d567SHaojian Zhuang {
11861d4eb27SAxel Lin 	struct device_node *nproot, *np;
1192e57d567SHaojian Zhuang 	int iset = 0;
12061d4eb27SAxel Lin 
1218853c95eSMarek Behún 	if (!dev_of_node(pdev->dev.parent))
1222e57d567SHaojian Zhuang 		return -ENODEV;
1238853c95eSMarek Behún 	nproot = of_get_child_by_name(dev_of_node(pdev->dev.parent), "leds");
1242e57d567SHaojian Zhuang 	if (!nproot) {
1252e57d567SHaojian Zhuang 		dev_err(&pdev->dev, "failed to find leds node\n");
1262e57d567SHaojian Zhuang 		return -ENODEV;
1272e57d567SHaojian Zhuang 	}
128*99a013c8SMarek Behún 	for_each_available_child_of_node(nproot, np) {
129555fc5baSRob Herring 		if (of_node_name_eq(np, data->name)) {
1302e57d567SHaojian Zhuang 			of_property_read_u32(np, "marvell,88pm860x-iset",
1312e57d567SHaojian Zhuang 					     &iset);
1322e57d567SHaojian Zhuang 			data->iset = PM8606_LED_CURRENT(iset);
133ffdc307dSJulia Lawall 			of_node_put(np);
1342e57d567SHaojian Zhuang 			break;
1352e57d567SHaojian Zhuang 		}
1362e57d567SHaojian Zhuang 	}
13761d4eb27SAxel Lin 	of_node_put(nproot);
1382e57d567SHaojian Zhuang 	return 0;
1392e57d567SHaojian Zhuang }
1402e57d567SHaojian Zhuang #else
1412e57d567SHaojian Zhuang #define pm860x_led_dt_init(x, y)	(-1)
1422e57d567SHaojian Zhuang #endif
1432e57d567SHaojian Zhuang 
pm860x_led_probe(struct platform_device * pdev)1440a2f915bSHaojian Zhuang static int pm860x_led_probe(struct platform_device *pdev)
1450a2f915bSHaojian Zhuang {
1460a2f915bSHaojian Zhuang 	struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
14787aae1eaSJingoo Han 	struct pm860x_led_pdata *pdata = dev_get_platdata(&pdev->dev);
1480a2f915bSHaojian Zhuang 	struct pm860x_led *data;
1490a2f915bSHaojian Zhuang 	struct resource *res;
150894fc8f2SHaojian Zhuang 	int ret = 0;
1510a2f915bSHaojian Zhuang 
152b523cfe6SDevendra Naga 	data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_led), GFP_KERNEL);
1530a2f915bSHaojian Zhuang 	if (data == NULL)
1540a2f915bSHaojian Zhuang 		return -ENOMEM;
155894fc8f2SHaojian Zhuang 	res = platform_get_resource_byname(pdev, IORESOURCE_REG, "control");
156894fc8f2SHaojian Zhuang 	if (!res) {
157894fc8f2SHaojian Zhuang 		dev_err(&pdev->dev, "No REG resource for control\n");
1583651c4b9SJingoo Han 		return -ENXIO;
159894fc8f2SHaojian Zhuang 	}
160894fc8f2SHaojian Zhuang 	data->reg_control = res->start;
161894fc8f2SHaojian Zhuang 	res = platform_get_resource_byname(pdev, IORESOURCE_REG, "blink");
162894fc8f2SHaojian Zhuang 	if (!res) {
163894fc8f2SHaojian Zhuang 		dev_err(&pdev->dev, "No REG resource for blink\n");
1643651c4b9SJingoo Han 		return -ENXIO;
165894fc8f2SHaojian Zhuang 	}
166894fc8f2SHaojian Zhuang 	data->reg_blink = res->start;
167894fc8f2SHaojian Zhuang 	memset(data->name, 0, MFD_NAME_SIZE);
168894fc8f2SHaojian Zhuang 	switch (pdev->id) {
169894fc8f2SHaojian Zhuang 	case 0:
170894fc8f2SHaojian Zhuang 		data->blink_mask = LED1_BLINK_EN;
171894fc8f2SHaojian Zhuang 		sprintf(data->name, "led0-red");
172894fc8f2SHaojian Zhuang 		break;
173894fc8f2SHaojian Zhuang 	case 1:
174894fc8f2SHaojian Zhuang 		data->blink_mask = LED1_BLINK_EN;
175894fc8f2SHaojian Zhuang 		sprintf(data->name, "led0-green");
176894fc8f2SHaojian Zhuang 		break;
177894fc8f2SHaojian Zhuang 	case 2:
178894fc8f2SHaojian Zhuang 		data->blink_mask = LED1_BLINK_EN;
179894fc8f2SHaojian Zhuang 		sprintf(data->name, "led0-blue");
180894fc8f2SHaojian Zhuang 		break;
181894fc8f2SHaojian Zhuang 	case 3:
182894fc8f2SHaojian Zhuang 		data->blink_mask = LED2_BLINK_EN;
183894fc8f2SHaojian Zhuang 		sprintf(data->name, "led1-red");
184894fc8f2SHaojian Zhuang 		break;
185894fc8f2SHaojian Zhuang 	case 4:
186894fc8f2SHaojian Zhuang 		data->blink_mask = LED2_BLINK_EN;
187894fc8f2SHaojian Zhuang 		sprintf(data->name, "led1-green");
188894fc8f2SHaojian Zhuang 		break;
189894fc8f2SHaojian Zhuang 	case 5:
190894fc8f2SHaojian Zhuang 		data->blink_mask = LED2_BLINK_EN;
191894fc8f2SHaojian Zhuang 		sprintf(data->name, "led1-blue");
192894fc8f2SHaojian Zhuang 		break;
193894fc8f2SHaojian Zhuang 	}
1940a2f915bSHaojian Zhuang 	data->chip = chip;
1950a2f915bSHaojian Zhuang 	data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion;
196894fc8f2SHaojian Zhuang 	data->port = pdev->id;
1972e57d567SHaojian Zhuang 	if (pm860x_led_dt_init(pdev, data))
1982e57d567SHaojian Zhuang 		if (pdata)
1990a2f915bSHaojian Zhuang 			data->iset = pdata->iset;
2000a2f915bSHaojian Zhuang 
2010a2f915bSHaojian Zhuang 	data->current_brightness = 0;
2020a2f915bSHaojian Zhuang 	data->cdev.name = data->name;
2038824fefbSAndrew Lunn 	data->cdev.brightness_set_blocking = pm860x_led_set;
2040a2f915bSHaojian Zhuang 	mutex_init(&data->lock);
2050a2f915bSHaojian Zhuang 
206eca21c2dSJohan Hovold 	ret = led_classdev_register(chip->dev, &data->cdev);
2070a2f915bSHaojian Zhuang 	if (ret < 0) {
2080a2f915bSHaojian Zhuang 		dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
209b523cfe6SDevendra Naga 		return ret;
2100a2f915bSHaojian Zhuang 	}
2113154c344SHaojian Zhuang 	pm860x_led_set(&data->cdev, 0);
212eca21c2dSJohan Hovold 
213eca21c2dSJohan Hovold 	platform_set_drvdata(pdev, data);
214eca21c2dSJohan Hovold 
2150a2f915bSHaojian Zhuang 	return 0;
2160a2f915bSHaojian Zhuang }
2170a2f915bSHaojian Zhuang 
pm860x_led_remove(struct platform_device * pdev)218eca21c2dSJohan Hovold static int pm860x_led_remove(struct platform_device *pdev)
219eca21c2dSJohan Hovold {
220eca21c2dSJohan Hovold 	struct pm860x_led *data = platform_get_drvdata(pdev);
221eca21c2dSJohan Hovold 
222eca21c2dSJohan Hovold 	led_classdev_unregister(&data->cdev);
223eca21c2dSJohan Hovold 
224eca21c2dSJohan Hovold 	return 0;
225eca21c2dSJohan Hovold }
2260a2f915bSHaojian Zhuang 
2270a2f915bSHaojian Zhuang static struct platform_driver pm860x_led_driver = {
2280a2f915bSHaojian Zhuang 	.driver	= {
2290a2f915bSHaojian Zhuang 		.name	= "88pm860x-led",
2300a2f915bSHaojian Zhuang 	},
2310a2f915bSHaojian Zhuang 	.probe	= pm860x_led_probe,
232eca21c2dSJohan Hovold 	.remove	= pm860x_led_remove,
2330a2f915bSHaojian Zhuang };
2340a2f915bSHaojian Zhuang 
235892a8843SAxel Lin module_platform_driver(pm860x_led_driver);
2360a2f915bSHaojian Zhuang 
2370a2f915bSHaojian Zhuang MODULE_DESCRIPTION("LED driver for Marvell PM860x");
2380a2f915bSHaojian Zhuang MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
2390a2f915bSHaojian Zhuang MODULE_LICENSE("GPL");
2400a2f915bSHaojian Zhuang MODULE_ALIAS("platform:88pm860x-led");
241