1/* 2 * linux/drivers/video/backlight/pwm_bl.c 3 * 4 * simple PWM based backlight control, board code has to setup 5 * 1) pin configuration so PWM waveforms can output 6 * 2) platform_data being correctly configured 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License version 2 as 10 * published by the Free Software Foundation. 11 */ 12 13#include <linux/module.h> 14#include <linux/kernel.h> 15#include <linux/init.h> 16#include <linux/platform_device.h> 17#include <linux/fb.h> 18#include <linux/backlight.h> 19#include <linux/err.h> 20#include <linux/pwm.h> 21#include <linux/pwm_backlight.h> 22#include <linux/slab.h> 23 24struct pwm_bl_data { 25 struct pwm_device *pwm; 26 struct device *dev; 27 unsigned int period; 28 int (*notify)(struct device *, 29 int brightness); 30}; 31 32static int pwm_backlight_update_status(struct backlight_device *bl) 33{ 34 struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); 35 int brightness = bl->props.brightness; 36 int max = bl->props.max_brightness; 37 38 if (bl->props.power != FB_BLANK_UNBLANK) 39 brightness = 0; 40 41 if (bl->props.fb_blank != FB_BLANK_UNBLANK) 42 brightness = 0; 43 44 if (pb->notify) 45 brightness = pb->notify(pb->dev, brightness); 46 47 if (brightness == 0) { 48 pwm_config(pb->pwm, 0, pb->period); 49 pwm_disable(pb->pwm); 50 } else { 51 pwm_config(pb->pwm, brightness * pb->period / max, pb->period); 52 pwm_enable(pb->pwm); 53 } 54 return 0; 55} 56 57static int pwm_backlight_get_brightness(struct backlight_device *bl) 58{ 59 return bl->props.brightness; 60} 61 62static const struct backlight_ops pwm_backlight_ops = { 63 .update_status = pwm_backlight_update_status, 64 .get_brightness = pwm_backlight_get_brightness, 65}; 66 67static int pwm_backlight_probe(struct platform_device *pdev) 68{ 69 struct backlight_properties props; 70 struct platform_pwm_backlight_data *data = pdev->dev.platform_data; 71 struct backlight_device *bl; 72 struct pwm_bl_data *pb; 73 int ret; 74 75 if (!data) { 76 dev_err(&pdev->dev, "failed to find platform data\n"); 77 return -EINVAL; 78 } 79 80 if (data->init) { 81 ret = data->init(&pdev->dev); 82 if (ret < 0) 83 return ret; 84 } 85 86 pb = kzalloc(sizeof(*pb), GFP_KERNEL); 87 if (!pb) { 88 dev_err(&pdev->dev, "no memory for state\n"); 89 ret = -ENOMEM; 90 goto err_alloc; 91 } 92 93 pb->period = data->pwm_period_ns; 94 pb->notify = data->notify; 95 pb->dev = &pdev->dev; 96 97 pb->pwm = pwm_request(data->pwm_id, "backlight"); 98 if (IS_ERR(pb->pwm)) { 99 dev_err(&pdev->dev, "unable to request PWM for backlight\n"); 100 ret = PTR_ERR(pb->pwm); 101 goto err_pwm; 102 } else 103 dev_dbg(&pdev->dev, "got pwm for backlight\n"); 104 105 memset(&props, 0, sizeof(struct backlight_properties)); 106 props.max_brightness = data->max_brightness; 107 bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb, 108 &pwm_backlight_ops, &props); 109 if (IS_ERR(bl)) { 110 dev_err(&pdev->dev, "failed to register backlight\n"); 111 ret = PTR_ERR(bl); 112 goto err_bl; 113 } 114 115 bl->props.brightness = data->dft_brightness; 116 backlight_update_status(bl); 117 118 platform_set_drvdata(pdev, bl); 119 return 0; 120 121err_bl: 122 pwm_free(pb->pwm); 123err_pwm: 124 kfree(pb); 125err_alloc: 126 if (data->exit) 127 data->exit(&pdev->dev); 128 return ret; 129} 130 131static int pwm_backlight_remove(struct platform_device *pdev) 132{ 133 struct platform_pwm_backlight_data *data = pdev->dev.platform_data; 134 struct backlight_device *bl = platform_get_drvdata(pdev); 135 struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); 136 137 backlight_device_unregister(bl); 138 pwm_config(pb->pwm, 0, pb->period); 139 pwm_disable(pb->pwm); 140 pwm_free(pb->pwm); 141 kfree(pb); 142 if (data->exit) 143 data->exit(&pdev->dev); 144 return 0; 145} 146 147#ifdef CONFIG_PM 148static int pwm_backlight_suspend(struct platform_device *pdev, 149 pm_message_t state) 150{ 151 struct backlight_device *bl = platform_get_drvdata(pdev); 152 struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); 153 154 if (pb->notify) 155 pb->notify(pb->dev, 0); 156 pwm_config(pb->pwm, 0, pb->period); 157 pwm_disable(pb->pwm); 158 return 0; 159} 160 161static int pwm_backlight_resume(struct platform_device *pdev) 162{ 163 struct backlight_device *bl = platform_get_drvdata(pdev); 164 165 backlight_update_status(bl); 166 return 0; 167} 168#else 169#define pwm_backlight_suspend NULL 170#define pwm_backlight_resume NULL 171#endif 172 173static struct platform_driver pwm_backlight_driver = { 174 .driver = { 175 .name = "pwm-backlight", 176 .owner = THIS_MODULE, 177 }, 178 .probe = pwm_backlight_probe, 179 .remove = pwm_backlight_remove, 180 .suspend = pwm_backlight_suspend, 181 .resume = pwm_backlight_resume, 182}; 183 184static int __init pwm_backlight_init(void) 185{ 186 return platform_driver_register(&pwm_backlight_driver); 187} 188module_init(pwm_backlight_init); 189 190static void __exit pwm_backlight_exit(void) 191{ 192 platform_driver_unregister(&pwm_backlight_driver); 193} 194module_exit(pwm_backlight_exit); 195 196MODULE_DESCRIPTION("PWM based Backlight Driver"); 197MODULE_LICENSE("GPL"); 198MODULE_ALIAS("platform:pwm-backlight"); 199