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 nproot = of_node_get(pdev->dev.parent->of_node); 134 if (!nproot) 135 return -ENODEV; 136 nproot = of_find_node_by_name(nproot, "leds"); 137 if (!nproot) { 138 dev_err(&pdev->dev, "failed to find leds node\n"); 139 return -ENODEV; 140 } 141 for_each_child_of_node(nproot, np) { 142 if (!of_node_cmp(np->name, data->name)) { 143 of_property_read_u32(np, "marvell,88pm860x-iset", 144 &iset); 145 data->iset = PM8606_LED_CURRENT(iset); 146 break; 147 } 148 } 149 of_node_put(nproot); 150 return 0; 151 } 152 #else 153 #define pm860x_led_dt_init(x, y) (-1) 154 #endif 155 156 static int pm860x_led_probe(struct platform_device *pdev) 157 { 158 struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); 159 struct pm860x_led_pdata *pdata = dev_get_platdata(&pdev->dev); 160 struct pm860x_led *data; 161 struct resource *res; 162 int ret = 0; 163 164 data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_led), GFP_KERNEL); 165 if (data == NULL) 166 return -ENOMEM; 167 res = platform_get_resource_byname(pdev, IORESOURCE_REG, "control"); 168 if (!res) { 169 dev_err(&pdev->dev, "No REG resource for control\n"); 170 return -ENXIO; 171 } 172 data->reg_control = res->start; 173 res = platform_get_resource_byname(pdev, IORESOURCE_REG, "blink"); 174 if (!res) { 175 dev_err(&pdev->dev, "No REG resource for blink\n"); 176 return -ENXIO; 177 } 178 data->reg_blink = res->start; 179 memset(data->name, 0, MFD_NAME_SIZE); 180 switch (pdev->id) { 181 case 0: 182 data->blink_mask = LED1_BLINK_EN; 183 sprintf(data->name, "led0-red"); 184 break; 185 case 1: 186 data->blink_mask = LED1_BLINK_EN; 187 sprintf(data->name, "led0-green"); 188 break; 189 case 2: 190 data->blink_mask = LED1_BLINK_EN; 191 sprintf(data->name, "led0-blue"); 192 break; 193 case 3: 194 data->blink_mask = LED2_BLINK_EN; 195 sprintf(data->name, "led1-red"); 196 break; 197 case 4: 198 data->blink_mask = LED2_BLINK_EN; 199 sprintf(data->name, "led1-green"); 200 break; 201 case 5: 202 data->blink_mask = LED2_BLINK_EN; 203 sprintf(data->name, "led1-blue"); 204 break; 205 } 206 platform_set_drvdata(pdev, data); 207 data->chip = chip; 208 data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion; 209 data->port = pdev->id; 210 if (pm860x_led_dt_init(pdev, data)) 211 if (pdata) 212 data->iset = pdata->iset; 213 214 data->current_brightness = 0; 215 data->cdev.name = data->name; 216 data->cdev.brightness_set = pm860x_led_set; 217 mutex_init(&data->lock); 218 INIT_WORK(&data->work, pm860x_led_work); 219 220 ret = led_classdev_register(chip->dev, &data->cdev); 221 if (ret < 0) { 222 dev_err(&pdev->dev, "Failed to register LED: %d\n", ret); 223 return ret; 224 } 225 pm860x_led_set(&data->cdev, 0); 226 return 0; 227 } 228 229 static int pm860x_led_remove(struct platform_device *pdev) 230 { 231 struct pm860x_led *data = platform_get_drvdata(pdev); 232 233 led_classdev_unregister(&data->cdev); 234 235 return 0; 236 } 237 238 static struct platform_driver pm860x_led_driver = { 239 .driver = { 240 .name = "88pm860x-led", 241 .owner = THIS_MODULE, 242 }, 243 .probe = pm860x_led_probe, 244 .remove = pm860x_led_remove, 245 }; 246 247 module_platform_driver(pm860x_led_driver); 248 249 MODULE_DESCRIPTION("LED driver for Marvell PM860x"); 250 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); 251 MODULE_LICENSE("GPL"); 252 MODULE_ALIAS("platform:88pm860x-led"); 253