/* * Backlight driver for Marvell Semiconductor 88PM8606 * * Copyright (C) 2009 Marvell International Ltd. * Haojian Zhuang * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #define MAX_BRIGHTNESS (0xFF) #define MIN_BRIGHTNESS (0) #define CURRENT_MASK (0x1F << 1) struct pm860x_backlight_data { struct pm860x_chip *chip; struct i2c_client *i2c; int current_brightness; int port; int pwm; int iset; }; static inline int wled_a(int port) { int ret; ret = ((port - PM8606_BACKLIGHT1) << 1) + 2; return ret; } static inline int wled_b(int port) { int ret; ret = ((port - PM8606_BACKLIGHT1) << 1) + 3; return ret; } /* WLED2 & WLED3 share the same IDC */ static inline int wled_idc(int port) { int ret; switch (port) { case PM8606_BACKLIGHT1: case PM8606_BACKLIGHT2: ret = ((port - PM8606_BACKLIGHT1) << 1) + 3; break; case PM8606_BACKLIGHT3: default: ret = ((port - PM8606_BACKLIGHT2) << 1) + 3; break; } return ret; } static int pm860x_backlight_set(struct backlight_device *bl, int brightness) { struct pm860x_backlight_data *data = bl_get_data(bl); struct pm860x_chip *chip = data->chip; unsigned char value; int ret; if (brightness > MAX_BRIGHTNESS) value = MAX_BRIGHTNESS; else value = brightness; ret = pm860x_reg_write(data->i2c, wled_a(data->port), value); if (ret < 0) goto out; if ((data->current_brightness == 0) && brightness) { if (data->iset) { ret = pm860x_set_bits(data->i2c, wled_idc(data->port), CURRENT_MASK, data->iset); if (ret < 0) goto out; } if (data->pwm) { ret = pm860x_set_bits(data->i2c, PM8606_PWM, PM8606_PWM_FREQ_MASK, data->pwm); if (ret < 0) goto out; } if (brightness == MAX_BRIGHTNESS) { /* set WLED_ON bit as 100% */ ret = pm860x_set_bits(data->i2c, wled_b(data->port), PM8606_WLED_ON, PM8606_WLED_ON); } } else { if (brightness == MAX_BRIGHTNESS) { /* set WLED_ON bit as 100% */ ret = pm860x_set_bits(data->i2c, wled_b(data->port), PM8606_WLED_ON, PM8606_WLED_ON); } else { /* clear WLED_ON bit since it's not 100% */ ret = pm860x_set_bits(data->i2c, wled_b(data->port), PM8606_WLED_ON, 0); } } if (ret < 0) goto out; dev_dbg(chip->dev, "set brightness %d\n", value); data->current_brightness = value; return 0; out: dev_dbg(chip->dev, "set brightness %d failure with return " "value:%d\n", value, ret); return ret; } static int pm860x_backlight_update_status(struct backlight_device *bl) { int brightness = bl->props.brightness; if (bl->props.power != FB_BLANK_UNBLANK) brightness = 0; if (bl->props.fb_blank != FB_BLANK_UNBLANK) brightness = 0; if (bl->props.state & BL_CORE_SUSPENDED) brightness = 0; return pm860x_backlight_set(bl, brightness); } static int pm860x_backlight_get_brightness(struct backlight_device *bl) { struct pm860x_backlight_data *data = bl_get_data(bl); struct pm860x_chip *chip = data->chip; int ret; ret = pm860x_reg_read(data->i2c, wled_a(data->port)); if (ret < 0) goto out; data->current_brightness = ret; dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness); return data->current_brightness; out: return -EINVAL; } static struct backlight_ops pm860x_backlight_ops = { .options = BL_CORE_SUSPENDRESUME, .update_status = pm860x_backlight_update_status, .get_brightness = pm860x_backlight_get_brightness, }; static int __check_device(struct pm860x_backlight_pdata *pdata, char *name) { struct pm860x_backlight_pdata *p = pdata; int ret = -EINVAL; while (p && p->id) { if ((p->id != PM8606_ID_BACKLIGHT) || (p->flags < 0)) break; if (!strncmp(name, pm860x_backlight_name[p->flags], MFD_NAME_SIZE)) { ret = (int)p->flags; break; } p++; } return ret; } static int pm860x_backlight_probe(struct platform_device *pdev) { struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); struct pm860x_platform_data *pm860x_pdata; struct pm860x_backlight_pdata *pdata = NULL; struct pm860x_backlight_data *data; struct backlight_device *bl; struct resource *res; struct backlight_properties props; unsigned char value; char name[MFD_NAME_SIZE]; int ret; res = platform_get_resource(pdev, IORESOURCE_IO, 0); if (res == NULL) { dev_err(&pdev->dev, "No I/O resource!\n"); return -EINVAL; } if (pdev->dev.parent->platform_data) { pm860x_pdata = pdev->dev.parent->platform_data; pdata = pm860x_pdata->backlight; } if (pdata == NULL) { dev_err(&pdev->dev, "platform data isn't assigned to " "backlight\n"); return -EINVAL; } data = kzalloc(sizeof(struct pm860x_backlight_data), GFP_KERNEL); if (data == NULL) return -ENOMEM; strncpy(name, res->name, MFD_NAME_SIZE); data->chip = chip; data->i2c = (chip->id == CHIP_PM8606) ? chip->client \ : chip->companion; data->current_brightness = MAX_BRIGHTNESS; data->pwm = pdata->pwm; data->iset = pdata->iset; data->port = __check_device(pdata, name); if (data->port < 0) { dev_err(&pdev->dev, "wrong platform data is assigned"); kfree(data); return -EINVAL; } memset(&props, 0, sizeof(struct backlight_properties)); props.max_brightness = MAX_BRIGHTNESS; bl = backlight_device_register(name, &pdev->dev, data, &pm860x_backlight_ops, &props); if (IS_ERR(bl)) { dev_err(&pdev->dev, "failed to register backlight\n"); kfree(data); return PTR_ERR(bl); } bl->props.brightness = MAX_BRIGHTNESS; platform_set_drvdata(pdev, bl); /* Enable reference VSYS */ ret = pm860x_reg_read(data->i2c, PM8606_VSYS); if (ret < 0) goto out; if ((ret & PM8606_VSYS_EN) == 0) { value = ret | PM8606_VSYS_EN; ret = pm860x_reg_write(data->i2c, PM8606_VSYS, value); if (ret < 0) goto out; } /* Enable reference OSC */ ret = pm860x_reg_read(data->i2c, PM8606_MISC); if (ret < 0) goto out; if ((ret & PM8606_MISC_OSC_EN) == 0) { value = ret | PM8606_MISC_OSC_EN; ret = pm860x_reg_write(data->i2c, PM8606_MISC, value); if (ret < 0) goto out; } /* read current backlight */ ret = pm860x_backlight_get_brightness(bl); if (ret < 0) goto out; backlight_update_status(bl); return 0; out: kfree(data); return ret; } static int pm860x_backlight_remove(struct platform_device *pdev) { struct backlight_device *bl = platform_get_drvdata(pdev); struct pm860x_backlight_data *data = bl_get_data(bl); backlight_device_unregister(bl); kfree(data); return 0; } static struct platform_driver pm860x_backlight_driver = { .driver = { .name = "88pm860x-backlight", .owner = THIS_MODULE, }, .probe = pm860x_backlight_probe, .remove = pm860x_backlight_remove, }; static int __init pm860x_backlight_init(void) { return platform_driver_register(&pm860x_backlight_driver); } module_init(pm860x_backlight_init); static void __exit pm860x_backlight_exit(void) { platform_driver_unregister(&pm860x_backlight_driver); } module_exit(pm860x_backlight_exit); MODULE_DESCRIPTION("Backlight Driver for Marvell Semiconductor 88PM8606"); MODULE_AUTHOR("Haojian Zhuang "); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:88pm860x-backlight");