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 <[email protected]>
Cc: Krzysztof Helt <[email protected]>
Cc: Haavard Skinnemoen <[email protected]>
Cc: Richard Purdie <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
This commit is contained in:
Hans-Christian Egtvedt 2008-07-23 21:31:48 -07:00 • committed by Linus Torvalds
commit 3e074058d7
4 changed files with 300 additions and 0 deletions

View file

@ -87,6 +87,18 @@ config BACKLIGHT_ATMEL_LCDC
If in doubt, it's safe to enable this option; it doesn't kick
in unless the board's description says it's wired that way.
config BACKLIGHT_ATMEL_PWM
tristate "Atmel PWM backlight control"
depends on BACKLIGHT_CLASS_DEVICE && ATMEL_PWM
default n
help
Say Y here if you want to use the PWM peripheral in Atmel AT91 and
AVR32 devices. This driver will need additional platform data to know
which PWM instance to use and how to configure it.
To compile this driver as a module, choose M here: the module will be
called atmel-pwm-bl.
config BACKLIGHT_CORGI
tristate "Generic (aka Sharp Corgi) Backlight Driver"
depends on BACKLIGHT_CLASS_DEVICE

View file

@ -7,6 +7,7 @@ obj-$(CONFIG_LCD_PLATFORM) += platform_lcd.o
obj-$(CONFIG_LCD_VGG2432A4) += vgg2432a4.o
obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o
obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o
obj-$(CONFIG_BACKLIGHT_CORGI) += corgi_bl.o
obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o
obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o

View file

