|  | /* | 
|  | * tps65217_bl.c | 
|  | * | 
|  | * TPS65217 backlight driver | 
|  | * | 
|  | * Copyright (C) 2012 Matthias Kaehlcke | 
|  | * Author: Matthias Kaehlcke <matthias@kaehlcke.net> | 
|  | * | 
|  | * This program is free software; you can redistribute it and/or | 
|  | * modify it under the terms of the GNU General Public License as | 
|  | * published by the Free Software Foundation version 2. | 
|  | * | 
|  | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | 
|  | * kind, whether express or implied; without even the implied warranty | 
|  | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | * GNU General Public License for more details. | 
|  | */ | 
|  |  | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/backlight.h> | 
|  | #include <linux/err.h> | 
|  | #include <linux/fb.h> | 
|  | #include <linux/mfd/tps65217.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/slab.h> | 
|  |  | 
|  | struct tps65217_bl { | 
|  | struct tps65217 *tps; | 
|  | struct device *dev; | 
|  | struct backlight_device *bl; | 
|  | bool is_enabled; | 
|  | }; | 
|  |  | 
|  | static int tps65217_bl_enable(struct tps65217_bl *tps65217_bl) | 
|  | { | 
|  | int rc; | 
|  |  | 
|  | rc = tps65217_set_bits(tps65217_bl->tps, TPS65217_REG_WLEDCTRL1, | 
|  | TPS65217_WLEDCTRL1_ISINK_ENABLE, | 
|  | TPS65217_WLEDCTRL1_ISINK_ENABLE, TPS65217_PROTECT_NONE); | 
|  | if (rc) { | 
|  | dev_err(tps65217_bl->dev, | 
|  | "failed to enable backlight: %d\n", rc); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | tps65217_bl->is_enabled = true; | 
|  |  | 
|  | dev_dbg(tps65217_bl->dev, "backlight enabled\n"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int tps65217_bl_disable(struct tps65217_bl *tps65217_bl) | 
|  | { | 
|  | int rc; | 
|  |  | 
|  | rc = tps65217_clear_bits(tps65217_bl->tps, | 
|  | TPS65217_REG_WLEDCTRL1, | 
|  | TPS65217_WLEDCTRL1_ISINK_ENABLE, | 
|  | TPS65217_PROTECT_NONE); | 
|  | if (rc) { | 
|  | dev_err(tps65217_bl->dev, | 
|  | "failed to disable backlight: %d\n", rc); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | tps65217_bl->is_enabled = false; | 
|  |  | 
|  | dev_dbg(tps65217_bl->dev, "backlight disabled\n"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int tps65217_bl_update_status(struct backlight_device *bl) | 
|  | { | 
|  | struct tps65217_bl *tps65217_bl = bl_get_data(bl); | 
|  | int rc; | 
|  | int brightness = bl->props.brightness; | 
|  |  | 
|  | if (bl->props.state & BL_CORE_SUSPENDED) | 
|  | brightness = 0; | 
|  |  | 
|  | if ((bl->props.power != FB_BLANK_UNBLANK) || | 
|  | (bl->props.fb_blank != FB_BLANK_UNBLANK)) | 
|  | /* framebuffer in low power mode or blanking active */ | 
|  | brightness = 0; | 
|  |  | 
|  | if (brightness > 0) { | 
|  | rc = tps65217_reg_write(tps65217_bl->tps, | 
|  | TPS65217_REG_WLEDCTRL2, | 
|  | brightness - 1, | 
|  | TPS65217_PROTECT_NONE); | 
|  | if (rc) { | 
|  | dev_err(tps65217_bl->dev, | 
|  | "failed to set brightness level: %d\n", rc); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | dev_dbg(tps65217_bl->dev, "brightness set to %d\n", brightness); | 
|  |  | 
|  | if (!tps65217_bl->is_enabled) | 
|  | rc = tps65217_bl_enable(tps65217_bl); | 
|  | } else { | 
|  | rc = tps65217_bl_disable(tps65217_bl); | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static const struct backlight_ops tps65217_bl_ops = { | 
|  | .options	= BL_CORE_SUSPENDRESUME, | 
|  | .update_status	= tps65217_bl_update_status, | 
|  | }; | 
|  |  | 
|  | static int tps65217_bl_hw_init(struct tps65217_bl *tps65217_bl, | 
|  | struct tps65217_bl_pdata *pdata) | 
|  | { | 
|  | int rc; | 
|  |  | 
|  | rc = tps65217_bl_disable(tps65217_bl); | 
|  | if (rc) | 
|  | return rc; | 
|  |  | 
|  | switch (pdata->isel) { | 
|  | case TPS65217_BL_ISET1: | 
|  | /* select ISET_1 current level */ | 
|  | rc = tps65217_clear_bits(tps65217_bl->tps, | 
|  | TPS65217_REG_WLEDCTRL1, | 
|  | TPS65217_WLEDCTRL1_ISEL, | 
|  | TPS65217_PROTECT_NONE); | 
|  | if (rc) { | 
|  | dev_err(tps65217_bl->dev, | 
|  | "failed to select ISET1 current level: %d)\n", | 
|  | rc); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | dev_dbg(tps65217_bl->dev, "selected ISET1 current level\n"); | 
|  |  | 
|  | break; | 
|  |  | 
|  | case TPS65217_BL_ISET2: | 
|  | /* select ISET2 current level */ | 
|  | rc = tps65217_set_bits(tps65217_bl->tps, TPS65217_REG_WLEDCTRL1, | 
|  | TPS65217_WLEDCTRL1_ISEL, | 
|  | TPS65217_WLEDCTRL1_ISEL, TPS65217_PROTECT_NONE); | 
|  | if (rc) { | 
|  | dev_err(tps65217_bl->dev, | 
|  | "failed to select ISET2 current level: %d\n", | 
|  | rc); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | dev_dbg(tps65217_bl->dev, "selected ISET2 current level\n"); | 
|  |  | 
|  | break; | 
|  |  | 
|  | default: | 
|  | dev_err(tps65217_bl->dev, | 
|  | "invalid value for current level: %d\n", pdata->isel); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* set PWM frequency */ | 
|  | rc = tps65217_set_bits(tps65217_bl->tps, | 
|  | TPS65217_REG_WLEDCTRL1, | 
|  | TPS65217_WLEDCTRL1_FDIM_MASK, | 
|  | pdata->fdim, | 
|  | TPS65217_PROTECT_NONE); | 
|  | if (rc) { | 
|  | dev_err(tps65217_bl->dev, | 
|  | "failed to select PWM dimming frequency: %d\n", | 
|  | rc); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_OF | 
|  | static struct tps65217_bl_pdata * | 
|  | tps65217_bl_parse_dt(struct platform_device *pdev) | 
|  | { | 
|  | struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent); | 
|  | struct device_node *node; | 
|  | struct tps65217_bl_pdata *pdata, *err; | 
|  | u32 val; | 
|  |  | 
|  | node = of_get_child_by_name(tps->dev->of_node, "backlight"); | 
|  | if (!node) | 
|  | return ERR_PTR(-ENODEV); | 
|  |  | 
|  | pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); | 
|  | if (!pdata) { | 
|  | err = ERR_PTR(-ENOMEM); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | pdata->isel = TPS65217_BL_ISET1; | 
|  | if (!of_property_read_u32(node, "isel", &val)) { | 
|  | if (val < TPS65217_BL_ISET1 || | 
|  | val > TPS65217_BL_ISET2) { | 
|  | dev_err(&pdev->dev, | 
|  | "invalid 'isel' value in the device tree\n"); | 
|  | err = ERR_PTR(-EINVAL); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | pdata->isel = val; | 
|  | } | 
|  |  | 
|  | pdata->fdim = TPS65217_BL_FDIM_200HZ; | 
|  | if (!of_property_read_u32(node, "fdim", &val)) { | 
|  | switch (val) { | 
|  | case 100: | 
|  | pdata->fdim = TPS65217_BL_FDIM_100HZ; | 
|  | break; | 
|  |  | 
|  | case 200: | 
|  | pdata->fdim = TPS65217_BL_FDIM_200HZ; | 
|  | break; | 
|  |  | 
|  | case 500: | 
|  | pdata->fdim = TPS65217_BL_FDIM_500HZ; | 
|  | break; | 
|  |  | 
|  | case 1000: | 
|  | pdata->fdim = TPS65217_BL_FDIM_1000HZ; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | dev_err(&pdev->dev, | 
|  | "invalid 'fdim' value in the device tree\n"); | 
|  | err = ERR_PTR(-EINVAL); | 
|  | goto err; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!of_property_read_u32(node, "default-brightness", &val)) { | 
|  | if (val > 100) { | 
|  | dev_err(&pdev->dev, | 
|  | "invalid 'default-brightness' value in the device tree\n"); | 
|  | err = ERR_PTR(-EINVAL); | 
|  | goto err; | 
|  | } | 
|  |  | 
|  | pdata->dft_brightness = val; | 
|  | } | 
|  |  | 
|  | of_node_put(node); | 
|  |  | 
|  | return pdata; | 
|  |  | 
|  | err: | 
|  | of_node_put(node); | 
|  |  | 
|  | return err; | 
|  | } | 
|  | #else | 
|  | static struct tps65217_bl_pdata * | 
|  | tps65217_bl_parse_dt(struct platform_device *pdev) | 
|  | { | 
|  | return NULL; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static int tps65217_bl_probe(struct platform_device *pdev) | 
|  | { | 
|  | int rc; | 
|  | struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent); | 
|  | struct tps65217_bl *tps65217_bl; | 
|  | struct tps65217_bl_pdata *pdata; | 
|  | struct backlight_properties bl_props; | 
|  |  | 
|  | pdata = tps65217_bl_parse_dt(pdev); | 
|  | if (IS_ERR(pdata)) | 
|  | return PTR_ERR(pdata); | 
|  |  | 
|  | tps65217_bl = devm_kzalloc(&pdev->dev, sizeof(*tps65217_bl), | 
|  | GFP_KERNEL); | 
|  | if (tps65217_bl == NULL) | 
|  | return -ENOMEM; | 
|  |  | 
|  | tps65217_bl->tps = tps; | 
|  | tps65217_bl->dev = &pdev->dev; | 
|  | tps65217_bl->is_enabled = false; | 
|  |  | 
|  | rc = tps65217_bl_hw_init(tps65217_bl, pdata); | 
|  | if (rc) | 
|  | return rc; | 
|  |  | 
|  | memset(&bl_props, 0, sizeof(struct backlight_properties)); | 
|  | bl_props.type = BACKLIGHT_RAW; | 
|  | bl_props.max_brightness = 100; | 
|  |  | 
|  | tps65217_bl->bl = devm_backlight_device_register(&pdev->dev, pdev->name, | 
|  | tps65217_bl->dev, tps65217_bl, | 
|  | &tps65217_bl_ops, &bl_props); | 
|  | if (IS_ERR(tps65217_bl->bl)) { | 
|  | dev_err(tps65217_bl->dev, | 
|  | "registration of backlight device failed: %d\n", rc); | 
|  | return PTR_ERR(tps65217_bl->bl); | 
|  | } | 
|  |  | 
|  | tps65217_bl->bl->props.brightness = pdata->dft_brightness; | 
|  | backlight_update_status(tps65217_bl->bl); | 
|  | platform_set_drvdata(pdev, tps65217_bl); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_OF | 
|  | static const struct of_device_id tps65217_bl_of_match[] = { | 
|  | { .compatible = "ti,tps65217-bl", }, | 
|  | { /* sentinel */ }, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, tps65217_bl_of_match); | 
|  | #endif | 
|  |  | 
|  | static struct platform_driver tps65217_bl_driver = { | 
|  | .probe		= tps65217_bl_probe, | 
|  | .driver		= { | 
|  | .name	= "tps65217-bl", | 
|  | .of_match_table = of_match_ptr(tps65217_bl_of_match), | 
|  | }, | 
|  | }; | 
|  |  | 
|  | module_platform_driver(tps65217_bl_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("TPS65217 Backlight driver"); | 
|  | MODULE_LICENSE("GPL v2"); | 
|  | MODULE_AUTHOR("Matthias Kaehlcke <matthias@kaehlcke.net>"); |