1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2d07e8bf8SHaojian Zhuang /* 3d07e8bf8SHaojian Zhuang * Backlight driver for Marvell Semiconductor 88PM8606 4d07e8bf8SHaojian Zhuang * 5d07e8bf8SHaojian Zhuang * Copyright (C) 2009 Marvell International Ltd. 6d07e8bf8SHaojian Zhuang * Haojian Zhuang <haojian.zhuang@marvell.com> 7d07e8bf8SHaojian Zhuang */ 8d07e8bf8SHaojian Zhuang 9d07e8bf8SHaojian Zhuang #include <linux/init.h> 10d07e8bf8SHaojian Zhuang #include <linux/kernel.h> 112e57d567SHaojian Zhuang #include <linux/of.h> 12d07e8bf8SHaojian Zhuang #include <linux/platform_device.h> 13adb70483SHaojian Zhuang #include <linux/slab.h> 14d07e8bf8SHaojian Zhuang #include <linux/fb.h> 15d07e8bf8SHaojian Zhuang #include <linux/i2c.h> 16d07e8bf8SHaojian Zhuang #include <linux/backlight.h> 17d07e8bf8SHaojian Zhuang #include <linux/mfd/88pm860x.h> 18355b200bSPaul Gortmaker #include <linux/module.h> 19d07e8bf8SHaojian Zhuang 20d07e8bf8SHaojian Zhuang #define MAX_BRIGHTNESS (0xFF) 21d07e8bf8SHaojian Zhuang #define MIN_BRIGHTNESS (0) 22d07e8bf8SHaojian Zhuang 232550326aSRandy Dunlap #define CURRENT_BITMASK (0x1F << 1) 24d07e8bf8SHaojian Zhuang 25d07e8bf8SHaojian Zhuang struct pm860x_backlight_data { 26d07e8bf8SHaojian Zhuang struct pm860x_chip *chip; 27d07e8bf8SHaojian Zhuang struct i2c_client *i2c; 28d07e8bf8SHaojian Zhuang int current_brightness; 29d07e8bf8SHaojian Zhuang int port; 30d07e8bf8SHaojian Zhuang int pwm; 31d07e8bf8SHaojian Zhuang int iset; 32a6ccdcd9SHaojian Zhuang int reg_duty_cycle; 33a6ccdcd9SHaojian Zhuang int reg_always_on; 34a6ccdcd9SHaojian Zhuang int reg_current; 35d07e8bf8SHaojian Zhuang }; 36d07e8bf8SHaojian Zhuang 371efc1581SJett.Zhou static int backlight_power_set(struct pm860x_chip *chip, int port, 381efc1581SJett.Zhou int on) 391efc1581SJett.Zhou { 401efc1581SJett.Zhou int ret = -EINVAL; 411efc1581SJett.Zhou 421efc1581SJett.Zhou switch (port) { 43a6ccdcd9SHaojian Zhuang case 0: 441efc1581SJett.Zhou ret = on ? pm8606_osc_enable(chip, WLED1_DUTY) : 451efc1581SJett.Zhou pm8606_osc_disable(chip, WLED1_DUTY); 461efc1581SJett.Zhou break; 47a6ccdcd9SHaojian Zhuang case 1: 481efc1581SJett.Zhou ret = on ? pm8606_osc_enable(chip, WLED2_DUTY) : 491efc1581SJett.Zhou pm8606_osc_disable(chip, WLED2_DUTY); 501efc1581SJett.Zhou break; 51a6ccdcd9SHaojian Zhuang case 2: 521efc1581SJett.Zhou ret = on ? pm8606_osc_enable(chip, WLED3_DUTY) : 531efc1581SJett.Zhou pm8606_osc_disable(chip, WLED3_DUTY); 541efc1581SJett.Zhou break; 551efc1581SJett.Zhou } 561efc1581SJett.Zhou return ret; 571efc1581SJett.Zhou } 581efc1581SJett.Zhou 59d07e8bf8SHaojian Zhuang static int pm860x_backlight_set(struct backlight_device *bl, int brightness) 60d07e8bf8SHaojian Zhuang { 61d07e8bf8SHaojian Zhuang struct pm860x_backlight_data *data = bl_get_data(bl); 62d07e8bf8SHaojian Zhuang struct pm860x_chip *chip = data->chip; 63d07e8bf8SHaojian Zhuang unsigned char value; 64d07e8bf8SHaojian Zhuang int ret; 65d07e8bf8SHaojian Zhuang 66d07e8bf8SHaojian Zhuang if (brightness > MAX_BRIGHTNESS) 67d07e8bf8SHaojian Zhuang value = MAX_BRIGHTNESS; 68d07e8bf8SHaojian Zhuang else 69d07e8bf8SHaojian Zhuang value = brightness; 70d07e8bf8SHaojian Zhuang 711efc1581SJett.Zhou if (brightness) 721efc1581SJett.Zhou backlight_power_set(chip, data->port, 1); 731efc1581SJett.Zhou 74a6ccdcd9SHaojian Zhuang ret = pm860x_reg_write(data->i2c, data->reg_duty_cycle, value); 75d07e8bf8SHaojian Zhuang if (ret < 0) 76d07e8bf8SHaojian Zhuang goto out; 77d07e8bf8SHaojian Zhuang 78d07e8bf8SHaojian Zhuang if ((data->current_brightness == 0) && brightness) { 79d07e8bf8SHaojian Zhuang if (data->iset) { 80a6ccdcd9SHaojian Zhuang ret = pm860x_set_bits(data->i2c, data->reg_current, 812550326aSRandy Dunlap CURRENT_BITMASK, data->iset); 82d07e8bf8SHaojian Zhuang if (ret < 0) 83d07e8bf8SHaojian Zhuang goto out; 84d07e8bf8SHaojian Zhuang } 85d07e8bf8SHaojian Zhuang if (data->pwm) { 86d07e8bf8SHaojian Zhuang ret = pm860x_set_bits(data->i2c, PM8606_PWM, 87d07e8bf8SHaojian Zhuang PM8606_PWM_FREQ_MASK, data->pwm); 88d07e8bf8SHaojian Zhuang if (ret < 0) 89d07e8bf8SHaojian Zhuang goto out; 90d07e8bf8SHaojian Zhuang } 91d07e8bf8SHaojian Zhuang if (brightness == MAX_BRIGHTNESS) { 92d07e8bf8SHaojian Zhuang /* set WLED_ON bit as 100% */ 93a6ccdcd9SHaojian Zhuang ret = pm860x_set_bits(data->i2c, data->reg_always_on, 94d07e8bf8SHaojian Zhuang PM8606_WLED_ON, PM8606_WLED_ON); 95d07e8bf8SHaojian Zhuang } 96d07e8bf8SHaojian Zhuang } else { 97d07e8bf8SHaojian Zhuang if (brightness == MAX_BRIGHTNESS) { 98d07e8bf8SHaojian Zhuang /* set WLED_ON bit as 100% */ 99a6ccdcd9SHaojian Zhuang ret = pm860x_set_bits(data->i2c, data->reg_always_on, 100d07e8bf8SHaojian Zhuang PM8606_WLED_ON, PM8606_WLED_ON); 101d07e8bf8SHaojian Zhuang } else { 102d07e8bf8SHaojian Zhuang /* clear WLED_ON bit since it's not 100% */ 103a6ccdcd9SHaojian Zhuang ret = pm860x_set_bits(data->i2c, data->reg_always_on, 104d07e8bf8SHaojian Zhuang PM8606_WLED_ON, 0); 105d07e8bf8SHaojian Zhuang } 106d07e8bf8SHaojian Zhuang } 107d07e8bf8SHaojian Zhuang if (ret < 0) 108d07e8bf8SHaojian Zhuang goto out; 109d07e8bf8SHaojian Zhuang 1101efc1581SJett.Zhou if (brightness == 0) 1111efc1581SJett.Zhou backlight_power_set(chip, data->port, 0); 1121efc1581SJett.Zhou 113d07e8bf8SHaojian Zhuang dev_dbg(chip->dev, "set brightness %d\n", value); 114d07e8bf8SHaojian Zhuang data->current_brightness = value; 115d07e8bf8SHaojian Zhuang return 0; 116d07e8bf8SHaojian Zhuang out: 11720c5a932SJingoo Han dev_dbg(chip->dev, "set brightness %d failure with return value: %d\n", 11820c5a932SJingoo Han value, ret); 119d07e8bf8SHaojian Zhuang return ret; 120d07e8bf8SHaojian Zhuang } 121d07e8bf8SHaojian Zhuang 122d07e8bf8SHaojian Zhuang static int pm860x_backlight_update_status(struct backlight_device *bl) 123d07e8bf8SHaojian Zhuang { 124*51d53e5bSSam Ravnborg return pm860x_backlight_set(bl, backlight_get_brightness(bl)); 125d07e8bf8SHaojian Zhuang } 126d07e8bf8SHaojian Zhuang 127d07e8bf8SHaojian Zhuang static int pm860x_backlight_get_brightness(struct backlight_device *bl) 128d07e8bf8SHaojian Zhuang { 129d07e8bf8SHaojian Zhuang struct pm860x_backlight_data *data = bl_get_data(bl); 130d07e8bf8SHaojian Zhuang struct pm860x_chip *chip = data->chip; 131d07e8bf8SHaojian Zhuang int ret; 132d07e8bf8SHaojian Zhuang 133a6ccdcd9SHaojian Zhuang ret = pm860x_reg_read(data->i2c, data->reg_duty_cycle); 134d07e8bf8SHaojian Zhuang if (ret < 0) 135d07e8bf8SHaojian Zhuang goto out; 136d07e8bf8SHaojian Zhuang data->current_brightness = ret; 137d07e8bf8SHaojian Zhuang dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness); 138d07e8bf8SHaojian Zhuang return data->current_brightness; 139d07e8bf8SHaojian Zhuang out: 140d07e8bf8SHaojian Zhuang return -EINVAL; 141d07e8bf8SHaojian Zhuang } 142d07e8bf8SHaojian Zhuang 143acc2472eSLionel Debroux static const struct backlight_ops pm860x_backlight_ops = { 144d07e8bf8SHaojian Zhuang .options = BL_CORE_SUSPENDRESUME, 145d07e8bf8SHaojian Zhuang .update_status = pm860x_backlight_update_status, 146d07e8bf8SHaojian Zhuang .get_brightness = pm860x_backlight_get_brightness, 147d07e8bf8SHaojian Zhuang }; 148d07e8bf8SHaojian Zhuang 1492e57d567SHaojian Zhuang #ifdef CONFIG_OF 1502e57d567SHaojian Zhuang static int pm860x_backlight_dt_init(struct platform_device *pdev, 1512e57d567SHaojian Zhuang struct pm860x_backlight_data *data, 1522e57d567SHaojian Zhuang char *name) 1532e57d567SHaojian Zhuang { 154c84c3833SAxel Lin struct device_node *nproot, *np; 1552e57d567SHaojian Zhuang int iset = 0; 156c84c3833SAxel Lin 157c6f77bc2SGeert Uytterhoeven nproot = of_get_child_by_name(pdev->dev.parent->of_node, "backlights"); 1582e57d567SHaojian Zhuang if (!nproot) { 1592e57d567SHaojian Zhuang dev_err(&pdev->dev, "failed to find backlights node\n"); 1602e57d567SHaojian Zhuang return -ENODEV; 1612e57d567SHaojian Zhuang } 1622e57d567SHaojian Zhuang for_each_child_of_node(nproot, np) { 1633cee7a7dSRob Herring if (of_node_name_eq(np, name)) { 1642e57d567SHaojian Zhuang of_property_read_u32(np, "marvell,88pm860x-iset", 1652e57d567SHaojian Zhuang &iset); 1662e57d567SHaojian Zhuang data->iset = PM8606_WLED_CURRENT(iset); 1672e57d567SHaojian Zhuang of_property_read_u32(np, "marvell,88pm860x-pwm", 1682e57d567SHaojian Zhuang &data->pwm); 169f85de2d9SJulia Lawall of_node_put(np); 1702e57d567SHaojian Zhuang break; 1712e57d567SHaojian Zhuang } 1722e57d567SHaojian Zhuang } 173c84c3833SAxel Lin of_node_put(nproot); 1742e57d567SHaojian Zhuang return 0; 1752e57d567SHaojian Zhuang } 1762e57d567SHaojian Zhuang #else 1772e57d567SHaojian Zhuang #define pm860x_backlight_dt_init(x, y, z) (-1) 1782e57d567SHaojian Zhuang #endif 1792e57d567SHaojian Zhuang 180d07e8bf8SHaojian Zhuang static int pm860x_backlight_probe(struct platform_device *pdev) 181d07e8bf8SHaojian Zhuang { 182d07e8bf8SHaojian Zhuang struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); 183c512794cSJingoo Han struct pm860x_backlight_pdata *pdata = dev_get_platdata(&pdev->dev); 184d07e8bf8SHaojian Zhuang struct pm860x_backlight_data *data; 185d07e8bf8SHaojian Zhuang struct backlight_device *bl; 186d07e8bf8SHaojian Zhuang struct resource *res; 187a19a6ee6SMatthew Garrett struct backlight_properties props; 188d07e8bf8SHaojian Zhuang char name[MFD_NAME_SIZE]; 189a6ccdcd9SHaojian Zhuang int ret = 0; 190d07e8bf8SHaojian Zhuang 191ce969228SJulia Lawall data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_backlight_data), 192ce969228SJulia Lawall GFP_KERNEL); 193d07e8bf8SHaojian Zhuang if (data == NULL) 194d07e8bf8SHaojian Zhuang return -ENOMEM; 195a6ccdcd9SHaojian Zhuang res = platform_get_resource_byname(pdev, IORESOURCE_REG, "duty cycle"); 196a6ccdcd9SHaojian Zhuang if (!res) { 197a6ccdcd9SHaojian Zhuang dev_err(&pdev->dev, "No REG resource for duty cycle\n"); 1983a1f9462SJingoo Han return -ENXIO; 199a6ccdcd9SHaojian Zhuang } 200a6ccdcd9SHaojian Zhuang data->reg_duty_cycle = res->start; 201a6ccdcd9SHaojian Zhuang res = platform_get_resource_byname(pdev, IORESOURCE_REG, "always on"); 202a6ccdcd9SHaojian Zhuang if (!res) { 2031a84db56SMasanari Iida dev_err(&pdev->dev, "No REG resource for always on\n"); 2043a1f9462SJingoo Han return -ENXIO; 205a6ccdcd9SHaojian Zhuang } 206a6ccdcd9SHaojian Zhuang data->reg_always_on = res->start; 207a6ccdcd9SHaojian Zhuang res = platform_get_resource_byname(pdev, IORESOURCE_REG, "current"); 208a6ccdcd9SHaojian Zhuang if (!res) { 209a6ccdcd9SHaojian Zhuang dev_err(&pdev->dev, "No REG resource for current\n"); 2103a1f9462SJingoo Han return -ENXIO; 211a6ccdcd9SHaojian Zhuang } 212a6ccdcd9SHaojian Zhuang data->reg_current = res->start; 213a6ccdcd9SHaojian Zhuang 214a6ccdcd9SHaojian Zhuang memset(name, 0, MFD_NAME_SIZE); 215a6ccdcd9SHaojian Zhuang sprintf(name, "backlight-%d", pdev->id); 216a6ccdcd9SHaojian Zhuang data->port = pdev->id; 217d07e8bf8SHaojian Zhuang data->chip = chip; 2182fe2380eSJingoo Han data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion; 219d07e8bf8SHaojian Zhuang data->current_brightness = MAX_BRIGHTNESS; 2202e57d567SHaojian Zhuang if (pm860x_backlight_dt_init(pdev, data, name)) { 221a6ccdcd9SHaojian Zhuang if (pdata) { 222d07e8bf8SHaojian Zhuang data->pwm = pdata->pwm; 223d07e8bf8SHaojian Zhuang data->iset = pdata->iset; 224d07e8bf8SHaojian Zhuang } 2252e57d567SHaojian Zhuang } 226d07e8bf8SHaojian Zhuang 227a19a6ee6SMatthew Garrett memset(&props, 0, sizeof(struct backlight_properties)); 228bb7ca747SMatthew Garrett props.type = BACKLIGHT_RAW; 229a19a6ee6SMatthew Garrett props.max_brightness = MAX_BRIGHTNESS; 230f829c9efSJingoo Han bl = devm_backlight_device_register(&pdev->dev, name, &pdev->dev, data, 231a19a6ee6SMatthew Garrett &pm860x_backlight_ops, &props); 232d07e8bf8SHaojian Zhuang if (IS_ERR(bl)) { 233d07e8bf8SHaojian Zhuang dev_err(&pdev->dev, "failed to register backlight\n"); 234d07e8bf8SHaojian Zhuang return PTR_ERR(bl); 235d07e8bf8SHaojian Zhuang } 236d07e8bf8SHaojian Zhuang bl->props.brightness = MAX_BRIGHTNESS; 237d07e8bf8SHaojian Zhuang 238d07e8bf8SHaojian Zhuang platform_set_drvdata(pdev, bl); 239d07e8bf8SHaojian Zhuang 240d07e8bf8SHaojian Zhuang /* read current backlight */ 241d07e8bf8SHaojian Zhuang ret = pm860x_backlight_get_brightness(bl); 242d07e8bf8SHaojian Zhuang if (ret < 0) 243f829c9efSJingoo Han return ret; 244d07e8bf8SHaojian Zhuang 245d07e8bf8SHaojian Zhuang backlight_update_status(bl); 246d07e8bf8SHaojian Zhuang return 0; 247d07e8bf8SHaojian Zhuang } 248d07e8bf8SHaojian Zhuang 249d07e8bf8SHaojian Zhuang static struct platform_driver pm860x_backlight_driver = { 250d07e8bf8SHaojian Zhuang .driver = { 251d07e8bf8SHaojian Zhuang .name = "88pm860x-backlight", 252d07e8bf8SHaojian Zhuang }, 253d07e8bf8SHaojian Zhuang .probe = pm860x_backlight_probe, 254d07e8bf8SHaojian Zhuang }; 255d07e8bf8SHaojian Zhuang 25681178e02SAxel Lin module_platform_driver(pm860x_backlight_driver); 257d07e8bf8SHaojian Zhuang 258d07e8bf8SHaojian Zhuang MODULE_DESCRIPTION("Backlight Driver for Marvell Semiconductor 88PM8606"); 259d07e8bf8SHaojian Zhuang MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); 260d07e8bf8SHaojian Zhuang MODULE_LICENSE("GPL"); 261d07e8bf8SHaojian Zhuang MODULE_ALIAS("platform:88pm860x-backlight"); 262