xref: /linux/drivers/video/backlight/aat2870_bl.c (revision 5d6f921b42749d1a70441685b7a4f2801e12ebfb)
14b0d711bSJin Park /*
24b0d711bSJin Park  * linux/drivers/video/backlight/aat2870_bl.c
34b0d711bSJin Park  *
44b0d711bSJin Park  * Copyright (c) 2011, NVIDIA Corporation.
54b0d711bSJin Park  * Author: Jin Park <jinyoungp@nvidia.com>
64b0d711bSJin Park  *
74b0d711bSJin Park  * This program is free software; you can redistribute it and/or
84b0d711bSJin Park  * modify it under the terms of the GNU General Public License
94b0d711bSJin Park  * version 2 as published by the Free Software Foundation.
104b0d711bSJin Park  *
114b0d711bSJin Park  * This program is distributed in the hope that it will be useful, but
124b0d711bSJin Park  * WITHOUT ANY WARRANTY; without even the implied warranty of
134b0d711bSJin Park  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
144b0d711bSJin Park  * General Public License for more details.
154b0d711bSJin Park  *
164b0d711bSJin Park  * You should have received a copy of the GNU General Public License
174b0d711bSJin Park  * along with this program; if not, write to the Free Software
184b0d711bSJin Park  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
194b0d711bSJin Park  * 02110-1301 USA
204b0d711bSJin Park  */
214b0d711bSJin Park 
224b0d711bSJin Park #include <linux/module.h>
234b0d711bSJin Park #include <linux/kernel.h>
244b0d711bSJin Park #include <linux/init.h>
254b0d711bSJin Park #include <linux/platform_device.h>
264b0d711bSJin Park #include <linux/mutex.h>
274b0d711bSJin Park #include <linux/delay.h>
284b0d711bSJin Park #include <linux/fb.h>
294b0d711bSJin Park #include <linux/backlight.h>
304b0d711bSJin Park #include <linux/mfd/aat2870.h>
314b0d711bSJin Park 
324b0d711bSJin Park struct aat2870_bl_driver_data {
334b0d711bSJin Park 	struct platform_device *pdev;
344b0d711bSJin Park 	struct backlight_device *bd;
354b0d711bSJin Park 
364b0d711bSJin Park 	int channels;
374b0d711bSJin Park 	int max_current;
384b0d711bSJin Park 	int brightness; /* current brightness */
394b0d711bSJin Park };
404b0d711bSJin Park 
414b0d711bSJin Park static inline int aat2870_brightness(struct aat2870_bl_driver_data *aat2870_bl,
424b0d711bSJin Park 				     int brightness)
434b0d711bSJin Park {
444b0d711bSJin Park 	struct backlight_device *bd = aat2870_bl->bd;
454b0d711bSJin Park 	int val;
464b0d711bSJin Park 
47*5d6f921bSAxel Lin 	val = brightness * (aat2870_bl->max_current - 1);
484b0d711bSJin Park 	val /= bd->props.max_brightness;
494b0d711bSJin Park 
504b0d711bSJin Park 	return val;
514b0d711bSJin Park }
524b0d711bSJin Park 
534b0d711bSJin Park static inline int aat2870_bl_enable(struct aat2870_bl_driver_data *aat2870_bl)
544b0d711bSJin Park {
554b0d711bSJin Park 	struct aat2870_data *aat2870
564b0d711bSJin Park 			= dev_get_drvdata(aat2870_bl->pdev->dev.parent);
574b0d711bSJin Park 
584b0d711bSJin Park 	return aat2870->write(aat2870, AAT2870_BL_CH_EN,
594b0d711bSJin Park 			      (u8)aat2870_bl->channels);
604b0d711bSJin Park }
614b0d711bSJin Park 
624b0d711bSJin Park static inline int aat2870_bl_disable(struct aat2870_bl_driver_data *aat2870_bl)
634b0d711bSJin Park {
644b0d711bSJin Park 	struct aat2870_data *aat2870
654b0d711bSJin Park 			= dev_get_drvdata(aat2870_bl->pdev->dev.parent);
664b0d711bSJin Park 
674b0d711bSJin Park 	return aat2870->write(aat2870, AAT2870_BL_CH_EN, 0x0);
684b0d711bSJin Park }
694b0d711bSJin Park 
704b0d711bSJin Park static int aat2870_bl_get_brightness(struct backlight_device *bd)
714b0d711bSJin Park {
724b0d711bSJin Park 	return bd->props.brightness;
734b0d711bSJin Park }
744b0d711bSJin Park 
754b0d711bSJin Park static int aat2870_bl_update_status(struct backlight_device *bd)
764b0d711bSJin Park {
774b0d711bSJin Park 	struct aat2870_bl_driver_data *aat2870_bl = dev_get_drvdata(&bd->dev);
784b0d711bSJin Park 	struct aat2870_data *aat2870 =
794b0d711bSJin Park 			dev_get_drvdata(aat2870_bl->pdev->dev.parent);
804b0d711bSJin Park 	int brightness = bd->props.brightness;
814b0d711bSJin Park 	int ret;
824b0d711bSJin Park 
834b0d711bSJin Park 	if ((brightness < 0) || (bd->props.max_brightness < brightness)) {
844b0d711bSJin Park 		dev_err(&bd->dev, "invalid brightness, %d\n", brightness);
854b0d711bSJin Park 		return -EINVAL;
864b0d711bSJin Park 	}
874b0d711bSJin Park 
884b0d711bSJin Park 	dev_dbg(&bd->dev, "brightness=%d, power=%d, state=%d\n",
894b0d711bSJin Park 		 bd->props.brightness, bd->props.power, bd->props.state);
904b0d711bSJin Park 
914b0d711bSJin Park 	if ((bd->props.power != FB_BLANK_UNBLANK) ||
924b0d711bSJin Park 			(bd->props.state & BL_CORE_FBBLANK) ||
934b0d711bSJin Park 			(bd->props.state & BL_CORE_SUSPENDED))
944b0d711bSJin Park 		brightness = 0;
954b0d711bSJin Park 
964b0d711bSJin Park 	ret = aat2870->write(aat2870, AAT2870_BLM,
974b0d711bSJin Park 			     (u8)aat2870_brightness(aat2870_bl, brightness));
984b0d711bSJin Park 	if (ret < 0)
994b0d711bSJin Park 		return ret;
1004b0d711bSJin Park 
1014b0d711bSJin Park 	if (brightness == 0) {
1024b0d711bSJin Park 		ret = aat2870_bl_disable(aat2870_bl);
1034b0d711bSJin Park 		if (ret < 0)
1044b0d711bSJin Park 			return ret;
1054b0d711bSJin Park 	} else if (aat2870_bl->brightness == 0) {
1064b0d711bSJin Park 		ret = aat2870_bl_enable(aat2870_bl);
1074b0d711bSJin Park 		if (ret < 0)
1084b0d711bSJin Park 			return ret;
1094b0d711bSJin Park 	}
1104b0d711bSJin Park 
1114b0d711bSJin Park 	aat2870_bl->brightness = brightness;
1124b0d711bSJin Park 
1134b0d711bSJin Park 	return 0;
1144b0d711bSJin Park }
1154b0d711bSJin Park 
1164b0d711bSJin Park static int aat2870_bl_check_fb(struct backlight_device *bd, struct fb_info *fi)
1174b0d711bSJin Park {
1184b0d711bSJin Park 	return 1;
1194b0d711bSJin Park }
1204b0d711bSJin Park 
1214b0d711bSJin Park static const struct backlight_ops aat2870_bl_ops = {
1224b0d711bSJin Park 	.options = BL_CORE_SUSPENDRESUME,
1234b0d711bSJin Park 	.get_brightness = aat2870_bl_get_brightness,
1244b0d711bSJin Park 	.update_status = aat2870_bl_update_status,
1254b0d711bSJin Park 	.check_fb = aat2870_bl_check_fb,
1264b0d711bSJin Park };
1274b0d711bSJin Park 
1284b0d711bSJin Park static int aat2870_bl_probe(struct platform_device *pdev)
1294b0d711bSJin Park {
1304b0d711bSJin Park 	struct aat2870_bl_platform_data *pdata = pdev->dev.platform_data;
1314b0d711bSJin Park 	struct aat2870_bl_driver_data *aat2870_bl;
1324b0d711bSJin Park 	struct backlight_device *bd;
1334b0d711bSJin Park 	struct backlight_properties props;
1344b0d711bSJin Park 	int ret = 0;
1354b0d711bSJin Park 
1364b0d711bSJin Park 	if (!pdata) {
1374b0d711bSJin Park 		dev_err(&pdev->dev, "No platform data\n");
1384b0d711bSJin Park 		ret = -ENXIO;
1394b0d711bSJin Park 		goto out;
1404b0d711bSJin Park 	}
1414b0d711bSJin Park 
1424b0d711bSJin Park 	if (pdev->id != AAT2870_ID_BL) {
1434b0d711bSJin Park 		dev_err(&pdev->dev, "Invalid device ID, %d\n", pdev->id);
1444b0d711bSJin Park 		ret = -EINVAL;
1454b0d711bSJin Park 		goto out;
1464b0d711bSJin Park 	}
1474b0d711bSJin Park 
1484b0d711bSJin Park 	aat2870_bl = kzalloc(sizeof(struct aat2870_bl_driver_data), GFP_KERNEL);
1494b0d711bSJin Park 	if (!aat2870_bl) {
1504b0d711bSJin Park 		dev_err(&pdev->dev,
1514b0d711bSJin Park 			"Failed to allocate memory for aat2870 backlight\n");
1524b0d711bSJin Park 		ret = -ENOMEM;
1534b0d711bSJin Park 		goto out;
1544b0d711bSJin Park 	}
1554b0d711bSJin Park 
1564b0d711bSJin Park 	memset(&props, 0, sizeof(struct backlight_properties));
1574b0d711bSJin Park 
1584b0d711bSJin Park 	props.type = BACKLIGHT_RAW;
1594b0d711bSJin Park 	bd = backlight_device_register("aat2870-backlight", &pdev->dev,
1604b0d711bSJin Park 				       aat2870_bl, &aat2870_bl_ops, &props);
1614c4dd903SAxel Lin 	if (IS_ERR(bd)) {
1624b0d711bSJin Park 		dev_err(&pdev->dev,
1634b0d711bSJin Park 			"Failed allocate memory for backlight device\n");
1644c4dd903SAxel Lin 		ret = PTR_ERR(bd);
1654b0d711bSJin Park 		goto out_kfree;
1664b0d711bSJin Park 	}
1674b0d711bSJin Park 
1684b0d711bSJin Park 	aat2870_bl->pdev = pdev;
1694b0d711bSJin Park 	platform_set_drvdata(pdev, aat2870_bl);
1704b0d711bSJin Park 
1714b0d711bSJin Park 	aat2870_bl->bd = bd;
1724b0d711bSJin Park 
1734b0d711bSJin Park 	if (pdata->channels > 0)
1744b0d711bSJin Park 		aat2870_bl->channels = pdata->channels;
1754b0d711bSJin Park 	else
1764b0d711bSJin Park 		aat2870_bl->channels = AAT2870_BL_CH_ALL;
1774b0d711bSJin Park 
178*5d6f921bSAxel Lin 	if (pdata->max_current > 0)
1794b0d711bSJin Park 		aat2870_bl->max_current = pdata->max_current;
1804b0d711bSJin Park 	else
1814b0d711bSJin Park 		aat2870_bl->max_current = AAT2870_CURRENT_27_9;
1824b0d711bSJin Park 
1834b0d711bSJin Park 	if (pdata->max_brightness > 0)
1844b0d711bSJin Park 		bd->props.max_brightness = pdata->max_brightness;
1854b0d711bSJin Park 	else
1864b0d711bSJin Park 		bd->props.max_brightness = 255;
1874b0d711bSJin Park 
1884b0d711bSJin Park 	aat2870_bl->brightness = 0;
1894b0d711bSJin Park 	bd->props.power = FB_BLANK_UNBLANK;
1904b0d711bSJin Park 	bd->props.brightness = bd->props.max_brightness;
1914b0d711bSJin Park 
1924b0d711bSJin Park 	ret = aat2870_bl_update_status(bd);
1934b0d711bSJin Park 	if (ret < 0) {
1944b0d711bSJin Park 		dev_err(&pdev->dev, "Failed to initialize\n");
1954b0d711bSJin Park 		goto out_bl_dev_unregister;
1964b0d711bSJin Park 	}
1974b0d711bSJin Park 
1984b0d711bSJin Park 	return 0;
1994b0d711bSJin Park 
2004b0d711bSJin Park out_bl_dev_unregister:
2014b0d711bSJin Park 	backlight_device_unregister(bd);
2024b0d711bSJin Park out_kfree:
2034b0d711bSJin Park 	kfree(aat2870_bl);
2044b0d711bSJin Park out:
2054b0d711bSJin Park 	return ret;
2064b0d711bSJin Park }
2074b0d711bSJin Park 
2084b0d711bSJin Park static int aat2870_bl_remove(struct platform_device *pdev)
2094b0d711bSJin Park {
2104b0d711bSJin Park 	struct aat2870_bl_driver_data *aat2870_bl = platform_get_drvdata(pdev);
2114b0d711bSJin Park 	struct backlight_device *bd = aat2870_bl->bd;
2124b0d711bSJin Park 
2134b0d711bSJin Park 	bd->props.power = FB_BLANK_POWERDOWN;
2144b0d711bSJin Park 	bd->props.brightness = 0;
2154b0d711bSJin Park 	backlight_update_status(bd);
2164b0d711bSJin Park 
2174b0d711bSJin Park 	backlight_device_unregister(bd);
2184b0d711bSJin Park 	kfree(aat2870_bl);
2194b0d711bSJin Park 
2204b0d711bSJin Park 	return 0;
2214b0d711bSJin Park }
2224b0d711bSJin Park 
2234b0d711bSJin Park static struct platform_driver aat2870_bl_driver = {
2244b0d711bSJin Park 	.driver = {
2254b0d711bSJin Park 		.name	= "aat2870-backlight",
2264b0d711bSJin Park 		.owner	= THIS_MODULE,
2274b0d711bSJin Park 	},
2284b0d711bSJin Park 	.probe		= aat2870_bl_probe,
2294b0d711bSJin Park 	.remove		= aat2870_bl_remove,
2304b0d711bSJin Park };
2314b0d711bSJin Park 
2324b0d711bSJin Park static int __init aat2870_bl_init(void)
2334b0d711bSJin Park {
2344b0d711bSJin Park 	return platform_driver_register(&aat2870_bl_driver);
2354b0d711bSJin Park }
2364b0d711bSJin Park subsys_initcall(aat2870_bl_init);
2374b0d711bSJin Park 
2384b0d711bSJin Park static void __exit aat2870_bl_exit(void)
2394b0d711bSJin Park {
2404b0d711bSJin Park 	platform_driver_unregister(&aat2870_bl_driver);
2414b0d711bSJin Park }
2424b0d711bSJin Park module_exit(aat2870_bl_exit);
2434b0d711bSJin Park 
2444b0d711bSJin Park MODULE_DESCRIPTION("AnalogicTech AAT2870 Backlight");
2454b0d711bSJin Park MODULE_LICENSE("GPL");
2464b0d711bSJin Park MODULE_AUTHOR("Jin Park <jinyoungp@nvidia.com>");
247