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/slab.h> 16 #include <linux/fb.h> 17 #include <linux/i2c.h> 18 #include <linux/backlight.h> 19 #include <linux/mfd/88pm860x.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 pm860x_backlight_probe(struct platform_device *pdev) 165 { 166 struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); 167 struct pm860x_backlight_pdata *pdata = NULL; 168 struct pm860x_backlight_data *data; 169 struct backlight_device *bl; 170 struct resource *res; 171 struct backlight_properties props; 172 unsigned char value; 173 char name[MFD_NAME_SIZE]; 174 int ret; 175 176 res = platform_get_resource(pdev, IORESOURCE_IO, 0); 177 if (res == NULL) { 178 dev_err(&pdev->dev, "No I/O resource!\n"); 179 return -EINVAL; 180 } 181 182 pdata = pdev->dev.platform_data; 183 if (pdata == NULL) { 184 dev_err(&pdev->dev, "platform data isn't assigned to " 185 "backlight\n"); 186 return -EINVAL; 187 } 188 189 data = kzalloc(sizeof(struct pm860x_backlight_data), GFP_KERNEL); 190 if (data == NULL) 191 return -ENOMEM; 192 strncpy(name, res->name, MFD_NAME_SIZE); 193 data->chip = chip; 194 data->i2c = (chip->id == CHIP_PM8606) ? chip->client \ 195 : chip->companion; 196 data->current_brightness = MAX_BRIGHTNESS; 197 data->pwm = pdata->pwm; 198 data->iset = pdata->iset; 199 data->port = pdata->flags; 200 if (data->port < 0) { 201 dev_err(&pdev->dev, "wrong platform data is assigned"); 202 kfree(data); 203 return -EINVAL; 204 } 205 206 memset(&props, 0, sizeof(struct backlight_properties)); 207 props.type = BACKLIGHT_RAW; 208 props.max_brightness = MAX_BRIGHTNESS; 209 bl = backlight_device_register(name, &pdev->dev, data, 210 &pm860x_backlight_ops, &props); 211 if (IS_ERR(bl)) { 212 dev_err(&pdev->dev, "failed to register backlight\n"); 213 kfree(data); 214 return PTR_ERR(bl); 215 } 216 bl->props.brightness = MAX_BRIGHTNESS; 217 218 platform_set_drvdata(pdev, bl); 219 220 /* Enable reference VSYS */ 221 ret = pm860x_reg_read(data->i2c, PM8606_VSYS); 222 if (ret < 0) 223 goto out; 224 if ((ret & PM8606_VSYS_EN) == 0) { 225 value = ret | PM8606_VSYS_EN; 226 ret = pm860x_reg_write(data->i2c, PM8606_VSYS, value); 227 if (ret < 0) 228 goto out; 229 } 230 /* Enable reference OSC */ 231 ret = pm860x_reg_read(data->i2c, PM8606_MISC); 232 if (ret < 0) 233 goto out; 234 if ((ret & PM8606_MISC_OSC_EN) == 0) { 235 value = ret | PM8606_MISC_OSC_EN; 236 ret = pm860x_reg_write(data->i2c, PM8606_MISC, value); 237 if (ret < 0) 238 goto out; 239 } 240 /* read current backlight */ 241 ret = pm860x_backlight_get_brightness(bl); 242 if (ret < 0) 243 goto out; 244 245 backlight_update_status(bl); 246 return 0; 247 out: 248 backlight_device_unregister(bl); 249 kfree(data); 250 return ret; 251 } 252 253 static int pm860x_backlight_remove(struct platform_device *pdev) 254 { 255 struct backlight_device *bl = platform_get_drvdata(pdev); 256 struct pm860x_backlight_data *data = bl_get_data(bl); 257 258 backlight_device_unregister(bl); 259 kfree(data); 260 return 0; 261 } 262 263 static struct platform_driver pm860x_backlight_driver = { 264 .driver = { 265 .name = "88pm860x-backlight", 266 .owner = THIS_MODULE, 267 }, 268 .probe = pm860x_backlight_probe, 269 .remove = pm860x_backlight_remove, 270 }; 271 272 static int __init pm860x_backlight_init(void) 273 { 274 return platform_driver_register(&pm860x_backlight_driver); 275 } 276 module_init(pm860x_backlight_init); 277 278 static void __exit pm860x_backlight_exit(void) 279 { 280 platform_driver_unregister(&pm860x_backlight_driver); 281 } 282 module_exit(pm860x_backlight_exit); 283 284 MODULE_DESCRIPTION("Backlight Driver for Marvell Semiconductor 88PM8606"); 285 MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); 286 MODULE_LICENSE("GPL"); 287 MODULE_ALIAS("platform:88pm860x-backlight"); 288