1 /* 2 * Backlight driver for Marvell Semiconductor 88PM8606 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 #include <linux/init.h> 13 #include <linux/kernel.h> 14 #include <linux/platform_device.h> 15 #include <linux/fb.h> 16 #include <linux/i2c.h> 17 #include <linux/backlight.h> 18 #include <linux/mfd/88pm860x.h> 19 #include <linux/slab.h> 20 21 #define MAX_BRIGHTNESS (0xFF) 22 #define MIN_BRIGHTNESS (0) 23 24 #define CURRENT_BITMASK (0x1F << 1) 25 26 struct pm860x_backlight_data { 27 struct pm860x_chip *chip; 28 struct i2c_client *i2c; 29 int current_brightness; 30 int port; 31 int pwm; 32 int iset; 33 }; 34 35 static inline int wled_a(int port) 36 { 37 int ret; 38 39 ret = ((port - PM8606_BACKLIGHT1) << 1) + 2; 40 return ret; 41 } 42 43 static inline int wled_b(int port) 44 { 45 int ret; 46 47 ret = ((port - PM8606_BACKLIGHT1) << 1) + 3; 48 return ret; 49 } 50 51 /* WLED2 & WLED3 share the same IDC */ 52 static inline int wled_idc(int port) 53 { 54 int ret; 55 56 switch (port) { 57 case PM8606_BACKLIGHT1: 58 case PM8606_BACKLIGHT2: 59 ret = ((port - PM8606_BACKLIGHT1) << 1) + 3; 60 break; 61 case PM8606_BACKLIGHT3: 62 default: 63 ret = ((port - PM8606_BACKLIGHT2) << 1) + 3; 64 break; 65 } 66 return ret; 67 } 68 69 static int pm860x_backlight_set(struct backlight_device *bl, int brightness) 70 { 71 struct pm860x_backlight_data *data = bl_get_data(bl); 72 struct pm860x_chip *chip = data->chip; 73 unsigned char value; 74 int ret; 75 76 if (brightness > MAX_BRIGHTNESS) 77 value = MAX_BRIGHTNESS; 78 else 79 value = brightness; 80 81 ret = pm860x_reg_write(data->i2c, wled_a(data->port), value); 82 if (ret < 0) 83 goto out; 84 85 if ((data->current_brightness == 0) && brightness) { 86 if (data->iset) { 87 ret = pm860x_set_bits(data->i2c, wled_idc(data->port), 88 CURRENT_BITMASK, data->iset); 89 if (ret < 0) 90 goto out; 91 } 92 if (data->pwm) { 93 ret = pm860x_set_bits(data->i2c, PM8606_PWM, 94 PM8606_PWM_FREQ_MASK, data->pwm); 95 if (ret < 0) 96 goto out; 97 } 98 if (brightness == MAX_BRIGHTNESS) { 99 /* set WLED_ON bit as 100% */ 100 ret = pm860x_set_bits(data->i2c, wled_b(data->port), 101 PM8606_WLED_ON, PM8606_WLED_ON); 102 } 103 } else { 104 if (brightness == MAX_BRIGHTNESS) { 105 /* set WLED_ON bit as 100% */ 106 ret = pm860x_set_bits(data->i2c, wled_b(data->port), 107 PM8606_WLED_ON, PM8606_WLED_ON); 108 } else { 109 /* clear WLED_ON bit since it's not 100% */ 110 ret = pm860x_set_bits(data->i2c, wled_b(data->port), 111 PM8606_WLED_ON, 0); 112 } 113 } 114 if (ret < 0) 115 goto out; 116 117 dev_dbg(chip->dev, "set brightness %d\n", value); 118 data->current_brightness = value; 119 return 0; 120 out: 121 dev_dbg(chip->dev, "set brightness %d failure with return " 122 "value:%d\n", value, ret); 123 return ret; 124 } 125 126 static int pm860x_backlight_update_status(struct backlight_device *bl) 127 { 128 int brightness = bl->props.brightness; 129 130 if (bl->props.power != FB_BLANK_UNBLANK) 131 brightness = 0; 132 133 if (bl->props.fb_blank != FB_BLANK_UNBLANK) 134 brightness = 0; 135 136 if (bl->props.state & BL_CORE_SUSPENDED) 137 brightness = 0; 138 139 return pm860x_backlight_set(bl, brightness); 140 } 141 142 static int pm860x_backlight_get_brightness(struct backlight_device *bl) 143 { 144 struct pm860x_backlight_data *data = bl_get_data(bl); 145 struct pm860x_chip *chip = data->chip; 146 int ret; 147 148 ret = pm860x_reg_read(data->i2c, wled_a(data->port)); 149 if (ret < 0) 150 goto out; 151 data->current_brightness = ret; 152 dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness); 153 return data->current_brightness; 154 out: 155 return -EINVAL; 156 } 157 158 static const struct backlight_ops pm860x_backlight_ops = { 159 .options = BL_CORE_SUSPENDRESUME, 160 .update_status = pm860x_backlight_update_status, 161 .get_brightness = pm860x_backlight_get_brightness, 162 }; 163 164 static int __check_device(struct pm860x_backlight_pdata *pdata, char *name) 165 { 166 struct pm860x_backlight_pdata *p = pdata; 167 int ret = -EINVAL; 168 169 while (p && p->id) { 170 if ((p->id != PM8606_ID_BACKLIGHT) || (p->flags < 0)) 171 break; 172 173 if (!strncmp(name, pm860x_backlight_name[p->flags], 174 MFD_NAME_SIZE)) { 175 ret = (int)p->flags; 176 break; 177 } 178 p++; 179 } 180 return ret; 181 } 182 183 static int pm860x_backlight_probe(struct platform_device *pdev) 184 { 185 struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); 186 struct pm860x_platform_data *pm860x_pdata; 187 struct pm860x_backlight_pdata *pdata = NULL; 188 struct pm860x_backlight_data *data; 189 struct backlight_device *bl; 190 struct resource *res; 191 struct backlight_properties props; 192 unsigned char value; 193 char name[MFD_NAME_SIZE]; 194 int ret; 195 196 res = platform_get_resource(pdev, IORESOURCE_IO, 0); 197 if (res == NULL) { 198 dev_err(&pdev->dev, "No I/O resource!\n"); 199 return -EINVAL; 200 } 201 202 if (pdev->dev.parent->platform_data) { 203 pm860x_pdata = pdev->dev.parent->platform_data; 204 pdata = pm860x_pdata->backlight; 205 } 206 if (pdata == NULL) { 207 dev_err(&pdev->dev, "platform data isn't assigned to " 208 "backlight\n"); 209 return -EINVAL; 210 } 211 212 data = kzalloc(sizeof(struct pm860x_backlight_data), GFP_KERNEL); 213 if (data == NULL) 214 return -ENOMEM; 215 strncpy(name, res->name, MFD_NAME_SIZE); 216 data->chip = chip; 217 data->i2c = (chip->id == CHIP_PM8606) ? chip->client \ 218 : chip->companion; 219 data->current_brightness = MAX_BRIGHTNESS; 220 data->pwm = pdata->pwm; 221 data->iset = pdata->iset; 222 data->port = __check_device(pdata, name); 223 if (data->port < 0) { 224 dev_err(&pdev->dev, "wrong platform data is assigned"); 225 kfree(data); 226 return -EINVAL; 227 } 228 229 memset(&props, 0, sizeof(struct backlight_properties)); 230 props.max_brightness = MAX_BRIGHTNESS; 231 bl = backlight_device_register(name, &pdev->dev, data, 232 &pm860x_backlight_ops, &props); 233 if (IS_ERR(bl)) { 234 dev_err(&pdev->dev, "failed to register backlight\n"); 235 kfree(data); 236 return PTR_ERR(bl); 237 } 238 bl->props.brightness = MAX_BRIGHTNESS; 239 240 platform_set_drvdata(pdev, bl); 241 242 /* Enable reference VSYS */ 243 ret = pm860x_reg_read(data->i2c, PM8606_VSYS); 244 if (ret < 0) 245 goto out; 246 if ((ret & PM8606_VSYS_EN) == 0) { 247 value = ret | PM8606_VSYS_EN; 248 ret = pm860x_reg_write(data->i2c, PM8606_VSYS, value); 249 if (ret < 0) 250 goto out; 251 } 252 /* Enable reference OSC */ 253 ret = pm860x_reg_read(data->i2c, PM8606_MISC); 254 if (ret < 0) 255 goto out; 256 if ((ret & PM8606_MISC_OSC_EN) == 0) { 257 value = ret | PM8606_MISC_OSC_EN; 258 ret = pm860x_reg_write(data->i2c, PM8606_MISC, value); 259 if (ret < 0) 260 goto out; 261 } 262 /* read current backlight */ 263 ret = pm860x_backlight_get_brightness(bl); 264 if (ret < 0) 265 goto out; 266 267 backlight_update_status(bl); 268 return 0; 269 out: 270 backlight_device_unregister(bl); 271 kfree(data); 272 return ret; 273 } 274 275 static int pm860x_backlight_remove(struct platform_device *pdev) 276 { 277 struct backlight_device *bl = platform_get_drvdata(pdev); 278 struct pm860x_backlight_data *data = bl_get_data(bl); 279 280 backlight_device_unregister(bl); 281 kfree(data); 282 return 0; 283 } 284 285 static struct platform_driver pm860x_backlight_driver = { 286 .driver = { 287 .name = "88pm860x-backlight", 288 .owner = THIS_MODULE, 289 }, 290 .probe = pm860x_backlight_probe, 291 .remove = pm860x_backlight_remove, 292 }; 293 294 static int __init pm860x_backlight_init(void) 295 { 296 return platform_driver_register(&pm860x_backlight_driver); 297 } 298 module_init(pm860x_backlight_init); 299 300 static void __exit pm860x_backlight_exit(void) 301 { 302 platform_driver_unregister(&pm860x_backlight_driver); 303 } 304 module_exit(pm860x_backlight_exit); 305 306 MODULE_DESCRIPTION("Backlight Driver for Marvell Semiconductor 88PM8606"); 307 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); 308 MODULE_LICENSE("GPL"); 309 MODULE_ALIAS("platform:88pm860x-backlight"); 310