Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

fbdev: LCD backlight driver using Atmel PWM driver

This patch adds a platform driver using the ATMEL PWM driver to control a
backlight which requires a PWM signal and optional GPIO signal for discrete
on/off signal. It has been tested on Favr-32 board from EarthLCD.

The driver is configurable by supplying a struct with the platform data. See
the include/linux/atmel-pwm-bl.h for details.

The board code for Favr-32 will be submitted to the AVR32 kernel list.

Signed-off-by: Hans-Christian Egtvedt <hans-christian.egtvedt@atmel.com>
Cc: Krzysztof Helt <krzysztof.h1@poczta.fm>
Cc: Haavard Skinnemoen <hskinnemoen@atmel.com>
Cc: Richard Purdie <rpurdie@rpsys.net>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Hans-Christian Egtvedt and committed by
Linus Torvalds
3e074058 2d04a4a7

+300
+12
drivers/video/backlight/Kconfig
··· 87 87 If in doubt, it's safe to enable this option; it doesn't kick 88 88 in unless the board's description says it's wired that way. 89 89 90 + config BACKLIGHT_ATMEL_PWM 91 + tristate "Atmel PWM backlight control" 92 + depends on BACKLIGHT_CLASS_DEVICE && ATMEL_PWM 93 + default n 94 + help 95 + Say Y here if you want to use the PWM peripheral in Atmel AT91 and 96 + AVR32 devices. This driver will need additional platform data to know 97 + which PWM instance to use and how to configure it. 98 + 99 + To compile this driver as a module, choose M here: the module will be 100 + called atmel-pwm-bl. 101 + 90 102 config BACKLIGHT_CORGI 91 103 tristate "Generic (aka Sharp Corgi) Backlight Driver" 92 104 depends on BACKLIGHT_CLASS_DEVICE
+1
drivers/video/backlight/Makefile
··· 7 7 obj-$(CONFIG_LCD_VGG2432A4) += vgg2432a4.o 8 8 9 9 obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o 10 + obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o 10 11 obj-$(CONFIG_BACKLIGHT_CORGI) += corgi_bl.o 11 12 obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o 12 13 obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o
+244
drivers/video/backlight/atmel-pwm-bl.c
··· 1 + /* 2 + * Copyright (C) 2008 Atmel Corporation 3 + * 4 + * Backlight driver using Atmel PWM peripheral. 5 + * 6 + * This program is free software; you can redistribute it and/or modify it 7 + * under the terms of the GNU General Public License version 2 as published by 8 + * the Free Software Foundation. 9 + */ 10 + #include <linux/init.h> 11 + #include <linux/kernel.h> 12 + #include <linux/module.h> 13 + #include <linux/platform_device.h> 14 + #include <linux/fb.h> 15 + #include <linux/clk.h> 16 + #include <linux/gpio.h> 17 + #include <linux/backlight.h> 18 + #include <linux/atmel_pwm.h> 19 + #include <linux/atmel-pwm-bl.h> 20 + 21 + struct atmel_pwm_bl { 22 + const struct atmel_pwm_bl_platform_data *pdata; 23 + struct backlight_device *bldev; 24 + struct platform_device *pdev; 25 + struct pwm_channel pwmc; 26 + int gpio_on; 27 + }; 28 + 29 + static int atmel_pwm_bl_set_intensity(struct backlight_device *bd) 30 + { 31 + struct atmel_pwm_bl *pwmbl = bl_get_data(bd); 32 + int intensity = bd->props.brightness; 33 + int pwm_duty; 34 + 35 + if (bd->props.power != FB_BLANK_UNBLANK) 36 + intensity = 0; 37 + if (bd->props.fb_blank != FB_BLANK_UNBLANK) 38 + intensity = 0; 39 + 40 + if (pwmbl->pdata->pwm_active_low) 41 + pwm_duty = pwmbl->pdata->pwm_duty_min + intensity; 42 + else 43 + pwm_duty = pwmbl->pdata->pwm_duty_max - intensity; 44 + 45 + if (pwm_duty > pwmbl->pdata->pwm_duty_max) 46 + pwm_duty = pwmbl->pdata->pwm_duty_max; 47 + if (pwm_duty < pwmbl->pdata->pwm_duty_min) 48 + pwm_duty = pwmbl->pdata->pwm_duty_min; 49 + 50 + if (!intensity) { 51 + if (pwmbl->gpio_on != -1) { 52 + gpio_set_value(pwmbl->gpio_on, 53 + 0 ^ pwmbl->pdata->on_active_low); 54 + } 55 + pwm_channel_writel(&pwmbl->pwmc, PWM_CUPD, pwm_duty); 56 + pwm_channel_disable(&pwmbl->pwmc); 57 + } else { 58 + pwm_channel_enable(&pwmbl->pwmc); 59 + pwm_channel_writel(&pwmbl->pwmc, PWM_CUPD, pwm_duty); 60 + if (pwmbl->gpio_on != -1) { 61 + gpio_set_value(pwmbl->gpio_on, 62 + 1 ^ pwmbl->pdata->on_active_low); 63 + } 64 + } 65 + 66 + return 0; 67 + } 68 + 69 + static int atmel_pwm_bl_get_intensity(struct backlight_device *bd) 70 + { 71 + struct atmel_pwm_bl *pwmbl = bl_get_data(bd); 72 + u8 intensity; 73 + 74 + if (pwmbl->pdata->pwm_active_low) { 75 + intensity = pwm_channel_readl(&pwmbl->pwmc, PWM_CDTY) - 76 + pwmbl->pdata->pwm_duty_min; 77 + } else { 78 + intensity = pwmbl->pdata->pwm_duty_max - 79 + pwm_channel_readl(&pwmbl->pwmc, PWM_CDTY); 80 + } 81 + 82 + return intensity; 83 + } 84 + 85 + static int atmel_pwm_bl_init_pwm(struct atmel_pwm_bl *pwmbl) 86 + { 87 + unsigned long pwm_rate = pwmbl->pwmc.mck; 88 + unsigned long prescale = DIV_ROUND_UP(pwm_rate, 89 + (pwmbl->pdata->pwm_frequency * 90 + pwmbl->pdata->pwm_compare_max)) - 1; 91 + 92 + /* 93 + * Prescale must be power of two and maximum 0xf in size because of 94 + * hardware limit. PWM speed will be: 95 + * PWM module clock speed / (2 ^ prescale). 96 + */ 97 + prescale = fls(prescale); 98 + if (prescale > 0xf) 99 + prescale = 0xf; 100 + 101 + pwm_channel_writel(&pwmbl->pwmc, PWM_CMR, prescale); 102 + pwm_channel_writel(&pwmbl->pwmc, PWM_CDTY, 103 + pwmbl->pdata->pwm_duty_min + 104 + pwmbl->bldev->props.brightness); 105 + pwm_channel_writel(&pwmbl->pwmc, PWM_CPRD, 106 + pwmbl->pdata->pwm_compare_max); 107 + 108 + dev_info(&pwmbl->pdev->dev, "Atmel PWM backlight driver " 109 + "(%lu Hz)\n", pwmbl->pwmc.mck / 110 + pwmbl->pdata->pwm_compare_max / 111 + (1 << prescale)); 112 + 113 + return pwm_channel_enable(&pwmbl->pwmc); 114 + } 115 + 116 + static struct backlight_ops atmel_pwm_bl_ops = { 117 + .get_brightness = atmel_pwm_bl_get_intensity, 118 + .update_status = atmel_pwm_bl_set_intensity, 119 + }; 120 + 121 + static int atmel_pwm_bl_probe(struct platform_device *pdev) 122 + { 123 + const struct atmel_pwm_bl_platform_data *pdata; 124 + struct backlight_device *bldev; 125 + struct atmel_pwm_bl *pwmbl; 126 + int retval; 127 + 128 + pwmbl = kzalloc(sizeof(struct atmel_pwm_bl), GFP_KERNEL); 129 + if (!pwmbl) 130 + return -ENOMEM; 131 + 132 + pwmbl->pdev = pdev; 133 + 134 + pdata = pdev->dev.platform_data; 135 + if (!pdata) { 136 + retval = -ENODEV; 137 + goto err_free_mem; 138 + } 139 + 140 + if (pdata->pwm_compare_max < pdata->pwm_duty_max || 141 + pdata->pwm_duty_min > pdata->pwm_duty_max || 142 + pdata->pwm_frequency == 0) { 143 + retval = -EINVAL; 144 + goto err_free_mem; 145 + } 146 + 147 + pwmbl->pdata = pdata; 148 + pwmbl->gpio_on = pdata->gpio_on; 149 + 150 + retval = pwm_channel_alloc(pdata->pwm_channel, &pwmbl->pwmc); 151 + if (retval) 152 + goto err_free_mem; 153 + 154 + if (pwmbl->gpio_on != -1) { 155 + retval = gpio_request(pwmbl->gpio_on, "gpio_atmel_pwm_bl"); 156 + if (retval) { 157 + pwmbl->gpio_on = -1; 158 + goto err_free_pwm; 159 + } 160 + 161 + /* Turn display off by defatult. */ 162 + retval = gpio_direction_output(pwmbl->gpio_on, 163 + 0 ^ pdata->on_active_low); 164 + if (retval) 165 + goto err_free_gpio; 166 + } 167 + 168 + bldev = backlight_device_register("atmel-pwm-bl", 169 + &pdev->dev, pwmbl, &atmel_pwm_bl_ops); 170 + if (IS_ERR(bldev)) { 171 + retval = PTR_ERR(bldev); 172 + goto err_free_gpio; 173 + } 174 + 175 + pwmbl->bldev = bldev; 176 + 177 + platform_set_drvdata(pdev, pwmbl); 178 + 179 + /* Power up the backlight by default at middle intesity. */ 180 + bldev->props.power = FB_BLANK_UNBLANK; 181 + bldev->props.max_brightness = pdata->pwm_duty_max - pdata->pwm_duty_min; 182 + bldev->props.brightness = bldev->props.max_brightness / 2; 183 + 184 + retval = atmel_pwm_bl_init_pwm(pwmbl); 185 + if (retval) 186 + goto err_free_bl_dev; 187 + 188 + atmel_pwm_bl_set_intensity(bldev); 189 + 190 + return 0; 191 + 192 + err_free_bl_dev: 193 + platform_set_drvdata(pdev, NULL); 194 + backlight_device_unregister(bldev); 195 + err_free_gpio: 196 + if (pwmbl->gpio_on != -1) 197 + gpio_free(pwmbl->gpio_on); 198 + err_free_pwm: 199 + pwm_channel_free(&pwmbl->pwmc); 200 + err_free_mem: 201 + kfree(pwmbl); 202 + return retval; 203 + } 204 + 205 + static int __exit atmel_pwm_bl_remove(struct platform_device *pdev) 206 + { 207 + struct atmel_pwm_bl *pwmbl = platform_get_drvdata(pdev); 208 + 209 + if (pwmbl->gpio_on != -1) { 210 + gpio_set_value(pwmbl->gpio_on, 0); 211 + gpio_free(pwmbl->gpio_on); 212 + } 213 + pwm_channel_disable(&pwmbl->pwmc); 214 + pwm_channel_free(&pwmbl->pwmc); 215 + backlight_device_unregister(pwmbl->bldev); 216 + platform_set_drvdata(pdev, NULL); 217 + kfree(pwmbl); 218 + 219 + return 0; 220 + } 221 + 222 + static struct platform_driver atmel_pwm_bl_driver = { 223 + .driver = { 224 + .name = "atmel-pwm-bl", 225 + }, 226 + /* REVISIT add suspend() and resume() */ 227 + .remove = __exit_p(atmel_pwm_bl_remove), 228 + }; 229 + 230 + static int __init atmel_pwm_bl_init(void) 231 + { 232 + return platform_driver_probe(&atmel_pwm_bl_driver, atmel_pwm_bl_probe); 233 + } 234 + module_init(atmel_pwm_bl_init); 235 + 236 + static void __exit atmel_pwm_bl_exit(void) 237 + { 238 + platform_driver_unregister(&atmel_pwm_bl_driver); 239 + } 240 + module_exit(atmel_pwm_bl_exit); 241 + 242 + MODULE_AUTHOR("Hans-Christian egtvedt <hans-christian.egtvedt@atmel.com>"); 243 + MODULE_DESCRIPTION("Atmel PWM backlight driver"); 244 + MODULE_LICENSE("GPL");
+43
include/linux/atmel-pwm-bl.h
··· 1 + /* 2 + * Copyright (C) 2007 Atmel Corporation 3 + * 4 + * Driver for the AT32AP700X PS/2 controller (PSIF). 5 + * 6 + * This program is free software; you can redistribute it and/or modify it 7 + * under the terms of the GNU General Public License version 2 as published 8 + * by the Free Software Foundation. 9 + */ 10 + 11 + #ifndef __INCLUDE_ATMEL_PWM_BL_H 12 + #define __INCLUDE_ATMEL_PWM_BL_H 13 + 14 + /** 15 + * struct atmel_pwm_bl_platform_data 16 + * @pwm_channel: which PWM channel in the PWM module to use. 17 + * @pwm_frequency: PWM frequency to generate, the driver will try to be as 18 + * close as the prescaler allows. 19 + * @pwm_compare_max: value to use in the PWM channel compare register. 20 + * @pwm_duty_max: maximum duty cycle value, must be less than or equal to 21 + * pwm_compare_max. 22 + * @pwm_duty_min: minimum duty cycle value, must be less than pwm_duty_max. 23 + * @pwm_active_low: set to one if the low part of the PWM signal increases the 24 + * brightness of the backlight. 25 + * @gpio_on: GPIO line to control the backlight on/off, set to -1 if not used. 26 + * @on_active_low: set to one if the on/off signal is on when GPIO is low. 27 + * 28 + * This struct must be added to the platform device in the board code. It is 29 + * used by the atmel-pwm-bl driver to setup the GPIO to control on/off and the 30 + * PWM device. 31 + */ 32 + struct atmel_pwm_bl_platform_data { 33 + unsigned int pwm_channel; 34 + unsigned int pwm_frequency; 35 + unsigned int pwm_compare_max; 36 + unsigned int pwm_duty_max; 37 + unsigned int pwm_duty_min; 38 + unsigned int pwm_active_low; 39 + int gpio_on; 40 + unsigned int on_active_low; 41 + }; 42 + 43 + #endif /* __INCLUDE_ATMEL_PWM_BL_H */