@ -0,0 +1,244 @@
/*
* Copyright (C) 2008 Atmel Corporation
*
* Backlight driver using Atmel PWM peripheral.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/clk.h>
#include <linux/gpio.h>
#include <linux/backlight.h>
#include <linux/atmel_pwm.h>
#include <linux/atmel-pwm-bl.h>
struct atmel_pwm_bl {
const struct atmel_pwm_bl_platform_data *pdata;
struct backlight_device *bldev;
struct platform_device *pdev;
struct pwm_channel pwmc;
int gpio_on;
};
static int atmel_pwm_bl_set_intensity(struct backlight_device *bd)
{
struct atmel_pwm_bl *pwmbl = bl_get_data(bd);
int intensity = bd->props.brightness;
int pwm_duty;
if (bd->props.power != FB_BLANK_UNBLANK)
intensity = 0;
if (bd->props.fb_blank != FB_BLANK_UNBLANK)
intensity = 0;
if (pwmbl->pdata->pwm_active_low)
pwm_duty = pwmbl->pdata->pwm_duty_min + intensity;
else
pwm_duty = pwmbl->pdata->pwm_duty_max - intensity;
if (pwm_duty > pwmbl->pdata->pwm_duty_max)
pwm_duty = pwmbl->pdata->pwm_duty_max;
if (pwm_duty < pwmbl->pdata->pwm_duty_min)
pwm_duty = pwmbl->pdata->pwm_duty_min;
if (!intensity) {
if (pwmbl->gpio_on != -1) {
gpio_set_value(pwmbl->gpio_on,
0 ^ pwmbl->pdata->on_active_low);
}
pwm_channel_writel(&pwmbl->pwmc, PWM_CUPD, pwm_duty);
pwm_channel_disable(&pwmbl->pwmc);
} else {
pwm_channel_enable(&pwmbl->pwmc);
pwm_channel_writel(&pwmbl->pwmc, PWM_CUPD, pwm_duty);
if (pwmbl->gpio_on != -1) {
gpio_set_value(pwmbl->gpio_on,
1 ^ pwmbl->pdata->on_active_low);
}
}
return 0;
}
static int atmel_pwm_bl_get_intensity(struct backlight_device *bd)
{
struct atmel_pwm_bl *pwmbl = bl_get_data(bd);
u8 intensity;
if (pwmbl->pdata->pwm_active_low) {
intensity = pwm_channel_readl(&pwmbl->pwmc, PWM_CDTY) -
pwmbl->pdata->pwm_duty_min;
} else {
intensity = pwmbl->pdata->pwm_duty_max -
pwm_channel_readl(&pwmbl->pwmc, PWM_CDTY);
}
return intensity;
}
static int atmel_pwm_bl_init_pwm(struct atmel_pwm_bl *pwmbl)
{
unsigned long pwm_rate = pwmbl->pwmc.mck;
unsigned long prescale = DIV_ROUND_UP(pwm_rate,
(pwmbl->pdata->pwm_frequency *
pwmbl->pdata->pwm_compare_max)) - 1;
/*
* Prescale must be power of two and maximum 0xf in size because of
* hardware limit. PWM speed will be:
* PWM module clock speed / (2 ^ prescale).
*/
prescale = fls(prescale);
if (prescale > 0xf)
prescale = 0xf;
pwm_channel_writel(&pwmbl->pwmc, PWM_CMR, prescale);
pwm_channel_writel(&pwmbl->pwmc, PWM_CDTY,
pwmbl->pdata->pwm_duty_min +
pwmbl->bldev->props.brightness);
pwm_channel_writel(&pwmbl->pwmc, PWM_CPRD,
pwmbl->pdata->pwm_compare_max);
dev_info(&pwmbl->pdev->dev, "Atmel PWM backlight driver "
"(%lu Hz)\n", pwmbl->pwmc.mck /
pwmbl->pdata->pwm_compare_max /
(1 << prescale));
return pwm_channel_enable(&pwmbl->pwmc);
}
static struct backlight_ops atmel_pwm_bl_ops = {
.get_brightness = atmel_pwm_bl_get_intensity,
.update_status = atmel_pwm_bl_set_intensity,
};
static int atmel_pwm_bl_probe(struct platform_device *pdev)
{
const struct atmel_pwm_bl_platform_data *pdata;
struct backlight_device *bldev;
struct atmel_pwm_bl *pwmbl;
int retval;
pwmbl = kzalloc(sizeof(struct atmel_pwm_bl), GFP_KERNEL);
if (!pwmbl)
return -ENOMEM;
pwmbl->pdev = pdev;
pdata = pdev->dev.platform_data;
if (!pdata) {
retval = -ENODEV;
goto err_free_mem;
}
if (pdata->pwm_compare_max < pdata->pwm_duty_max ||
pdata->pwm_duty_min > pdata->pwm_duty_max ||
pdata->pwm_frequency == 0) {
retval = -EINVAL;
goto err_free_mem;
}
pwmbl->pdata = pdata;
pwmbl->gpio_on = pdata->gpio_on;
retval = pwm_channel_alloc(pdata->pwm_channel, &pwmbl->pwmc);
if (retval)
goto err_free_mem;
if (pwmbl->gpio_on != -1) {
retval = gpio_request(pwmbl->gpio_on, "gpio_atmel_pwm_bl");
if (retval) {
pwmbl->gpio_on = -1;
goto err_free_pwm;
}
/* Turn display off by defatult. */
retval = gpio_direction_output(pwmbl->gpio_on,
0 ^ pdata->on_active_low);
if (retval)
goto err_free_gpio;
}
bldev = backlight_device_register("atmel-pwm-bl",
&pdev->dev, pwmbl, &atmel_pwm_bl_ops);
if (IS_ERR(bldev)) {
retval = PTR_ERR(bldev);
goto err_free_gpio;
}
pwmbl->bldev = bldev;
platform_set_drvdata(pdev, pwmbl);
/* Power up the backlight by default at middle intesity. */
bldev->props.power = FB_BLANK_UNBLANK;
bldev->props.max_brightness = pdata->pwm_duty_max - pdata->pwm_duty_min;
bldev->props.brightness = bldev->props.max_brightness / 2;
retval = atmel_pwm_bl_init_pwm(pwmbl);
if (retval)
goto err_free_bl_dev;
atmel_pwm_bl_set_intensity(bldev);
return 0;
err_free_bl_dev:
platform_set_drvdata(pdev, NULL);
backlight_device_unregister(bldev);
err_free_gpio:
if (pwmbl->gpio_on != -1)
gpio_free(pwmbl->gpio_on);
err_free_pwm:
pwm_channel_free(&pwmbl->pwmc);
err_free_mem:
kfree(pwmbl);
return retval;
}
static int __exit atmel_pwm_bl_remove(struct platform_device *pdev)
{
struct atmel_pwm_bl *pwmbl = platform_get_drvdata(pdev);
if (pwmbl->gpio_on != -1) {
gpio_set_value(pwmbl->gpio_on, 0);
gpio_free(pwmbl->gpio_on);
}
pwm_channel_disable(&pwmbl->pwmc);
pwm_channel_free(&pwmbl->pwmc);
backlight_device_unregister(pwmbl->bldev);
platform_set_drvdata(pdev, NULL);
kfree(pwmbl);
return 0;
}
static struct platform_driver atmel_pwm_bl_driver = {
.driver = {
.name = "atmel-pwm-bl",
},
/* REVISIT add suspend() and resume() */
.remove = __exit_p(atmel_pwm_bl_remove),
};
static int __init atmel_pwm_bl_init(void)
{
return platform_driver_probe(&atmel_pwm_bl_driver, atmel_pwm_bl_probe);
}
module_init(atmel_pwm_bl_init);
static void __exit atmel_pwm_bl_exit(void)
{
platform_driver_unregister(&atmel_pwm_bl_driver);
}
module_exit(atmel_pwm_bl_exit);
MODULE_AUTHOR("Hans-Christian egtvedt <[email protected]>");
MODULE_DESCRIPTION("Atmel PWM backlight driver");
MODULE_LICENSE("GPL");