linux/drivers/video/backlight/corgi_lcd.c

625 lines
16 KiB
C
Raw Normal View History

/*
* LCD/Backlight Driver for Sharp Zaurus Handhelds (various models)
*
* Copyright (c) 2004-2006 Richard Purdie
*
* Based on Sharp's 2.4 Backlight Driver
*
* Copyright (c) 2008 Marvell International Ltd.
* Converted to SPI device based LCD/Backlight device driver
* by Eric Miao <eric.miao@marvell.com>
*
* 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/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/fb.h>
#include <linux/lcd.h>
#include <linux/spi/spi.h>
#include <linux/spi/corgi_lcd.h>
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
#include <linux/slab.h>
#include <asm/mach/sharpsl_param.h>
#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
/* Register Addresses */
#define RESCTL_ADRS 0x00
#define PHACTRL_ADRS 0x01
#define DUTYCTRL_ADRS 0x02
#define POWERREG0_ADRS 0x03
#define POWERREG1_ADRS 0x04
#define GPOR3_ADRS 0x05
#define PICTRL_ADRS 0x06
#define POLCTRL_ADRS 0x07
/* Register Bit Definitions */
#define RESCTL_QVGA 0x01
#define RESCTL_VGA 0x00
#define POWER1_VW_ON 0x01 /* VW Supply FET ON */
#define POWER1_GVSS_ON 0x02 /* GVSS(-8V) Power Supply ON */
#define POWER1_VDD_ON 0x04 /* VDD(8V),SVSS(-4V) Power Supply ON */
#define POWER1_VW_OFF 0x00 /* VW Supply FET OFF */
#define POWER1_GVSS_OFF 0x00 /* GVSS(-8V) Power Supply OFF */
#define POWER1_VDD_OFF 0x00 /* VDD(8V),SVSS(-4V) Power Supply OFF */
#define POWER0_COM_DCLK 0x01 /* COM Voltage DC Bias DAC Serial Data Clock */
#define POWER0_COM_DOUT 0x02 /* COM Voltage DC Bias DAC Serial Data Out */
#define POWER0_DAC_ON 0x04 /* DAC Power Supply ON */
#define POWER0_COM_ON 0x08 /* COM Power Supply ON */
#define POWER0_VCC5_ON 0x10 /* VCC5 Power Supply ON */
#define POWER0_DAC_OFF 0x00 /* DAC Power Supply OFF */
#define POWER0_COM_OFF 0x00 /* COM Power Supply OFF */
#define POWER0_VCC5_OFF 0x00 /* VCC5 Power Supply OFF */
#define PICTRL_INIT_STATE 0x01
#define PICTRL_INIOFF 0x02
#define PICTRL_POWER_DOWN 0x04
#define PICTRL_COM_SIGNAL_OFF 0x08
#define PICTRL_DAC_SIGNAL_OFF 0x10
#define POLCTRL_SYNC_POL_FALL 0x01
#define POLCTRL_EN_POL_FALL 0x02
#define POLCTRL_DATA_POL_FALL 0x04
#define POLCTRL_SYNC_ACT_H 0x08
#define POLCTRL_EN_ACT_L 0x10
#define POLCTRL_SYNC_POL_RISE 0x00
#define POLCTRL_EN_POL_RISE 0x00
#define POLCTRL_DATA_POL_RISE 0x00
#define POLCTRL_SYNC_ACT_L 0x00
#define POLCTRL_EN_ACT_H 0x00
#define PHACTRL_PHASE_MANUAL 0x01
#define DEFAULT_PHAD_QVGA (9)
#define DEFAULT_COMADJ (125)
struct corgi_lcd {
struct spi_device *spi_dev;
struct lcd_device *lcd_dev;
struct backlight_device *bl_dev;
int limit_mask;
int intensity;
int power;
int mode;
char buf[2];
int gpio_backlight_on;
int gpio_backlight_cont;
int gpio_backlight_cont_inverted;
void (*kick_battery)(void);
};
static int corgi_ssp_lcdtg_send(struct corgi_lcd *lcd, int reg, uint8_t val);
static struct corgi_lcd *the_corgi_lcd;
static unsigned long corgibl_flags;
#define CORGIBL_SUSPENDED 0x01
#define CORGIBL_BATTLOW 0x02
/*
* This is only a pseudo I2C interface. We can't use the standard kernel
* routines as the interface is write only. We just assume the data is acked...
*/
static void lcdtg_ssp_i2c_send(struct corgi_lcd *lcd, uint8_t data)
{
corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, data);
udelay(10);
}
static void lcdtg_i2c_send_bit(struct corgi_lcd *lcd, uint8_t data)
{
lcdtg_ssp_i2c_send(lcd, data);
lcdtg_ssp_i2c_send(lcd, data | POWER0_COM_DCLK);
lcdtg_ssp_i2c_send(lcd, data);
}
static void lcdtg_i2c_send_start(struct corgi_lcd *lcd, uint8_t base)
{
lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK | POWER0_COM_DOUT);
lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK);
lcdtg_ssp_i2c_send(lcd, base);
}
static void lcdtg_i2c_send_stop(struct corgi_lcd *lcd, uint8_t base)
{
lcdtg_ssp_i2c_send(lcd, base);
lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK);
lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK | POWER0_COM_DOUT);
}
static void lcdtg_i2c_send_byte(struct corgi_lcd *lcd,
uint8_t base, uint8_t data)
{
int i;
for (i = 0; i < 8; i++) {
if (data & 0x80)
lcdtg_i2c_send_bit(lcd, base | POWER0_COM_DOUT);
else
lcdtg_i2c_send_bit(lcd, base);
data <<= 1;
}
}
static void lcdtg_i2c_wait_ack(struct corgi_lcd *lcd, uint8_t base)
{
lcdtg_i2c_send_bit(lcd, base);
}
static void lcdtg_set_common_voltage(struct corgi_lcd *lcd,
uint8_t base_data, uint8_t data)
{
/* Set Common Voltage to M62332FP via I2C */
lcdtg_i2c_send_start(lcd, base_data);
lcdtg_i2c_send_byte(lcd, base_data, 0x9c);
lcdtg_i2c_wait_ack(lcd, base_data);
lcdtg_i2c_send_byte(lcd, base_data, 0x00);
lcdtg_i2c_wait_ack(lcd, base_data);
lcdtg_i2c_send_byte(lcd, base_data, data);
lcdtg_i2c_wait_ack(lcd, base_data);
lcdtg_i2c_send_stop(lcd, base_data);
}
static int corgi_ssp_lcdtg_send(struct corgi_lcd *lcd, int adrs, uint8_t data)
{
struct spi_message msg;
struct spi_transfer xfer = {
.len = 1,
.cs_change = 1,
.tx_buf = lcd->buf,
};
lcd->buf[0] = ((adrs & 0x07) << 5) | (data & 0x1f);
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
return spi_sync(lcd->spi_dev, &msg);
}
/* Set Phase Adjust */
static void lcdtg_set_phadadj(struct corgi_lcd *lcd, int mode)
{
int adj;
switch (mode) {
case CORGI_LCD_MODE_VGA:
/* Setting for VGA */
adj = sharpsl_param.phadadj;
adj = (adj < 0) ? PHACTRL_PHASE_MANUAL :
PHACTRL_PHASE_MANUAL | ((adj & 0xf) << 1);
break;
case CORGI_LCD_MODE_QVGA:
default:
/* Setting for QVGA */
adj = (DEFAULT_PHAD_QVGA << 1) | PHACTRL_PHASE_MANUAL;
break;
}
corgi_ssp_lcdtg_send(lcd, PHACTRL_ADRS, adj);
}
static void corgi_lcd_power_on(struct corgi_lcd *lcd)
{
int comadj;
/* Initialize Internal Logic & Port */
corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS,
PICTRL_POWER_DOWN | PICTRL_INIOFF |
PICTRL_INIT_STATE | PICTRL_COM_SIGNAL_OFF |
PICTRL_DAC_SIGNAL_OFF);
corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_OFF |
POWER0_COM_OFF | POWER0_VCC5_OFF);
corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_OFF);
/* VDD(+8V), SVSS(-4V) ON */
corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_ON);
mdelay(3);
/* DAC ON */
corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON |
POWER0_COM_OFF | POWER0_VCC5_OFF);
/* INIB = H, INI = L */
/* PICTL[0] = H , PICTL[1] = PICTL[2] = PICTL[4] = L */
corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS,
PICTRL_INIT_STATE | PICTRL_COM_SIGNAL_OFF);
/* Set Common Voltage */
comadj = sharpsl_param.comadj;
if (comadj < 0)
comadj = DEFAULT_COMADJ;
lcdtg_set_common_voltage(lcd, POWER0_DAC_ON | POWER0_COM_OFF |
POWER0_VCC5_OFF, comadj);
/* VCC5 ON, DAC ON */
corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON |
POWER0_COM_OFF | POWER0_VCC5_ON);
/* GVSS(-8V) ON, VDD ON */
corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
POWER1_VW_OFF | POWER1_GVSS_ON | POWER1_VDD_ON);
mdelay(2);
/* COM SIGNAL ON (PICTL[3] = L) */
corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, PICTRL_INIT_STATE);
/* COM ON, DAC ON, VCC5_ON */
corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON |
POWER0_COM_ON | POWER0_VCC5_ON);
/* VW ON, GVSS ON, VDD ON */
corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
POWER1_VW_ON | POWER1_GVSS_ON | POWER1_VDD_ON);
/* Signals output enable */
corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, 0);
/* Set Phase Adjust */
lcdtg_set_phadadj(lcd, lcd->mode);
/* Initialize for Input Signals from ATI */
corgi_ssp_lcdtg_send(lcd, POLCTRL_ADRS,
POLCTRL_SYNC_POL_RISE | POLCTRL_EN_POL_RISE |
POLCTRL_DATA_POL_RISE | POLCTRL_SYNC_ACT_L |
POLCTRL_EN_ACT_H);
udelay(1000);
switch (lcd->mode) {
case CORGI_LCD_MODE_VGA:
corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_VGA);
break;
case CORGI_LCD_MODE_QVGA:
default:
corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_QVGA);
break;
}
}
static void corgi_lcd_power_off(struct corgi_lcd *lcd)
{
/* 60Hz x 2 frame = 16.7msec x 2 = 33.4 msec */
msleep(34);
/* (1)VW OFF */
corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
POWER1_VW_OFF | POWER1_GVSS_ON | POWER1_VDD_ON);
/* (2)COM OFF */
corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, PICTRL_COM_SIGNAL_OFF);
corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
POWER0_DAC_ON | POWER0_COM_OFF | POWER0_VCC5_ON);
/* (3)Set Common Voltage Bias 0V */
lcdtg_set_common_voltage(lcd, POWER0_DAC_ON | POWER0_COM_OFF |
POWER0_VCC5_ON, 0);
/* (4)GVSS OFF */
corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_ON);
/* (5)VCC5 OFF */
corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
POWER0_DAC_ON | POWER0_COM_OFF | POWER0_VCC5_OFF);
/* (6)Set PDWN, INIOFF, DACOFF */
corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS,
PICTRL_INIOFF | PICTRL_DAC_SIGNAL_OFF |
PICTRL_POWER_DOWN | PICTRL_COM_SIGNAL_OFF);
/* (7)DAC OFF */
corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
POWER0_DAC_OFF | POWER0_COM_OFF | POWER0_VCC5_OFF);
/* (8)VDD OFF */
corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_OFF);
}
static int corgi_lcd_set_mode(struct lcd_device *ld, struct fb_videomode *m)
{
struct corgi_lcd *lcd = lcd_get_data(ld);
int mode = CORGI_LCD_MODE_QVGA;
if (m->xres == 640 || m->xres == 480)
mode = CORGI_LCD_MODE_VGA;
if (lcd->mode == mode)
return 0;
lcdtg_set_phadadj(lcd, mode);
switch (mode) {
case CORGI_LCD_MODE_VGA:
corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_VGA);
break;
case CORGI_LCD_MODE_QVGA:
default:
corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_QVGA);
break;
}
lcd->mode = mode;
return 0;
}
static int corgi_lcd_set_power(struct lcd_device *ld, int power)
{
struct corgi_lcd *lcd = lcd_get_data(ld);
if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power))
corgi_lcd_power_on(lcd);
if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power))
corgi_lcd_power_off(lcd);
lcd->power = power;
return 0;
}
static int corgi_lcd_get_power(struct lcd_device *ld)
{
struct corgi_lcd *lcd = lcd_get_data(ld);
return lcd->power;
}
static struct lcd_ops corgi_lcd_ops = {
.get_power = corgi_lcd_get_power,
.set_power = corgi_lcd_set_power,
.set_mode = corgi_lcd_set_mode,
};
static int corgi_bl_get_intensity(struct backlight_device *bd)
{
struct corgi_lcd *lcd = bl_get_data(bd);
return lcd->intensity;
}
static int corgi_bl_set_intensity(struct corgi_lcd *lcd, int intensity)
{
int cont;
if (intensity > 0x10)
intensity += 0x10;
corgi_ssp_lcdtg_send(lcd, DUTYCTRL_ADRS, intensity);
/* Bit 5 via GPIO_BACKLIGHT_CONT */
cont = !!(intensity & 0x20) ^ lcd->gpio_backlight_cont_inverted;
if (gpio_is_valid(lcd->gpio_backlight_cont))
backlight: corgi_lcd: Use gpio_set_value_cansleep() to avoid WARN_ON Changing backlight intensity on an Akita (Sharp Zaurus C-1000) triggers WARN_ON message: WARNING: at drivers/gpio/gpiolib.c:1672 __gpio_set_value+0x38/0xa4() Modules linked in: Backtrace: corgi_bl_set_intensity+0x0/0x74 corgi_bl_update_status+0x0/0x64 corgi_lcd_probe+0x0/0x258 spi_drv_probe+0x0/0x24 driver_probe_device+0x0/0x208 __driver_attach+0x0/0x94 bus_for_each_dev+0x0/0x90 driver_attach+0x0/0x28 bus_add_driver+0x0/0x22c driver_register+0x0/0x134 spi_register_driver+0x0/0x60 corgi_lcd_driver_init+0x0/0x1c do_one_initcall+0x0/0x174 kernel_init+0x0/0x2a8 Akita machines have backlight controls hooked to a gpio expander chip, max7310 using i2c transfers which can sleep. In this case, pca953x_gpio_set_value() can be called to control gpio, and pca953x_setup_gpio() sets can_sleep flag. Therefore, gpio_set_value_cansleep() should be used in order to avoid WARN_ON on akita machines. Akita is the only exception in this case since other users of corgi_lcd access backlight gpio controls through a different gpio expander which does not set the can_sleep flag. Signed-off-by: Marko Katic <dromede@gmail.com> Signed-off-by: Jingoo Han <jg1.han@samsung.com> Cc: Russell King <linux@arm.linux.org.uk> Cc: Grant Likely <grant.likely@secretlab.ca> Cc: Florian Tobias Schandinat <FlorianSchandinat@gmx.de> Cc: Richard Purdie <rpurdie@rpsys.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2012-12-18 08:01:14 +08:00
gpio_set_value_cansleep(lcd->gpio_backlight_cont, cont);
if (gpio_is_valid(lcd->gpio_backlight_on))
backlight: corgi_lcd: Use gpio_set_value_cansleep() to avoid WARN_ON Changing backlight intensity on an Akita (Sharp Zaurus C-1000) triggers WARN_ON message: WARNING: at drivers/gpio/gpiolib.c:1672 __gpio_set_value+0x38/0xa4() Modules linked in: Backtrace: corgi_bl_set_intensity+0x0/0x74 corgi_bl_update_status+0x0/0x64 corgi_lcd_probe+0x0/0x258 spi_drv_probe+0x0/0x24 driver_probe_device+0x0/0x208 __driver_attach+0x0/0x94 bus_for_each_dev+0x0/0x90 driver_attach+0x0/0x28 bus_add_driver+0x0/0x22c driver_register+0x0/0x134 spi_register_driver+0x0/0x60 corgi_lcd_driver_init+0x0/0x1c do_one_initcall+0x0/0x174 kernel_init+0x0/0x2a8 Akita machines have backlight controls hooked to a gpio expander chip, max7310 using i2c transfers which can sleep. In this case, pca953x_gpio_set_value() can be called to control gpio, and pca953x_setup_gpio() sets can_sleep flag. Therefore, gpio_set_value_cansleep() should be used in order to avoid WARN_ON on akita machines. Akita is the only exception in this case since other users of corgi_lcd access backlight gpio controls through a different gpio expander which does not set the can_sleep flag. Signed-off-by: Marko Katic <dromede@gmail.com> Signed-off-by: Jingoo Han <jg1.han@samsung.com> Cc: Russell King <linux@arm.linux.org.uk> Cc: Grant Likely <grant.likely@secretlab.ca> Cc: Florian Tobias Schandinat <FlorianSchandinat@gmx.de> Cc: Richard Purdie <rpurdie@rpsys.net> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2012-12-18 08:01:14 +08:00
gpio_set_value_cansleep(lcd->gpio_backlight_on, intensity);
if (lcd->kick_battery)
lcd->kick_battery();
lcd->intensity = intensity;
return 0;
}
static int corgi_bl_update_status(struct backlight_device *bd)
{
struct corgi_lcd *lcd = bl_get_data(bd);
int intensity = bd->props.brightness;
if (bd->props.power != FB_BLANK_UNBLANK)
intensity = 0;
if (bd->props.fb_blank != FB_BLANK_UNBLANK)
intensity = 0;
if (corgibl_flags & CORGIBL_SUSPENDED)
intensity = 0;
if ((corgibl_flags & CORGIBL_BATTLOW) && intensity > lcd->limit_mask)
intensity = lcd->limit_mask;
return corgi_bl_set_intensity(lcd, intensity);
}
void corgi_lcd_limit_intensity(int limit)
{
if (limit)
corgibl_flags |= CORGIBL_BATTLOW;
else
corgibl_flags &= ~CORGIBL_BATTLOW;
backlight_update_status(the_corgi_lcd->bl_dev);
}
EXPORT_SYMBOL(corgi_lcd_limit_intensity);
static const struct backlight_ops corgi_bl_ops = {
.get_brightness = corgi_bl_get_intensity,
.update_status = corgi_bl_update_status,
};
#ifdef CONFIG_PM_SLEEP
static int corgi_lcd_suspend(struct device *dev)
{
struct corgi_lcd *lcd = dev_get_drvdata(dev);
corgibl_flags |= CORGIBL_SUSPENDED;
corgi_bl_set_intensity(lcd, 0);
corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN);
return 0;
}
static int corgi_lcd_resume(struct device *dev)
{
struct corgi_lcd *lcd = dev_get_drvdata(dev);
corgibl_flags &= ~CORGIBL_SUSPENDED;
corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK);
backlight_update_status(lcd->bl_dev);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(corgi_lcd_pm_ops, corgi_lcd_suspend, corgi_lcd_resume);
static int setup_gpio_backlight(struct corgi_lcd *lcd,
struct corgi_lcd_platform_data *pdata)
{
struct spi_device *spi = lcd->spi_dev;
int err;
lcd->gpio_backlight_on = -1;
lcd->gpio_backlight_cont = -1;
if (gpio_is_valid(pdata->gpio_backlight_on)) {
err = devm_gpio_request(&spi->dev, pdata->gpio_backlight_on,
"BL_ON");
if (err) {
dev_err(&spi->dev,
"failed to request GPIO%d for backlight_on\n",
pdata->gpio_backlight_on);
return err;
}
lcd->gpio_backlight_on = pdata->gpio_backlight_on;
gpio_direction_output(lcd->gpio_backlight_on, 0);
}
if (gpio_is_valid(pdata->gpio_backlight_cont)) {
err = devm_gpio_request(&spi->dev, pdata->gpio_backlight_cont,
"BL_CONT");
if (err) {
dev_err(&spi->dev,
"failed to request GPIO%d for backlight_cont\n",
pdata->gpio_backlight_cont);
return err;
}
lcd->gpio_backlight_cont = pdata->gpio_backlight_cont;
/* spitz and akita use both GPIOs for backlight, and
* have inverted polarity of GPIO_BACKLIGHT_CONT
*/
if (gpio_is_valid(lcd->gpio_backlight_on)) {
lcd->gpio_backlight_cont_inverted = 1;
gpio_direction_output(lcd->gpio_backlight_cont, 1);
} else {
lcd->gpio_backlight_cont_inverted = 0;
gpio_direction_output(lcd->gpio_backlight_cont, 0);
}
}
return 0;
}
static int corgi_lcd_probe(struct spi_device *spi)
{
struct backlight_properties props;
struct corgi_lcd_platform_data *pdata = spi->dev.platform_data;
struct corgi_lcd *lcd;
int ret = 0;
if (pdata == NULL) {
dev_err(&spi->dev, "platform data not available\n");
return -EINVAL;
}
lcd = devm_kzalloc(&spi->dev, sizeof(struct corgi_lcd), GFP_KERNEL);
if (!lcd) {
dev_err(&spi->dev, "failed to allocate memory\n");
return -ENOMEM;
}
lcd->spi_dev = spi;
lcd->lcd_dev = lcd_device_register("corgi_lcd", &spi->dev,
lcd, &corgi_lcd_ops);
if (IS_ERR(lcd->lcd_dev))
return PTR_ERR(lcd->lcd_dev);
lcd->power = FB_BLANK_POWERDOWN;
lcd->mode = (pdata) ? pdata->init_mode : CORGI_LCD_MODE_VGA;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = pdata->max_intensity;
lcd->bl_dev = backlight_device_register("corgi_bl", &spi->dev, lcd,
&corgi_bl_ops, &props);
if (IS_ERR(lcd->bl_dev)) {
ret = PTR_ERR(lcd->bl_dev);
goto err_unregister_lcd;
}
lcd->bl_dev->props.brightness = pdata->default_intensity;
lcd->bl_dev->props.power = FB_BLANK_UNBLANK;
ret = setup_gpio_backlight(lcd, pdata);
if (ret)
goto err_unregister_bl;
lcd->kick_battery = pdata->kick_battery;
spi_set_drvdata(spi, lcd);
corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK);
backlight_update_status(lcd->bl_dev);
lcd->limit_mask = pdata->limit_mask;
the_corgi_lcd = lcd;
return 0;
err_unregister_bl:
backlight_device_unregister(lcd->bl_dev);
err_unregister_lcd:
lcd_device_unregister(lcd->lcd_dev);
return ret;
}
static int corgi_lcd_remove(struct spi_device *spi)
{
struct corgi_lcd *lcd = spi_get_drvdata(spi);
lcd->bl_dev->props.power = FB_BLANK_UNBLANK;
lcd->bl_dev->props.brightness = 0;
backlight_update_status(lcd->bl_dev);
backlight_device_unregister(lcd->bl_dev);
corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN);
lcd_device_unregister(lcd->lcd_dev);
return 0;
}
static struct spi_driver corgi_lcd_driver = {
.driver = {
.name = "corgi-lcd",
.owner = THIS_MODULE,
.pm = &corgi_lcd_pm_ops,
},
.probe = corgi_lcd_probe,
.remove = corgi_lcd_remove,
};
module_spi_driver(corgi_lcd_driver);
MODULE_DESCRIPTION("LCD and backlight driver for SHARP C7x0/Cxx00");
MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:corgi-lcd");