1 /* 2 * LED driver for Marvell 88PM860x 3 * 4 * Copyright (C) 2009 Marvell International Ltd. 5 * Haojian Zhuang <haojian.zhuang@marvell.com> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 * 11 */ 12 13 #include <linux/kernel.h> 14 #include <linux/of.h> 15 #include <linux/platform_device.h> 16 #include <linux/i2c.h> 17 #include <linux/leds.h> 18 #include <linux/slab.h> 19 #include <linux/workqueue.h> 20 #include <linux/mfd/88pm860x.h> 21 #include <linux/module.h> 22 23 #define LED_PWM_MASK (0x1F) 24 #define LED_CURRENT_MASK (0x07 << 5) 25 26 #define LED_BLINK_MASK (0x7F) 27 28 #define LED_ON_CONTINUOUS (0x0F << 3) 29 30 #define LED1_BLINK_EN (1 << 1) 31 #define LED2_BLINK_EN (1 << 2) 32 33 struct pm860x_led { 34 struct led_classdev cdev; 35 struct i2c_client *i2c; 36 struct work_struct work; 37 struct pm860x_chip *chip; 38 struct mutex lock; 39 char name[MFD_NAME_SIZE]; 40 41 int port; 42 int iset; 43 unsigned char brightness; 44 unsigned char current_brightness; 45 46 int reg_control; 47 int reg_blink; 48 int blink_mask; 49 }; 50 51 static int led_power_set(struct pm860x_chip *chip, int port, int on) 52 { 53 int ret = -EINVAL; 54 55 switch (port) { 56 case 0: 57 case 1: 58 case 2: 59 ret = on ? pm8606_osc_enable(chip, RGB1_ENABLE) : 60 pm8606_osc_disable(chip, RGB1_ENABLE); 61 break; 62 case 3: 63 case 4: 64 case 5: 65 ret = on ? pm8606_osc_enable(chip, RGB2_ENABLE) : 66 pm8606_osc_disable(chip, RGB2_ENABLE); 67 break; 68 } 69 return ret; 70 } 71 72 static void pm860x_led_work(struct work_struct *work) 73 { 74 75 struct pm860x_led *led; 76 struct pm860x_chip *chip; 77 unsigned char buf[3]; 78 int ret; 79 80 led = container_of(work, struct pm860x_led, work); 81 chip = led->chip; 82 mutex_lock(&led->lock); 83 if ((led->current_brightness == 0) && led->brightness) { 84 led_power_set(chip, led->port, 1); 85 if (led->iset) { 86 pm860x_set_bits(led->i2c, led->reg_control, 87 LED_CURRENT_MASK, led->iset); 88 } 89 pm860x_set_bits(led->i2c, led->reg_blink, 90 LED_BLINK_MASK, LED_ON_CONTINUOUS); 91 pm860x_set_bits(led->i2c, PM8606_WLED3B, led->blink_mask, 92 led->blink_mask); 93 } 94 pm860x_set_bits(led->i2c, led->reg_control, LED_PWM_MASK, 95 led->brightness); 96 97 if (led->brightness == 0) { 98 pm860x_bulk_read(led->i2c, led->reg_control, 3, buf); 99 ret = buf[0] & LED_PWM_MASK; 100 ret |= buf[1] & LED_PWM_MASK; 101 ret |= buf[2] & LED_PWM_MASK; 102 if (ret == 0) { 103 /* unset current since no led is lighting */ 104 pm860x_set_bits(led->i2c, led->reg_control, 105 LED_CURRENT_MASK, 0); 106 pm860x_set_bits(led->i2c, PM8606_WLED3B, 107 led->blink_mask, 0); 108 led_power_set(chip, led->port, 0); 109 } 110 } 111 led->current_brightness = led->brightness; 112 dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n", 113 led->reg_control, led->brightness); 114 mutex_unlock(&led->lock); 115 } 116 117 static void pm860x_led_set(struct led_classdev *cdev, 118 enum led_brightness value) 119 { 120 struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev); 121 122 data->brightness = value >> 3; 123 schedule_work(&data->work); 124 } 125 126 #ifdef CONFIG_OF 127 static int pm860x_led_dt_init(struct platform_device *pdev, 128 struct pm860x_led *data) 129 { 130 struct device_node *nproot, *np; 131 int iset = 0; 132 133 if (!pdev->dev.parent->of_node) 134 return -ENODEV; 135 nproot = of_get_child_by_name(pdev->dev.parent->of_node, "leds"); 136 if (!nproot) { 137 dev_err(&pdev->dev, "failed to find leds node\n"); 138 return -ENODEV; 139 } 140 for_each_child_of_node(nproot, np) { 141 if (!of_node_cmp(np->name, data->name)) { 142 of_property_read_u32(np, "marvell,88pm860x-iset", 143 &iset); 144 data->iset = PM8606_LED_CURRENT(iset); 145 break; 146 } 147 } 148 of_node_put(nproot); 149 return 0; 150 } 151 #else 152 #define pm860x_led_dt_init(x, y) (-1) 153 #endif 154 155 static int pm860x_led_probe(struct platform_device *pdev) 156 { 157 struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); 158 struct pm860x_led_pdata *pdata = dev_get_platdata(&pdev->dev); 159 struct pm860x_led *data; 160 struct resource *res; 161 int ret = 0; 162 163 data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_led), GFP_KERNEL); 164 if (data == NULL) 165 return -ENOMEM; 166 res = platform_get_resource_byname(pdev, IORESOURCE_REG, "control"); 167 if (!res) { 168 dev_err(&pdev->dev, "No REG resource for control\n"); 169 return -ENXIO; 170 } 171 data->reg_control = res->start; 172 res = platform_get_resource_byname(pdev, IORESOURCE_REG, "blink"); 173 if (!res) { 174 dev_err(&pdev->dev, "No REG resource for blink\n"); 175 return -ENXIO; 176 } 177 data->reg_blink = res->start; 178 memset(data->name, 0, MFD_NAME_SIZE); 179 switch (pdev->id) { 180 case 0: 181 data->blink_mask = LED1_BLINK_EN; 182 sprintf(data->name, "led0-red"); 183 break; 184 case 1: 185 data->blink_mask = LED1_BLINK_EN; 186 sprintf(data->name, "led0-green"); 187 break; 188 case 2: 189 data->blink_mask = LED1_BLINK_EN; 190 sprintf(data->name, "led0-blue"); 191 break; 192 case 3: 193 data->blink_mask = LED2_BLINK_EN; 194 sprintf(data->name, "led1-red"); 195 break; 196 case 4: 197 data->blink_mask = LED2_BLINK_EN; 198 sprintf(data->name, "led1-green"); 199 break; 200 case 5: 201 data->blink_mask = LED2_BLINK_EN; 202 sprintf(data->name, "led1-blue"); 203 break; 204 } 205 platform_set_drvdata(pdev, data); 206 data->chip = chip; 207 data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion; 208 data->port = pdev->id; 209 if (pm860x_led_dt_init(pdev, data)) 210 if (pdata) 211 data->iset = pdata->iset; 212 213 data->current_brightness = 0; 214 data->cdev.name = data->name; 215 data->cdev.brightness_set = pm860x_led_set; 216 mutex_init(&data->lock); 217 INIT_WORK(&data->work, pm860x_led_work); 218 219 ret = led_classdev_register(chip->dev, &data->cdev); 220 if (ret < 0) { 221 dev_err(&pdev->dev, "Failed to register LED: %d\n", ret); 222 return ret; 223 } 224 pm860x_led_set(&data->cdev, 0); 225 return 0; 226 } 227 228 static int pm860x_led_remove(struct platform_device *pdev) 229 { 230 struct pm860x_led *data = platform_get_drvdata(pdev); 231 232 led_classdev_unregister(&data->cdev); 233 234 return 0; 235 } 236 237 static struct platform_driver pm860x_led_driver = { 238 .driver = { 239 .name = "88pm860x-led", 240 }, 241 .probe = pm860x_led_probe, 242 .remove = pm860x_led_remove, 243 }; 244 245 module_platform_driver(pm860x_led_driver); 246 247 MODULE_DESCRIPTION("LED driver for Marvell PM860x"); 248 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); 249 MODULE_LICENSE("GPL"); 250 MODULE_ALIAS("platform:88pm860x-led"); 251