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