Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

View file

@ -0,0 +1,277 @@
/*
* Backlight driver for Marvell Semiconductor 88PM8606
*
* Copyright (C) 2009 Marvell International Ltd.
* Haojian Zhuang <haojian.zhuang@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/init.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/fb.h>
#include <linux/i2c.h>
#include <linux/backlight.h>
#include <linux/mfd/88pm860x.h>
#include <linux/module.h>
#define MAX_BRIGHTNESS (0xFF)
#define MIN_BRIGHTNESS (0)
#define CURRENT_BITMASK (0x1F << 1)
struct pm860x_backlight_data {
struct pm860x_chip *chip;
struct i2c_client *i2c;
int current_brightness;
int port;
int pwm;
int iset;
int reg_duty_cycle;
int reg_always_on;
int reg_current;
};
static int backlight_power_set(struct pm860x_chip *chip, int port,
int on)
{
int ret = -EINVAL;
switch (port) {
case 0:
ret = on ? pm8606_osc_enable(chip, WLED1_DUTY) :
pm8606_osc_disable(chip, WLED1_DUTY);
break;
case 1:
ret = on ? pm8606_osc_enable(chip, WLED2_DUTY) :
pm8606_osc_disable(chip, WLED2_DUTY);
break;
case 2:
ret = on ? pm8606_osc_enable(chip, WLED3_DUTY) :
pm8606_osc_disable(chip, WLED3_DUTY);
break;
}
return ret;
}
static int pm860x_backlight_set(struct backlight_device *bl, int brightness)
{
struct pm860x_backlight_data *data = bl_get_data(bl);
struct pm860x_chip *chip = data->chip;
unsigned char value;
int ret;
if (brightness > MAX_BRIGHTNESS)
value = MAX_BRIGHTNESS;
else
value = brightness;
if (brightness)
backlight_power_set(chip, data->port, 1);
ret = pm860x_reg_write(data->i2c, data->reg_duty_cycle, value);
if (ret < 0)
goto out;
if ((data->current_brightness == 0) && brightness) {
if (data->iset) {
ret = pm860x_set_bits(data->i2c, data->reg_current,
CURRENT_BITMASK, data->iset);
if (ret < 0)
goto out;
}
if (data->pwm) {
ret = pm860x_set_bits(data->i2c, PM8606_PWM,
PM8606_PWM_FREQ_MASK, data->pwm);
if (ret < 0)
goto out;
}
if (brightness == MAX_BRIGHTNESS) {
/* set WLED_ON bit as 100% */
ret = pm860x_set_bits(data->i2c, data->reg_always_on,
PM8606_WLED_ON, PM8606_WLED_ON);
}
} else {
if (brightness == MAX_BRIGHTNESS) {
/* set WLED_ON bit as 100% */
ret = pm860x_set_bits(data->i2c, data->reg_always_on,
PM8606_WLED_ON, PM8606_WLED_ON);
} else {
/* clear WLED_ON bit since it's not 100% */
ret = pm860x_set_bits(data->i2c, data->reg_always_on,
PM8606_WLED_ON, 0);
}
}
if (ret < 0)
goto out;
if (brightness == 0)
backlight_power_set(chip, data->port, 0);
dev_dbg(chip->dev, "set brightness %d\n", value);
data->current_brightness = value;
return 0;
out:
dev_dbg(chip->dev, "set brightness %d failure with return value: %d\n",
value, ret);
return ret;
}
static int pm860x_backlight_update_status(struct backlight_device *bl)
{
int brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.state & BL_CORE_SUSPENDED)
brightness = 0;
return pm860x_backlight_set(bl, brightness);
}
static int pm860x_backlight_get_brightness(struct backlight_device *bl)
{
struct pm860x_backlight_data *data = bl_get_data(bl);
struct pm860x_chip *chip = data->chip;
int ret;
ret = pm860x_reg_read(data->i2c, data->reg_duty_cycle);
if (ret < 0)
goto out;
data->current_brightness = ret;
dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness);
return data->current_brightness;
out:
return -EINVAL;
}
static const struct backlight_ops pm860x_backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = pm860x_backlight_update_status,
.get_brightness = pm860x_backlight_get_brightness,
};
#ifdef CONFIG_OF
static int pm860x_backlight_dt_init(struct platform_device *pdev,
struct pm860x_backlight_data *data,
char *name)
{
struct device_node *nproot, *np;
int iset = 0;
nproot = of_node_get(pdev->dev.parent->of_node);
if (!nproot)
return -ENODEV;
nproot = of_find_node_by_name(nproot, "backlights");
if (!nproot) {
dev_err(&pdev->dev, "failed to find backlights node\n");
return -ENODEV;
}
for_each_child_of_node(nproot, np) {
if (!of_node_cmp(np->name, name)) {
of_property_read_u32(np, "marvell,88pm860x-iset",
&iset);
data->iset = PM8606_WLED_CURRENT(iset);
of_property_read_u32(np, "marvell,88pm860x-pwm",
&data->pwm);
break;
}
}
of_node_put(nproot);
return 0;
}
#else
#define pm860x_backlight_dt_init(x, y, z) (-1)
#endif
static int pm860x_backlight_probe(struct platform_device *pdev)
{
struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
struct pm860x_backlight_pdata *pdata = dev_get_platdata(&pdev->dev);
struct pm860x_backlight_data *data;
struct backlight_device *bl;
struct resource *res;
struct backlight_properties props;
char name[MFD_NAME_SIZE];
int ret = 0;
data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_backlight_data),
GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
res = platform_get_resource_byname(pdev, IORESOURCE_REG, "duty cycle");
if (!res) {
dev_err(&pdev->dev, "No REG resource for duty cycle\n");
return -ENXIO;
}
data->reg_duty_cycle = res->start;
res = platform_get_resource_byname(pdev, IORESOURCE_REG, "always on");
if (!res) {
dev_err(&pdev->dev, "No REG resource for always on\n");
return -ENXIO;
}
data->reg_always_on = res->start;
res = platform_get_resource_byname(pdev, IORESOURCE_REG, "current");
if (!res) {
dev_err(&pdev->dev, "No REG resource for current\n");
return -ENXIO;
}
data->reg_current = res->start;
memset(name, 0, MFD_NAME_SIZE);
sprintf(name, "backlight-%d", pdev->id);
data->port = pdev->id;
data->chip = chip;
data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion;
data->current_brightness = MAX_BRIGHTNESS;
if (pm860x_backlight_dt_init(pdev, data, name)) {
if (pdata) {
data->pwm = pdata->pwm;
data->iset = pdata->iset;
}
}
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = MAX_BRIGHTNESS;
bl = devm_backlight_device_register(&pdev->dev, name, &pdev->dev, data,
&pm860x_backlight_ops, &props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
bl->props.brightness = MAX_BRIGHTNESS;
platform_set_drvdata(pdev, bl);
/* read current backlight */
ret = pm860x_backlight_get_brightness(bl);
if (ret < 0)
return ret;
backlight_update_status(bl);
return 0;
}
static struct platform_driver pm860x_backlight_driver = {
.driver = {
.name = "88pm860x-backlight",
},
.probe = pm860x_backlight_probe,
};
module_platform_driver(pm860x_backlight_driver);
MODULE_DESCRIPTION("Backlight Driver for Marvell Semiconductor 88PM8606");
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:88pm860x-backlight");

View file

@ -0,0 +1,447 @@
#
# Backlight & LCD drivers configuration
#
menuconfig BACKLIGHT_LCD_SUPPORT
bool "Backlight & LCD device support"
help
Enable this to be able to choose the drivers for controlling the
backlight and the LCD panel on some platforms, for example on PDAs.
if BACKLIGHT_LCD_SUPPORT
#
# LCD
#
config LCD_CLASS_DEVICE
tristate "Lowlevel LCD controls"
default m
help
This framework adds support for low-level control of LCD.
Some framebuffer devices connect to platform-specific LCD modules
in order to have a platform-specific way to control the flat panel
(contrast and applying power to the LCD (not to the backlight!)).
To have support for your specific LCD panel you will have to
select the proper drivers which depend on this option.
if LCD_CLASS_DEVICE
config LCD_CORGI
tristate "LCD Panel support for SHARP corgi/spitz model"
depends on SPI_MASTER && PXA_SHARPSL && BACKLIGHT_CLASS_DEVICE
help
Say y here to support the LCD panels usually found on SHARP
corgi (C7x0) and spitz (Cxx00) models.
config LCD_L4F00242T03
tristate "Epson L4F00242T03 LCD"
depends on SPI_MASTER && GPIOLIB
help
SPI driver for Epson L4F00242T03. This provides basic support
for init and powering the LCD up/down through a sysfs interface.
config LCD_LMS283GF05
tristate "Samsung LMS283GF05 LCD"
depends on SPI_MASTER && GPIOLIB
help
SPI driver for Samsung LMS283GF05. This provides basic support
for powering the LCD up/down through a sysfs interface.
config LCD_LTV350QV
tristate "Samsung LTV350QV LCD Panel"
depends on SPI_MASTER
help
If you have a Samsung LTV350QV LCD panel, say y to include a
power control driver for it. The panel starts up in power
off state, so you need this driver in order to see any
output.
The LTV350QV panel is present on all ATSTK1000 boards.
config LCD_ILI922X
tristate "ILI Technology ILI9221/ILI9222 support"
depends on SPI
help
If you have a panel based on the ILI9221/9222 controller
chip then say y to include a driver for it.
config LCD_ILI9320
tristate "ILI Technology ILI9320 controller support"
depends on SPI
help
If you have a panel based on the ILI9320 controller chip
then say y to include a power driver for it.
config LCD_TDO24M
tristate "Toppoly TDO24M and TDO35S LCD Panels support"
depends on SPI_MASTER
help
If you have a Toppoly TDO24M/TDO35S series LCD panel, say y here to
include the support for it.
config LCD_VGG2432A4
tristate "VGG2432A4 LCM device support"
depends on SPI_MASTER
select LCD_ILI9320
help
If you have a VGG2432A4 panel based on the ILI9320 controller chip
then say y to include a power driver for it.
config LCD_PLATFORM
tristate "Platform LCD controls"
help
This driver provides a platform-device registered LCD power
control interface.
config LCD_TOSA
tristate "Sharp SL-6000 LCD Driver"
depends on I2C && SPI && MACH_TOSA
help
If you have an Sharp SL-6000 Zaurus say Y to enable a driver
for its LCD.
config LCD_HP700
tristate "HP Jornada 700 series LCD Driver"
depends on SA1100_JORNADA720_SSP && !PREEMPT
default y
help
If you have an HP Jornada 700 series handheld (710/720/728)
say Y to enable LCD control driver.
config LCD_S6E63M0
tristate "S6E63M0 AMOLED LCD Driver"
depends on SPI && BACKLIGHT_CLASS_DEVICE
default n
help
If you have an S6E63M0 LCD Panel, say Y to enable its
LCD control driver.
config LCD_LD9040
tristate "LD9040 AMOLED LCD Driver"
depends on SPI && BACKLIGHT_CLASS_DEVICE
default n
help
If you have an LD9040 Panel, say Y to enable its
control driver.
config LCD_AMS369FG06
tristate "AMS369FG06 AMOLED LCD Driver"
depends on SPI && BACKLIGHT_CLASS_DEVICE
default n
help
If you have an AMS369FG06 AMOLED Panel, say Y to enable its
LCD control driver.
config LCD_LMS501KF03
tristate "LMS501KF03 LCD Driver"
depends on SPI
default n
help
If you have an LMS501KF03 LCD Panel, say Y to enable its
LCD control driver.
config LCD_HX8357
tristate "Himax HX-8357 LCD Driver"
depends on SPI
help
If you have a HX-8357 LCD panel, say Y to enable its LCD control
driver.
endif # LCD_CLASS_DEVICE
#
# Backlight
#
config BACKLIGHT_CLASS_DEVICE
tristate "Lowlevel Backlight controls"
default m
help
This framework adds support for low-level control of the LCD
backlight. This includes support for brightness and power.
To have support for your specific LCD panel you will have to
select the proper drivers which depend on this option.
if BACKLIGHT_CLASS_DEVICE
config BACKLIGHT_ATMEL_LCDC
bool "Atmel LCDC Contrast-as-Backlight control"
depends on FB_ATMEL
default y if MACH_AT91SAM9261EK || MACH_AT91SAM9G10EK || MACH_AT91SAM9263EK
help
This provides a backlight control internal to the Atmel LCDC
driver. If the LCD "contrast control" on your board is wired
so it controls the backlight brightness, select this option to
export this as a PWM-based backlight control.
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_EP93XX
tristate "Cirrus EP93xx Backlight Driver"
depends on FB_EP93XX
help
If you have a LCD backlight connected to the BRIGHT output of
the EP93xx, say Y here to enable this driver.
To compile this driver as a module, choose M here: the module will
be called ep93xx_bl.
config BACKLIGHT_GENERIC
tristate "Generic (aka Sharp Corgi) Backlight Driver"
default y
help
Say y to enable the generic platform backlight driver previously
known as the Corgi backlight driver. If you have a Sharp Zaurus
SL-C7xx, SL-Cxx00 or SL-6000x say y.
config BACKLIGHT_IPAQ_MICRO
tristate "iPAQ microcontroller backlight driver"
depends on MFD_IPAQ_MICRO
default y
help
Say y to enable the backlight driver for Compaq iPAQ handheld
computers. Say yes if you have one of the h3100/h3600/h3700
machines.
config BACKLIGHT_LM3533
tristate "Backlight Driver for LM3533"
depends on BACKLIGHT_CLASS_DEVICE
depends on MFD_LM3533
help
Say Y to enable the backlight driver for National Semiconductor / TI
LM3533 Lighting Power chips.
The backlights can be controlled directly, through PWM input, or by
the ambient-light-sensor interface. The chip supports 256 brightness
levels.
config BACKLIGHT_LOCOMO
tristate "Sharp LOCOMO LCD/Backlight Driver"
depends on SHARP_LOCOMO
default y
help
If you have a Sharp Zaurus SL-5500 (Collie) or SL-5600 (Poodle) say y to
enable the LCD/backlight driver.
config BACKLIGHT_OMAP1
tristate "OMAP1 PWL-based LCD Backlight"
depends on ARCH_OMAP1
default y
help
This driver controls the LCD backlight level and power for
the PWL module of OMAP1 processors. Say Y if your board
uses this hardware.
config BACKLIGHT_HP680
tristate "HP Jornada 680 Backlight Driver"
depends on SH_HP6XX
default y
help
If you have a HP Jornada 680, say y to enable the
backlight driver.
config BACKLIGHT_HP700
tristate "HP Jornada 700 series Backlight Driver"
depends on SA1100_JORNADA720_SSP && !PREEMPT
default y
help
If you have an HP Jornada 700 series,
say Y to include backlight control driver.
config BACKLIGHT_CARILLO_RANCH
tristate "Intel Carillo Ranch Backlight Driver"
depends on LCD_CLASS_DEVICE && PCI && X86 && FB_LE80578
help
If you have a Intel LE80578 (Carillo Ranch) say Y to enable the
backlight driver.
config BACKLIGHT_PWM
tristate "Generic PWM based Backlight Driver"
depends on PWM
help
If you have a LCD backlight adjustable by PWM, say Y to enable
this driver.
config BACKLIGHT_DA903X
tristate "Backlight Driver for DA9030/DA9034 using WLED"
depends on PMIC_DA903X
help
If you have a LCD backlight connected to the WLED output of DA9030
or DA9034 WLED output, say Y here to enable this driver.
config BACKLIGHT_DA9052
tristate "Dialog DA9052/DA9053 WLED"
depends on PMIC_DA9052
help
Enable the Backlight Driver for DA9052-BC and DA9053-AA/Bx PMICs.
config BACKLIGHT_MAX8925
tristate "Backlight driver for MAX8925"
depends on MFD_MAX8925
help
If you have a LCD backlight connected to the WLED output of MAX8925
WLED output, say Y here to enable this driver.
config BACKLIGHT_APPLE
tristate "Apple Backlight Driver"
depends on X86 && ACPI
help
If you have an Intel-based Apple say Y to enable a driver for its
backlight.
config BACKLIGHT_TOSA
tristate "Sharp SL-6000 Backlight Driver"
depends on I2C && MACH_TOSA && LCD_TOSA
help
If you have an Sharp SL-6000 Zaurus say Y to enable a driver
for its backlight
config BACKLIGHT_SAHARA
tristate "Tabletkiosk Sahara Touch-iT Backlight Driver"
depends on X86
help
If you have a Tabletkiosk Sahara Touch-iT, say y to enable the
backlight driver.
config BACKLIGHT_WM831X
tristate "WM831x PMIC Backlight Driver"
depends on MFD_WM831X
help
If you have a backlight driven by the ISINK and DCDC of a
WM831x PMIC say y to enable the backlight driver for it.
config BACKLIGHT_ADP5520
tristate "Backlight Driver for ADP5520/ADP5501 using WLED"
depends on PMIC_ADP5520
help
If you have a LCD backlight connected to the BST/BL_SNK output of
ADP5520 or ADP5501, say Y here to enable this driver.
To compile this driver as a module, choose M here: the module will
be called adp5520_bl.
config BACKLIGHT_ADP8860
tristate "Backlight Driver for ADP8860/ADP8861/ADP8863 using WLED"
depends on BACKLIGHT_CLASS_DEVICE && I2C
select NEW_LEDS
select LEDS_CLASS
help
If you have a LCD backlight connected to the ADP8860, ADP8861 or
ADP8863 say Y here to enable this driver.
To compile this driver as a module, choose M here: the module will
be called adp8860_bl.
config BACKLIGHT_ADP8870
tristate "Backlight Driver for ADP8870 using WLED"
depends on BACKLIGHT_CLASS_DEVICE && I2C
select NEW_LEDS
select LEDS_CLASS
help
If you have a LCD backlight connected to the ADP8870,
say Y here to enable this driver.
To compile this driver as a module, choose M here: the module will
be called adp8870_bl.
config BACKLIGHT_88PM860X
tristate "Backlight Driver for 88PM8606 using WLED"
depends on MFD_88PM860X
help
Say Y to enable the backlight driver for Marvell 88PM8606.
config BACKLIGHT_PCF50633
tristate "Backlight driver for NXP PCF50633 MFD"
depends on BACKLIGHT_CLASS_DEVICE && MFD_PCF50633
help
If you have a backlight driven by a NXP PCF50633 MFD, say Y here to
enable its driver.
config BACKLIGHT_AAT2870
tristate "AnalogicTech AAT2870 Backlight"
depends on BACKLIGHT_CLASS_DEVICE && MFD_AAT2870_CORE
help
If you have a AnalogicTech AAT2870 say Y to enable the
backlight driver.
config BACKLIGHT_LM3630A
tristate "Backlight Driver for LM3630A"
depends on BACKLIGHT_CLASS_DEVICE && I2C && PWM
select REGMAP_I2C
help
This supports TI LM3630A Backlight Driver
config BACKLIGHT_LM3639
tristate "Backlight Driver for LM3639"
depends on BACKLIGHT_CLASS_DEVICE && I2C
select REGMAP_I2C
select NEW_LEDS
select LEDS_CLASS
help
This supports TI LM3639 Backlight + 1.5A Flash LED Driver
config BACKLIGHT_LP855X
tristate "Backlight driver for TI LP855X"
depends on BACKLIGHT_CLASS_DEVICE && I2C && PWM
help
This supports TI LP8550, LP8551, LP8552, LP8553, LP8555, LP8556 and
LP8557 backlight driver.
config BACKLIGHT_LP8788
tristate "Backlight driver for TI LP8788 MFD"
depends on BACKLIGHT_CLASS_DEVICE && MFD_LP8788 && PWM
help
This supports TI LP8788 backlight driver.
config BACKLIGHT_OT200
tristate "Backlight driver for ot200 visualisation device"
depends on BACKLIGHT_CLASS_DEVICE && CS5535_MFGPT && GPIO_CS5535
help
To compile this driver as a module, choose M here: the module will be
called ot200_bl.
config BACKLIGHT_PANDORA
tristate "Backlight driver for Pandora console"
depends on TWL4030_CORE
help
If you have a Pandora console, say Y to enable the
backlight driver.
config BACKLIGHT_TPS65217
tristate "TPS65217 Backlight"
depends on BACKLIGHT_CLASS_DEVICE && MFD_TPS65217
help
If you have a Texas Instruments TPS65217 say Y to enable the
backlight driver.
config BACKLIGHT_AS3711
tristate "AS3711 Backlight"
depends on BACKLIGHT_CLASS_DEVICE && MFD_AS3711
help
If you have an Austrian Microsystems AS3711 say Y to enable the
backlight driver.
config BACKLIGHT_GPIO
tristate "Generic GPIO based Backlight Driver"
depends on GPIOLIB
help
If you have a LCD backlight adjustable by GPIO, say Y to enable
this driver.
config BACKLIGHT_LV5207LP
tristate "Sanyo LV5207LP Backlight"
depends on I2C
help
If you have a Sanyo LV5207LP say Y to enable the backlight driver.
config BACKLIGHT_BD6107
tristate "Rohm BD6107 Backlight"
depends on I2C
help
If you have a Rohm BD6107 say Y to enable the backlight driver.
endif # BACKLIGHT_CLASS_DEVICE
endif # BACKLIGHT_LCD_SUPPORT

View file

@ -0,0 +1,55 @@
# Backlight & LCD drivers
obj-$(CONFIG_LCD_AMS369FG06) += ams369fg06.o
obj-$(CONFIG_LCD_CLASS_DEVICE) += lcd.o
obj-$(CONFIG_LCD_CORGI) += corgi_lcd.o
obj-$(CONFIG_LCD_HP700) += jornada720_lcd.o
obj-$(CONFIG_LCD_HX8357) += hx8357.o
obj-$(CONFIG_LCD_ILI922X) += ili922x.o
obj-$(CONFIG_LCD_ILI9320) += ili9320.o
obj-$(CONFIG_LCD_L4F00242T03) += l4f00242t03.o
obj-$(CONFIG_LCD_LD9040) += ld9040.o
obj-$(CONFIG_LCD_LMS283GF05) += lms283gf05.o
obj-$(CONFIG_LCD_LMS501KF03) += lms501kf03.o
obj-$(CONFIG_LCD_LTV350QV) += ltv350qv.o
obj-$(CONFIG_LCD_PLATFORM) += platform_lcd.o
obj-$(CONFIG_LCD_S6E63M0) += s6e63m0.o
obj-$(CONFIG_LCD_TDO24M) += tdo24m.o
obj-$(CONFIG_LCD_TOSA) += tosa_lcd.o
obj-$(CONFIG_LCD_VGG2432A4) += vgg2432a4.o
obj-$(CONFIG_BACKLIGHT_88PM860X) += 88pm860x_bl.o
obj-$(CONFIG_BACKLIGHT_AAT2870) += aat2870_bl.o
obj-$(CONFIG_BACKLIGHT_ADP5520) += adp5520_bl.o
obj-$(CONFIG_BACKLIGHT_ADP8860) += adp8860_bl.o
obj-$(CONFIG_BACKLIGHT_ADP8870) += adp8870_bl.o
obj-$(CONFIG_BACKLIGHT_APPLE) += apple_bl.o
obj-$(CONFIG_BACKLIGHT_AS3711) += as3711_bl.o
obj-$(CONFIG_BACKLIGHT_BD6107) += bd6107.o
obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o
obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o
obj-$(CONFIG_BACKLIGHT_DA903X) += da903x_bl.o
obj-$(CONFIG_BACKLIGHT_DA9052) += da9052_bl.o
obj-$(CONFIG_BACKLIGHT_EP93XX) += ep93xx_bl.o
obj-$(CONFIG_BACKLIGHT_GENERIC) += generic_bl.o
obj-$(CONFIG_BACKLIGHT_GPIO) += gpio_backlight.o
obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o
obj-$(CONFIG_BACKLIGHT_HP700) += jornada720_bl.o
obj-$(CONFIG_BACKLIGHT_IPAQ_MICRO) += ipaq_micro_bl.o
obj-$(CONFIG_BACKLIGHT_LM3533) += lm3533_bl.o
obj-$(CONFIG_BACKLIGHT_LM3630A) += lm3630a_bl.o
obj-$(CONFIG_BACKLIGHT_LM3639) += lm3639_bl.o
obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o
obj-$(CONFIG_BACKLIGHT_LP855X) += lp855x_bl.o
obj-$(CONFIG_BACKLIGHT_LP8788) += lp8788_bl.o
obj-$(CONFIG_BACKLIGHT_LV5207LP) += lv5207lp.o
obj-$(CONFIG_BACKLIGHT_MAX8925) += max8925_bl.o
obj-$(CONFIG_BACKLIGHT_OMAP1) += omap1_bl.o
obj-$(CONFIG_BACKLIGHT_OT200) += ot200_bl.o
obj-$(CONFIG_BACKLIGHT_PANDORA) += pandora_bl.o
obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o
obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o
obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o
obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o
obj-$(CONFIG_BACKLIGHT_TPS65217) += tps65217_bl.o
obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_bl.o

View file

@ -0,0 +1,233 @@
/*
* linux/drivers/video/backlight/aat2870_bl.c
*
* Copyright (c) 2011, NVIDIA Corporation.
* Author: Jin Park <jinyoungp@nvidia.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.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/mfd/aat2870.h>
struct aat2870_bl_driver_data {
struct platform_device *pdev;
struct backlight_device *bd;
int channels;
int max_current;
int brightness; /* current brightness */
};
static inline int aat2870_brightness(struct aat2870_bl_driver_data *aat2870_bl,
int brightness)
{
struct backlight_device *bd = aat2870_bl->bd;
int val;
val = brightness * (aat2870_bl->max_current - 1);
val /= bd->props.max_brightness;
return val;
}
static inline int aat2870_bl_enable(struct aat2870_bl_driver_data *aat2870_bl)
{
struct aat2870_data *aat2870
= dev_get_drvdata(aat2870_bl->pdev->dev.parent);
return aat2870->write(aat2870, AAT2870_BL_CH_EN,
(u8)aat2870_bl->channels);
}
static inline int aat2870_bl_disable(struct aat2870_bl_driver_data *aat2870_bl)
{
struct aat2870_data *aat2870
= dev_get_drvdata(aat2870_bl->pdev->dev.parent);
return aat2870->write(aat2870, AAT2870_BL_CH_EN, 0x0);
}
static int aat2870_bl_update_status(struct backlight_device *bd)
{
struct aat2870_bl_driver_data *aat2870_bl = bl_get_data(bd);
struct aat2870_data *aat2870 =
dev_get_drvdata(aat2870_bl->pdev->dev.parent);
int brightness = bd->props.brightness;
int ret;
if ((brightness < 0) || (bd->props.max_brightness < brightness)) {
dev_err(&bd->dev, "invalid brightness, %d\n", brightness);
return -EINVAL;
}
dev_dbg(&bd->dev, "brightness=%d, power=%d, state=%d\n",
bd->props.brightness, bd->props.power, bd->props.state);
if ((bd->props.power != FB_BLANK_UNBLANK) ||
(bd->props.state & BL_CORE_FBBLANK) ||
(bd->props.state & BL_CORE_SUSPENDED))
brightness = 0;
ret = aat2870->write(aat2870, AAT2870_BLM,
(u8)aat2870_brightness(aat2870_bl, brightness));
if (ret < 0)
return ret;
if (brightness == 0) {
ret = aat2870_bl_disable(aat2870_bl);
if (ret < 0)
return ret;
} else if (aat2870_bl->brightness == 0) {
ret = aat2870_bl_enable(aat2870_bl);
if (ret < 0)
return ret;
}
aat2870_bl->brightness = brightness;
return 0;
}
static int aat2870_bl_check_fb(struct backlight_device *bd, struct fb_info *fi)
{
return 1;
}
static const struct backlight_ops aat2870_bl_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = aat2870_bl_update_status,
.check_fb = aat2870_bl_check_fb,
};
static int aat2870_bl_probe(struct platform_device *pdev)
{
struct aat2870_bl_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct aat2870_bl_driver_data *aat2870_bl;
struct backlight_device *bd;
struct backlight_properties props;
int ret = 0;
if (!pdata) {
dev_err(&pdev->dev, "No platform data\n");
ret = -ENXIO;
goto out;
}
if (pdev->id != AAT2870_ID_BL) {
dev_err(&pdev->dev, "Invalid device ID, %d\n", pdev->id);
ret = -EINVAL;
goto out;
}
aat2870_bl = devm_kzalloc(&pdev->dev,
sizeof(struct aat2870_bl_driver_data),
GFP_KERNEL);
if (!aat2870_bl) {
ret = -ENOMEM;
goto out;
}
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
bd = devm_backlight_device_register(&pdev->dev, "aat2870-backlight",
&pdev->dev, aat2870_bl, &aat2870_bl_ops,
&props);
if (IS_ERR(bd)) {
dev_err(&pdev->dev,
"Failed allocate memory for backlight device\n");
ret = PTR_ERR(bd);
goto out;
}
aat2870_bl->pdev = pdev;
platform_set_drvdata(pdev, aat2870_bl);
aat2870_bl->bd = bd;
if (pdata->channels > 0)
aat2870_bl->channels = pdata->channels;
else
aat2870_bl->channels = AAT2870_BL_CH_ALL;
if (pdata->max_current > 0)
aat2870_bl->max_current = pdata->max_current;
else
aat2870_bl->max_current = AAT2870_CURRENT_27_9;
if (pdata->max_brightness > 0)
bd->props.max_brightness = pdata->max_brightness;
else
bd->props.max_brightness = 255;
aat2870_bl->brightness = 0;
bd->props.power = FB_BLANK_UNBLANK;
bd->props.brightness = bd->props.max_brightness;
ret = aat2870_bl_update_status(bd);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to initialize\n");
return ret;
}
return 0;
out:
return ret;
}
static int aat2870_bl_remove(struct platform_device *pdev)
{
struct aat2870_bl_driver_data *aat2870_bl = platform_get_drvdata(pdev);
struct backlight_device *bd = aat2870_bl->bd;
bd->props.power = FB_BLANK_POWERDOWN;
bd->props.brightness = 0;
backlight_update_status(bd);
return 0;
}
static struct platform_driver aat2870_bl_driver = {
.driver = {
.name = "aat2870-backlight",
},
.probe = aat2870_bl_probe,
.remove = aat2870_bl_remove,
};
static int __init aat2870_bl_init(void)
{
return platform_driver_register(&aat2870_bl_driver);
}
subsys_initcall(aat2870_bl_init);
static void __exit aat2870_bl_exit(void)
{
platform_driver_unregister(&aat2870_bl_driver);
}
module_exit(aat2870_bl_exit);
MODULE_DESCRIPTION("AnalogicTech AAT2870 Backlight");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jin Park <jinyoungp@nvidia.com>");

View file

@ -0,0 +1,389 @@
/*
* Backlight driver for Analog Devices ADP5520/ADP5501 MFD PMICs
*
* Copyright 2009 Analog Devices Inc.
*
* Licensed under the GPL-2 or later.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/mfd/adp5520.h>
#include <linux/slab.h>
#include <linux/module.h>
struct adp5520_bl {
struct device *master;
struct adp5520_backlight_platform_data *pdata;
struct mutex lock;
unsigned long cached_daylight_max;
int id;
int current_brightness;
};
static int adp5520_bl_set(struct backlight_device *bl, int brightness)
{
struct adp5520_bl *data = bl_get_data(bl);
struct device *master = data->master;
int ret = 0;
if (data->pdata->en_ambl_sens) {
if ((brightness > 0) && (brightness < ADP5020_MAX_BRIGHTNESS)) {
/* Disable Ambient Light auto adjust */
ret |= adp5520_clr_bits(master, ADP5520_BL_CONTROL,
ADP5520_BL_AUTO_ADJ);
ret |= adp5520_write(master, ADP5520_DAYLIGHT_MAX,
brightness);
} else {
/*
* MAX_BRIGHTNESS -> Enable Ambient Light auto adjust
* restore daylight l3 sysfs brightness
*/
ret |= adp5520_write(master, ADP5520_DAYLIGHT_MAX,
data->cached_daylight_max);
ret |= adp5520_set_bits(master, ADP5520_BL_CONTROL,
ADP5520_BL_AUTO_ADJ);
}
} else {
ret |= adp5520_write(master, ADP5520_DAYLIGHT_MAX, brightness);
}
if (data->current_brightness && brightness == 0)
ret |= adp5520_set_bits(master,
ADP5520_MODE_STATUS, ADP5520_DIM_EN);
else if (data->current_brightness == 0 && brightness)
ret |= adp5520_clr_bits(master,
ADP5520_MODE_STATUS, ADP5520_DIM_EN);
if (!ret)
data->current_brightness = brightness;
return ret;
}
static int adp5520_bl_update_status(struct backlight_device *bl)
{
int brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
return adp5520_bl_set(bl, brightness);
}
static int adp5520_bl_get_brightness(struct backlight_device *bl)
{
struct adp5520_bl *data = bl_get_data(bl);
int error;
uint8_t reg_val;
error = adp5520_read(data->master, ADP5520_BL_VALUE, &reg_val);
return error ? data->current_brightness : reg_val;
}
static const struct backlight_ops adp5520_bl_ops = {
.update_status = adp5520_bl_update_status,
.get_brightness = adp5520_bl_get_brightness,
};
static int adp5520_bl_setup(struct backlight_device *bl)
{
struct adp5520_bl *data = bl_get_data(bl);
struct device *master = data->master;
struct adp5520_backlight_platform_data *pdata = data->pdata;
int ret = 0;
ret |= adp5520_write(master, ADP5520_DAYLIGHT_MAX,
pdata->l1_daylight_max);
ret |= adp5520_write(master, ADP5520_DAYLIGHT_DIM,
pdata->l1_daylight_dim);
if (pdata->en_ambl_sens) {
data->cached_daylight_max = pdata->l1_daylight_max;
ret |= adp5520_write(master, ADP5520_OFFICE_MAX,
pdata->l2_office_max);
ret |= adp5520_write(master, ADP5520_OFFICE_DIM,
pdata->l2_office_dim);
ret |= adp5520_write(master, ADP5520_DARK_MAX,
pdata->l3_dark_max);
ret |= adp5520_write(master, ADP5520_DARK_DIM,
pdata->l3_dark_dim);
ret |= adp5520_write(master, ADP5520_L2_TRIP,
pdata->l2_trip);
ret |= adp5520_write(master, ADP5520_L2_HYS,
pdata->l2_hyst);
ret |= adp5520_write(master, ADP5520_L3_TRIP,
pdata->l3_trip);
ret |= adp5520_write(master, ADP5520_L3_HYS,
pdata->l3_hyst);
ret |= adp5520_write(master, ADP5520_ALS_CMPR_CFG,
ALS_CMPR_CFG_VAL(pdata->abml_filt,
ADP5520_L3_EN));
}
ret |= adp5520_write(master, ADP5520_BL_CONTROL,
BL_CTRL_VAL(pdata->fade_led_law,
pdata->en_ambl_sens));
ret |= adp5520_write(master, ADP5520_BL_FADE, FADE_VAL(pdata->fade_in,
pdata->fade_out));
ret |= adp5520_set_bits(master, ADP5520_MODE_STATUS,
ADP5520_BL_EN | ADP5520_DIM_EN);
return ret;
}
static ssize_t adp5520_show(struct device *dev, char *buf, int reg)
{
struct adp5520_bl *data = dev_get_drvdata(dev);
int ret;
uint8_t reg_val;
mutex_lock(&data->lock);
ret = adp5520_read(data->master, reg, &reg_val);
mutex_unlock(&data->lock);
if (ret < 0)
return ret;
return sprintf(buf, "%u\n", reg_val);
}
static ssize_t adp5520_store(struct device *dev, const char *buf,
size_t count, int reg)
{
struct adp5520_bl *data = dev_get_drvdata(dev);
unsigned long val;
int ret;
ret = kstrtoul(buf, 10, &val);
if (ret)
return ret;
mutex_lock(&data->lock);
adp5520_write(data->master, reg, val);
mutex_unlock(&data->lock);
return count;
}
static ssize_t adp5520_bl_dark_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp5520_show(dev, buf, ADP5520_DARK_MAX);
}
static ssize_t adp5520_bl_dark_max_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp5520_store(dev, buf, count, ADP5520_DARK_MAX);
}
static DEVICE_ATTR(dark_max, 0664, adp5520_bl_dark_max_show,
adp5520_bl_dark_max_store);
static ssize_t adp5520_bl_office_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp5520_show(dev, buf, ADP5520_OFFICE_MAX);
}
static ssize_t adp5520_bl_office_max_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp5520_store(dev, buf, count, ADP5520_OFFICE_MAX);
}
static DEVICE_ATTR(office_max, 0664, adp5520_bl_office_max_show,
adp5520_bl_office_max_store);
static ssize_t adp5520_bl_daylight_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp5520_show(dev, buf, ADP5520_DAYLIGHT_MAX);
}
static ssize_t adp5520_bl_daylight_max_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct adp5520_bl *data = dev_get_drvdata(dev);
int ret;
ret = kstrtoul(buf, 10, &data->cached_daylight_max);
if (ret < 0)
return ret;
return adp5520_store(dev, buf, count, ADP5520_DAYLIGHT_MAX);
}
static DEVICE_ATTR(daylight_max, 0664, adp5520_bl_daylight_max_show,
adp5520_bl_daylight_max_store);
static ssize_t adp5520_bl_dark_dim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp5520_show(dev, buf, ADP5520_DARK_DIM);
}
static ssize_t adp5520_bl_dark_dim_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp5520_store(dev, buf, count, ADP5520_DARK_DIM);
}
static DEVICE_ATTR(dark_dim, 0664, adp5520_bl_dark_dim_show,
adp5520_bl_dark_dim_store);
static ssize_t adp5520_bl_office_dim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp5520_show(dev, buf, ADP5520_OFFICE_DIM);
}
static ssize_t adp5520_bl_office_dim_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp5520_store(dev, buf, count, ADP5520_OFFICE_DIM);
}
static DEVICE_ATTR(office_dim, 0664, adp5520_bl_office_dim_show,
adp5520_bl_office_dim_store);
static ssize_t adp5520_bl_daylight_dim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp5520_show(dev, buf, ADP5520_DAYLIGHT_DIM);
}
static ssize_t adp5520_bl_daylight_dim_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp5520_store(dev, buf, count, ADP5520_DAYLIGHT_DIM);
}
static DEVICE_ATTR(daylight_dim, 0664, adp5520_bl_daylight_dim_show,
adp5520_bl_daylight_dim_store);
static struct attribute *adp5520_bl_attributes[] = {
&dev_attr_dark_max.attr,
&dev_attr_dark_dim.attr,
&dev_attr_office_max.attr,
&dev_attr_office_dim.attr,
&dev_attr_daylight_max.attr,
&dev_attr_daylight_dim.attr,
NULL
};
static const struct attribute_group adp5520_bl_attr_group = {
.attrs = adp5520_bl_attributes,
};
static int adp5520_bl_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct backlight_device *bl;
struct adp5520_bl *data;
int ret = 0;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
data->master = pdev->dev.parent;
data->pdata = dev_get_platdata(&pdev->dev);
if (data->pdata == NULL) {
dev_err(&pdev->dev, "missing platform data\n");
return -ENODEV;
}
data->id = pdev->id;
data->current_brightness = 0;
mutex_init(&data->lock);
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = ADP5020_MAX_BRIGHTNESS;
bl = devm_backlight_device_register(&pdev->dev, pdev->name,
data->master, data, &adp5520_bl_ops,
&props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
bl->props.brightness = ADP5020_MAX_BRIGHTNESS;
if (data->pdata->en_ambl_sens)
ret = sysfs_create_group(&bl->dev.kobj,
&adp5520_bl_attr_group);
if (ret) {
dev_err(&pdev->dev, "failed to register sysfs\n");
return ret;
}
platform_set_drvdata(pdev, bl);
ret |= adp5520_bl_setup(bl);
backlight_update_status(bl);
return ret;
}
static int adp5520_bl_remove(struct platform_device *pdev)
{
struct backlight_device *bl = platform_get_drvdata(pdev);
struct adp5520_bl *data = bl_get_data(bl);
adp5520_clr_bits(data->master, ADP5520_MODE_STATUS, ADP5520_BL_EN);
if (data->pdata->en_ambl_sens)
sysfs_remove_group(&bl->dev.kobj,
&adp5520_bl_attr_group);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int adp5520_bl_suspend(struct device *dev)
{
struct backlight_device *bl = dev_get_drvdata(dev);
return adp5520_bl_set(bl, 0);
}
static int adp5520_bl_resume(struct device *dev)
{
struct backlight_device *bl = dev_get_drvdata(dev);
backlight_update_status(bl);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(adp5520_bl_pm_ops, adp5520_bl_suspend,
adp5520_bl_resume);
static struct platform_driver adp5520_bl_driver = {
.driver = {
.name = "adp5520-backlight",
.pm = &adp5520_bl_pm_ops,
},
.probe = adp5520_bl_probe,
.remove = adp5520_bl_remove,
};
module_platform_driver(adp5520_bl_driver);
MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
MODULE_DESCRIPTION("ADP5520(01) Backlight Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:adp5520-backlight");

View file

@ -0,0 +1,822 @@
/*
* Backlight driver for Analog Devices ADP8860 Backlight Devices
*
* Copyright 2009-2010 Analog Devices Inc.
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/pm.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/leds.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/i2c/adp8860.h>
#define ADP8860_EXT_FEATURES
#define ADP8860_USE_LEDS
#define ADP8860_MFDVID 0x00 /* Manufacturer and device ID */
#define ADP8860_MDCR 0x01 /* Device mode and status */
#define ADP8860_MDCR2 0x02 /* Device mode and Status Register 2 */
#define ADP8860_INTR_EN 0x03 /* Interrupts enable */
#define ADP8860_CFGR 0x04 /* Configuration register */
#define ADP8860_BLSEN 0x05 /* Sink enable backlight or independent */
#define ADP8860_BLOFF 0x06 /* Backlight off timeout */
#define ADP8860_BLDIM 0x07 /* Backlight dim timeout */
#define ADP8860_BLFR 0x08 /* Backlight fade in and out rates */
#define ADP8860_BLMX1 0x09 /* Backlight (Brightness Level 1-daylight) maximum current */
#define ADP8860_BLDM1 0x0A /* Backlight (Brightness Level 1-daylight) dim current */
#define ADP8860_BLMX2 0x0B /* Backlight (Brightness Level 2-office) maximum current */
#define ADP8860_BLDM2 0x0C /* Backlight (Brightness Level 2-office) dim current */
#define ADP8860_BLMX3 0x0D /* Backlight (Brightness Level 3-dark) maximum current */
#define ADP8860_BLDM3 0x0E /* Backlight (Brightness Level 3-dark) dim current */
#define ADP8860_ISCFR 0x0F /* Independent sink current fade control register */
#define ADP8860_ISCC 0x10 /* Independent sink current control register */
#define ADP8860_ISCT1 0x11 /* Independent Sink Current Timer Register LED[7:5] */
#define ADP8860_ISCT2 0x12 /* Independent Sink Current Timer Register LED[4:1] */
#define ADP8860_ISCF 0x13 /* Independent sink current fade register */
#define ADP8860_ISC7 0x14 /* Independent Sink Current LED7 */
#define ADP8860_ISC6 0x15 /* Independent Sink Current LED6 */
#define ADP8860_ISC5 0x16 /* Independent Sink Current LED5 */
#define ADP8860_ISC4 0x17 /* Independent Sink Current LED4 */
#define ADP8860_ISC3 0x18 /* Independent Sink Current LED3 */
#define ADP8860_ISC2 0x19 /* Independent Sink Current LED2 */
#define ADP8860_ISC1 0x1A /* Independent Sink Current LED1 */
#define ADP8860_CCFG 0x1B /* Comparator configuration */
#define ADP8860_CCFG2 0x1C /* Second comparator configuration */
#define ADP8860_L2_TRP 0x1D /* L2 comparator reference */
#define ADP8860_L2_HYS 0x1E /* L2 hysteresis */
#define ADP8860_L3_TRP 0x1F /* L3 comparator reference */
#define ADP8860_L3_HYS 0x20 /* L3 hysteresis */
#define ADP8860_PH1LEVL 0x21 /* First phototransistor ambient light level-low byte register */
#define ADP8860_PH1LEVH 0x22 /* First phototransistor ambient light level-high byte register */
#define ADP8860_PH2LEVL 0x23 /* Second phototransistor ambient light level-low byte register */
#define ADP8860_PH2LEVH 0x24 /* Second phototransistor ambient light level-high byte register */
#define ADP8860_MANUFID 0x0 /* Analog Devices ADP8860 Manufacturer ID */
#define ADP8861_MANUFID 0x4 /* Analog Devices ADP8861 Manufacturer ID */
#define ADP8863_MANUFID 0x2 /* Analog Devices ADP8863 Manufacturer ID */
#define ADP8860_DEVID(x) ((x) & 0xF)
#define ADP8860_MANID(x) ((x) >> 4)
/* MDCR Device mode and status */
#define INT_CFG (1 << 6)
#define NSTBY (1 << 5)
#define DIM_EN (1 << 4)
#define GDWN_DIS (1 << 3)
#define SIS_EN (1 << 2)
#define CMP_AUTOEN (1 << 1)
#define BLEN (1 << 0)
/* ADP8860_CCFG Main ALS comparator level enable */
#define L3_EN (1 << 1)
#define L2_EN (1 << 0)
#define CFGR_BLV_SHIFT 3
#define CFGR_BLV_MASK 0x3
#define ADP8860_FLAG_LED_MASK 0xFF
#define FADE_VAL(in, out) ((0xF & (in)) | ((0xF & (out)) << 4))
#define BL_CFGR_VAL(law, blv) ((((blv) & CFGR_BLV_MASK) << CFGR_BLV_SHIFT) | ((0x3 & (law)) << 1))
#define ALS_CCFG_VAL(filt) ((0x7 & filt) << 5)
enum {
adp8860,
adp8861,
adp8863
};
struct adp8860_led {
struct led_classdev cdev;
struct work_struct work;
struct i2c_client *client;
enum led_brightness new_brightness;
int id;
int flags;
};
struct adp8860_bl {
struct i2c_client *client;
struct backlight_device *bl;
struct adp8860_led *led;
struct adp8860_backlight_platform_data *pdata;
struct mutex lock;
unsigned long cached_daylight_max;
int id;
int revid;
int current_brightness;
unsigned en_ambl_sens:1;
unsigned gdwn_dis:1;
};
static int adp8860_read(struct i2c_client *client, int reg, uint8_t *val)
{
int ret;
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0) {
dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
return ret;
}
*val = (uint8_t)ret;
return 0;
}
static int adp8860_write(struct i2c_client *client, u8 reg, u8 val)
{
return i2c_smbus_write_byte_data(client, reg, val);
}
static int adp8860_set_bits(struct i2c_client *client, int reg, uint8_t bit_mask)
{
struct adp8860_bl *data = i2c_get_clientdata(client);
uint8_t reg_val;
int ret;
mutex_lock(&data->lock);
ret = adp8860_read(client, reg, &reg_val);
if (!ret && ((reg_val & bit_mask) != bit_mask)) {
reg_val |= bit_mask;
ret = adp8860_write(client, reg, reg_val);
}
mutex_unlock(&data->lock);
return ret;
}
static int adp8860_clr_bits(struct i2c_client *client, int reg, uint8_t bit_mask)
{
struct adp8860_bl *data = i2c_get_clientdata(client);
uint8_t reg_val;
int ret;
mutex_lock(&data->lock);
ret = adp8860_read(client, reg, &reg_val);
if (!ret && (reg_val & bit_mask)) {
reg_val &= ~bit_mask;
ret = adp8860_write(client, reg, reg_val);
}
mutex_unlock(&data->lock);
return ret;
}
/*
* Independent sink / LED
*/
#if defined(ADP8860_USE_LEDS)
static void adp8860_led_work(struct work_struct *work)
{
struct adp8860_led *led = container_of(work, struct adp8860_led, work);
adp8860_write(led->client, ADP8860_ISC1 - led->id + 1,
led->new_brightness >> 1);
}
static void adp8860_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct adp8860_led *led;
led = container_of(led_cdev, struct adp8860_led, cdev);
led->new_brightness = value;
schedule_work(&led->work);
}
static int adp8860_led_setup(struct adp8860_led *led)
{
struct i2c_client *client = led->client;
int ret = 0;
ret = adp8860_write(client, ADP8860_ISC1 - led->id + 1, 0);
ret |= adp8860_set_bits(client, ADP8860_ISCC, 1 << (led->id - 1));
if (led->id > 4)
ret |= adp8860_set_bits(client, ADP8860_ISCT1,
(led->flags & 0x3) << ((led->id - 5) * 2));
else
ret |= adp8860_set_bits(client, ADP8860_ISCT2,
(led->flags & 0x3) << ((led->id - 1) * 2));
return ret;
}
static int adp8860_led_probe(struct i2c_client *client)
{
struct adp8860_backlight_platform_data *pdata =
dev_get_platdata(&client->dev);
struct adp8860_bl *data = i2c_get_clientdata(client);
struct adp8860_led *led, *led_dat;
struct led_info *cur_led;
int ret, i;
led = devm_kzalloc(&client->dev, sizeof(*led) * pdata->num_leds,
GFP_KERNEL);
if (led == NULL)
return -ENOMEM;
ret = adp8860_write(client, ADP8860_ISCFR, pdata->led_fade_law);
ret = adp8860_write(client, ADP8860_ISCT1,
(pdata->led_on_time & 0x3) << 6);
ret |= adp8860_write(client, ADP8860_ISCF,
FADE_VAL(pdata->led_fade_in, pdata->led_fade_out));
if (ret) {
dev_err(&client->dev, "failed to write\n");
return ret;
}
for (i = 0; i < pdata->num_leds; ++i) {
cur_led = &pdata->leds[i];
led_dat = &led[i];
led_dat->id = cur_led->flags & ADP8860_FLAG_LED_MASK;
if (led_dat->id > 7 || led_dat->id < 1) {
dev_err(&client->dev, "Invalid LED ID %d\n",
led_dat->id);
ret = -EINVAL;
goto err;
}
if (pdata->bl_led_assign & (1 << (led_dat->id - 1))) {
dev_err(&client->dev, "LED %d used by Backlight\n",
led_dat->id);
ret = -EBUSY;
goto err;
}
led_dat->cdev.name = cur_led->name;
led_dat->cdev.default_trigger = cur_led->default_trigger;
led_dat->cdev.brightness_set = adp8860_led_set;
led_dat->cdev.brightness = LED_OFF;
led_dat->flags = cur_led->flags >> FLAG_OFFT_SHIFT;
led_dat->client = client;
led_dat->new_brightness = LED_OFF;
INIT_WORK(&led_dat->work, adp8860_led_work);
ret = led_classdev_register(&client->dev, &led_dat->cdev);
if (ret) {
dev_err(&client->dev, "failed to register LED %d\n",
led_dat->id);
goto err;
}
ret = adp8860_led_setup(led_dat);
if (ret) {
dev_err(&client->dev, "failed to write\n");
i++;
goto err;
}
}
data->led = led;
return 0;
err:
for (i = i - 1; i >= 0; --i) {
led_classdev_unregister(&led[i].cdev);
cancel_work_sync(&led[i].work);
}
return ret;
}
static int adp8860_led_remove(struct i2c_client *client)
{
struct adp8860_backlight_platform_data *pdata =
dev_get_platdata(&client->dev);
struct adp8860_bl *data = i2c_get_clientdata(client);
int i;
for (i = 0; i < pdata->num_leds; i++) {
led_classdev_unregister(&data->led[i].cdev);
cancel_work_sync(&data->led[i].work);
}
return 0;
}
#else
static int adp8860_led_probe(struct i2c_client *client)
{
return 0;
}
static int adp8860_led_remove(struct i2c_client *client)
{
return 0;
}
#endif
static int adp8860_bl_set(struct backlight_device *bl, int brightness)
{
struct adp8860_bl *data = bl_get_data(bl);
struct i2c_client *client = data->client;
int ret = 0;
if (data->en_ambl_sens) {
if ((brightness > 0) && (brightness < ADP8860_MAX_BRIGHTNESS)) {
/* Disable Ambient Light auto adjust */
ret |= adp8860_clr_bits(client, ADP8860_MDCR,
CMP_AUTOEN);
ret |= adp8860_write(client, ADP8860_BLMX1, brightness);
} else {
/*
* MAX_BRIGHTNESS -> Enable Ambient Light auto adjust
* restore daylight l1 sysfs brightness
*/
ret |= adp8860_write(client, ADP8860_BLMX1,
data->cached_daylight_max);
ret |= adp8860_set_bits(client, ADP8860_MDCR,
CMP_AUTOEN);
}
} else
ret |= adp8860_write(client, ADP8860_BLMX1, brightness);
if (data->current_brightness && brightness == 0)
ret |= adp8860_set_bits(client,
ADP8860_MDCR, DIM_EN);
else if (data->current_brightness == 0 && brightness)
ret |= adp8860_clr_bits(client,
ADP8860_MDCR, DIM_EN);
if (!ret)
data->current_brightness = brightness;
return ret;
}
static int adp8860_bl_update_status(struct backlight_device *bl)
{
int brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
return adp8860_bl_set(bl, brightness);
}
static int adp8860_bl_get_brightness(struct backlight_device *bl)
{
struct adp8860_bl *data = bl_get_data(bl);
return data->current_brightness;
}
static const struct backlight_ops adp8860_bl_ops = {
.update_status = adp8860_bl_update_status,
.get_brightness = adp8860_bl_get_brightness,
};
static int adp8860_bl_setup(struct backlight_device *bl)
{
struct adp8860_bl *data = bl_get_data(bl);
struct i2c_client *client = data->client;
struct adp8860_backlight_platform_data *pdata = data->pdata;
int ret = 0;
ret |= adp8860_write(client, ADP8860_BLSEN, ~pdata->bl_led_assign);
ret |= adp8860_write(client, ADP8860_BLMX1, pdata->l1_daylight_max);
ret |= adp8860_write(client, ADP8860_BLDM1, pdata->l1_daylight_dim);
if (data->en_ambl_sens) {
data->cached_daylight_max = pdata->l1_daylight_max;
ret |= adp8860_write(client, ADP8860_BLMX2,
pdata->l2_office_max);
ret |= adp8860_write(client, ADP8860_BLDM2,
pdata->l2_office_dim);
ret |= adp8860_write(client, ADP8860_BLMX3,
pdata->l3_dark_max);
ret |= adp8860_write(client, ADP8860_BLDM3,
pdata->l3_dark_dim);
ret |= adp8860_write(client, ADP8860_L2_TRP, pdata->l2_trip);
ret |= adp8860_write(client, ADP8860_L2_HYS, pdata->l2_hyst);
ret |= adp8860_write(client, ADP8860_L3_TRP, pdata->l3_trip);
ret |= adp8860_write(client, ADP8860_L3_HYS, pdata->l3_hyst);
ret |= adp8860_write(client, ADP8860_CCFG, L2_EN | L3_EN |
ALS_CCFG_VAL(pdata->abml_filt));
}
ret |= adp8860_write(client, ADP8860_CFGR,
BL_CFGR_VAL(pdata->bl_fade_law, 0));
ret |= adp8860_write(client, ADP8860_BLFR, FADE_VAL(pdata->bl_fade_in,
pdata->bl_fade_out));
ret |= adp8860_set_bits(client, ADP8860_MDCR, BLEN | DIM_EN | NSTBY |
(data->gdwn_dis ? GDWN_DIS : 0));
return ret;
}
static ssize_t adp8860_show(struct device *dev, char *buf, int reg)
{
struct adp8860_bl *data = dev_get_drvdata(dev);
int error;
uint8_t reg_val;
mutex_lock(&data->lock);
error = adp8860_read(data->client, reg, &reg_val);
mutex_unlock(&data->lock);
if (error < 0)
return error;
return sprintf(buf, "%u\n", reg_val);
}
static ssize_t adp8860_store(struct device *dev, const char *buf,
size_t count, int reg)
{
struct adp8860_bl *data = dev_get_drvdata(dev);
unsigned long val;
int ret;
ret = kstrtoul(buf, 10, &val);
if (ret)
return ret;
mutex_lock(&data->lock);
adp8860_write(data->client, reg, val);
mutex_unlock(&data->lock);
return count;
}
static ssize_t adp8860_bl_l3_dark_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8860_show(dev, buf, ADP8860_BLMX3);
}
static ssize_t adp8860_bl_l3_dark_max_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
return adp8860_store(dev, buf, count, ADP8860_BLMX3);
}
static DEVICE_ATTR(l3_dark_max, 0664, adp8860_bl_l3_dark_max_show,
adp8860_bl_l3_dark_max_store);
static ssize_t adp8860_bl_l2_office_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8860_show(dev, buf, ADP8860_BLMX2);
}
static ssize_t adp8860_bl_l2_office_max_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
return adp8860_store(dev, buf, count, ADP8860_BLMX2);
}
static DEVICE_ATTR(l2_office_max, 0664, adp8860_bl_l2_office_max_show,
adp8860_bl_l2_office_max_store);
static ssize_t adp8860_bl_l1_daylight_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8860_show(dev, buf, ADP8860_BLMX1);
}
static ssize_t adp8860_bl_l1_daylight_max_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct adp8860_bl *data = dev_get_drvdata(dev);
int ret = kstrtoul(buf, 10, &data->cached_daylight_max);
if (ret)
return ret;
return adp8860_store(dev, buf, count, ADP8860_BLMX1);
}
static DEVICE_ATTR(l1_daylight_max, 0664, adp8860_bl_l1_daylight_max_show,
adp8860_bl_l1_daylight_max_store);
static ssize_t adp8860_bl_l3_dark_dim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8860_show(dev, buf, ADP8860_BLDM3);
}
static ssize_t adp8860_bl_l3_dark_dim_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp8860_store(dev, buf, count, ADP8860_BLDM3);
}
static DEVICE_ATTR(l3_dark_dim, 0664, adp8860_bl_l3_dark_dim_show,
adp8860_bl_l3_dark_dim_store);
static ssize_t adp8860_bl_l2_office_dim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8860_show(dev, buf, ADP8860_BLDM2);
}
static ssize_t adp8860_bl_l2_office_dim_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp8860_store(dev, buf, count, ADP8860_BLDM2);
}
static DEVICE_ATTR(l2_office_dim, 0664, adp8860_bl_l2_office_dim_show,
adp8860_bl_l2_office_dim_store);
static ssize_t adp8860_bl_l1_daylight_dim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8860_show(dev, buf, ADP8860_BLDM1);
}
static ssize_t adp8860_bl_l1_daylight_dim_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp8860_store(dev, buf, count, ADP8860_BLDM1);
}
static DEVICE_ATTR(l1_daylight_dim, 0664, adp8860_bl_l1_daylight_dim_show,
adp8860_bl_l1_daylight_dim_store);
#ifdef ADP8860_EXT_FEATURES
static ssize_t adp8860_bl_ambient_light_level_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adp8860_bl *data = dev_get_drvdata(dev);
int error;
uint8_t reg_val;
uint16_t ret_val;
mutex_lock(&data->lock);
error = adp8860_read(data->client, ADP8860_PH1LEVL, &reg_val);
ret_val = reg_val;
error |= adp8860_read(data->client, ADP8860_PH1LEVH, &reg_val);
mutex_unlock(&data->lock);
if (error < 0)
return error;
/* Return 13-bit conversion value for the first light sensor */
ret_val += (reg_val & 0x1F) << 8;
return sprintf(buf, "%u\n", ret_val);
}
static DEVICE_ATTR(ambient_light_level, 0444,
adp8860_bl_ambient_light_level_show, NULL);
static ssize_t adp8860_bl_ambient_light_zone_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adp8860_bl *data = dev_get_drvdata(dev);
int error;
uint8_t reg_val;
mutex_lock(&data->lock);
error = adp8860_read(data->client, ADP8860_CFGR, &reg_val);
mutex_unlock(&data->lock);
if (error < 0)
return error;
return sprintf(buf, "%u\n",
((reg_val >> CFGR_BLV_SHIFT) & CFGR_BLV_MASK) + 1);
}
static ssize_t adp8860_bl_ambient_light_zone_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct adp8860_bl *data = dev_get_drvdata(dev);
unsigned long val;
uint8_t reg_val;
int ret;
ret = kstrtoul(buf, 10, &val);
if (ret)
return ret;
if (val == 0) {
/* Enable automatic ambient light sensing */
adp8860_set_bits(data->client, ADP8860_MDCR, CMP_AUTOEN);
} else if ((val > 0) && (val <= 3)) {
/* Disable automatic ambient light sensing */
adp8860_clr_bits(data->client, ADP8860_MDCR, CMP_AUTOEN);
/* Set user supplied ambient light zone */
mutex_lock(&data->lock);
adp8860_read(data->client, ADP8860_CFGR, &reg_val);
reg_val &= ~(CFGR_BLV_MASK << CFGR_BLV_SHIFT);
reg_val |= (val - 1) << CFGR_BLV_SHIFT;
adp8860_write(data->client, ADP8860_CFGR, reg_val);
mutex_unlock(&data->lock);
}
return count;
}
static DEVICE_ATTR(ambient_light_zone, 0664,
adp8860_bl_ambient_light_zone_show,
adp8860_bl_ambient_light_zone_store);
#endif
static struct attribute *adp8860_bl_attributes[] = {
&dev_attr_l3_dark_max.attr,
&dev_attr_l3_dark_dim.attr,
&dev_attr_l2_office_max.attr,
&dev_attr_l2_office_dim.attr,
&dev_attr_l1_daylight_max.attr,
&dev_attr_l1_daylight_dim.attr,
#ifdef ADP8860_EXT_FEATURES
&dev_attr_ambient_light_level.attr,
&dev_attr_ambient_light_zone.attr,
#endif
NULL
};
static const struct attribute_group adp8860_bl_attr_group = {
.attrs = adp8860_bl_attributes,
};
static int adp8860_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct backlight_device *bl;
struct adp8860_bl *data;
struct adp8860_backlight_platform_data *pdata =
dev_get_platdata(&client->dev);
struct backlight_properties props;
uint8_t reg_val;
int ret;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
return -EIO;
}
if (!pdata) {
dev_err(&client->dev, "no platform data?\n");
return -EINVAL;
}
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
ret = adp8860_read(client, ADP8860_MFDVID, &reg_val);
if (ret < 0)
return ret;
switch (ADP8860_MANID(reg_val)) {
case ADP8863_MANUFID:
data->gdwn_dis = !!pdata->gdwn_dis;
case ADP8860_MANUFID:
data->en_ambl_sens = !!pdata->en_ambl_sens;
break;
case ADP8861_MANUFID:
data->gdwn_dis = !!pdata->gdwn_dis;
break;
default:
dev_err(&client->dev, "failed to probe\n");
return -ENODEV;
}
/* It's confirmed that the DEVID field is actually a REVID */
data->revid = ADP8860_DEVID(reg_val);
data->client = client;
data->pdata = pdata;
data->id = id->driver_data;
data->current_brightness = 0;
i2c_set_clientdata(client, data);
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW;
props.max_brightness = ADP8860_MAX_BRIGHTNESS;
mutex_init(&data->lock);
bl = devm_backlight_device_register(&client->dev,
dev_driver_string(&client->dev),
&client->dev, data, &adp8860_bl_ops, &props);
if (IS_ERR(bl)) {
dev_err(&client->dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
bl->props.brightness = ADP8860_MAX_BRIGHTNESS;
data->bl = bl;
if (data->en_ambl_sens)
ret = sysfs_create_group(&bl->dev.kobj,
&adp8860_bl_attr_group);
if (ret) {
dev_err(&client->dev, "failed to register sysfs\n");
return ret;
}
ret = adp8860_bl_setup(bl);
if (ret) {
ret = -EIO;
goto out;
}
backlight_update_status(bl);
dev_info(&client->dev, "%s Rev.%d Backlight\n",
client->name, data->revid);
if (pdata->num_leds)
adp8860_led_probe(client);
return 0;
out:
if (data->en_ambl_sens)
sysfs_remove_group(&data->bl->dev.kobj,
&adp8860_bl_attr_group);
return ret;
}
static int adp8860_remove(struct i2c_client *client)
{
struct adp8860_bl *data = i2c_get_clientdata(client);
adp8860_clr_bits(client, ADP8860_MDCR, NSTBY);
if (data->led)
adp8860_led_remove(client);
if (data->en_ambl_sens)
sysfs_remove_group(&data->bl->dev.kobj,
&adp8860_bl_attr_group);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int adp8860_i2c_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
adp8860_clr_bits(client, ADP8860_MDCR, NSTBY);
return 0;
}
static int adp8860_i2c_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
adp8860_set_bits(client, ADP8860_MDCR, NSTBY | BLEN);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(adp8860_i2c_pm_ops, adp8860_i2c_suspend,
adp8860_i2c_resume);
static const struct i2c_device_id adp8860_id[] = {
{ "adp8860", adp8860 },
{ "adp8861", adp8861 },
{ "adp8863", adp8863 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adp8860_id);
static struct i2c_driver adp8860_driver = {
.driver = {
.name = KBUILD_MODNAME,
.pm = &adp8860_i2c_pm_ops,
},
.probe = adp8860_probe,
.remove = adp8860_remove,
.id_table = adp8860_id,
};
module_i2c_driver(adp8860_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
MODULE_DESCRIPTION("ADP8860 Backlight driver");
MODULE_ALIAS("i2c:adp8860-backlight");

View file

@ -0,0 +1,995 @@
/*
* Backlight driver for Analog Devices ADP8870 Backlight Devices
*
* Copyright 2009-2011 Analog Devices Inc.
*
* Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/pm.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/leds.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/i2c/adp8870.h>
#define ADP8870_EXT_FEATURES
#define ADP8870_USE_LEDS
#define ADP8870_MFDVID 0x00 /* Manufacturer and device ID */
#define ADP8870_MDCR 0x01 /* Device mode and status */
#define ADP8870_INT_STAT 0x02 /* Interrupts status */
#define ADP8870_INT_EN 0x03 /* Interrupts enable */
#define ADP8870_CFGR 0x04 /* Configuration register */
#define ADP8870_BLSEL 0x05 /* Sink enable backlight or independent */
#define ADP8870_PWMLED 0x06 /* PWM Enable Selection Register */
#define ADP8870_BLOFF 0x07 /* Backlight off timeout */
#define ADP8870_BLDIM 0x08 /* Backlight dim timeout */
#define ADP8870_BLFR 0x09 /* Backlight fade in and out rates */
#define ADP8870_BLMX1 0x0A /* Backlight (Brightness Level 1-daylight) maximum current */
#define ADP8870_BLDM1 0x0B /* Backlight (Brightness Level 1-daylight) dim current */
#define ADP8870_BLMX2 0x0C /* Backlight (Brightness Level 2-bright) maximum current */
#define ADP8870_BLDM2 0x0D /* Backlight (Brightness Level 2-bright) dim current */
#define ADP8870_BLMX3 0x0E /* Backlight (Brightness Level 3-office) maximum current */
#define ADP8870_BLDM3 0x0F /* Backlight (Brightness Level 3-office) dim current */
#define ADP8870_BLMX4 0x10 /* Backlight (Brightness Level 4-indoor) maximum current */
#define ADP8870_BLDM4 0x11 /* Backlight (Brightness Level 4-indoor) dim current */
#define ADP8870_BLMX5 0x12 /* Backlight (Brightness Level 5-dark) maximum current */
#define ADP8870_BLDM5 0x13 /* Backlight (Brightness Level 5-dark) dim current */
#define ADP8870_ISCLAW 0x1A /* Independent sink current fade law register */
#define ADP8870_ISCC 0x1B /* Independent sink current control register */
#define ADP8870_ISCT1 0x1C /* Independent Sink Current Timer Register LED[7:5] */
#define ADP8870_ISCT2 0x1D /* Independent Sink Current Timer Register LED[4:1] */
#define ADP8870_ISCF 0x1E /* Independent sink current fade register */
#define ADP8870_ISC1 0x1F /* Independent Sink Current LED1 */
#define ADP8870_ISC2 0x20 /* Independent Sink Current LED2 */
#define ADP8870_ISC3 0x21 /* Independent Sink Current LED3 */
#define ADP8870_ISC4 0x22 /* Independent Sink Current LED4 */
#define ADP8870_ISC5 0x23 /* Independent Sink Current LED5 */
#define ADP8870_ISC6 0x24 /* Independent Sink Current LED6 */
#define ADP8870_ISC7 0x25 /* Independent Sink Current LED7 (Brightness Level 1-daylight) */
#define ADP8870_ISC7_L2 0x26 /* Independent Sink Current LED7 (Brightness Level 2-bright) */
#define ADP8870_ISC7_L3 0x27 /* Independent Sink Current LED7 (Brightness Level 3-office) */
#define ADP8870_ISC7_L4 0x28 /* Independent Sink Current LED7 (Brightness Level 4-indoor) */
#define ADP8870_ISC7_L5 0x29 /* Independent Sink Current LED7 (Brightness Level 5-dark) */
#define ADP8870_CMP_CTL 0x2D /* ALS Comparator Control Register */
#define ADP8870_ALS1_EN 0x2E /* Main ALS comparator level enable */
#define ADP8870_ALS2_EN 0x2F /* Second ALS comparator level enable */
#define ADP8870_ALS1_STAT 0x30 /* Main ALS Comparator Status Register */
#define ADP8870_ALS2_STAT 0x31 /* Second ALS Comparator Status Register */
#define ADP8870_L2TRP 0x32 /* L2 comparator reference */
#define ADP8870_L2HYS 0x33 /* L2 hysteresis */
#define ADP8870_L3TRP 0x34 /* L3 comparator reference */
#define ADP8870_L3HYS 0x35 /* L3 hysteresis */
#define ADP8870_L4TRP 0x36 /* L4 comparator reference */
#define ADP8870_L4HYS 0x37 /* L4 hysteresis */
#define ADP8870_L5TRP 0x38 /* L5 comparator reference */
#define ADP8870_L5HYS 0x39 /* L5 hysteresis */
#define ADP8870_PH1LEVL 0x40 /* First phototransistor ambient light level-low byte register */
#define ADP8870_PH1LEVH 0x41 /* First phototransistor ambient light level-high byte register */
#define ADP8870_PH2LEVL 0x42 /* Second phototransistor ambient light level-low byte register */
#define ADP8870_PH2LEVH 0x43 /* Second phototransistor ambient light level-high byte register */
#define ADP8870_MANUFID 0x3 /* Analog Devices AD8870 Manufacturer and device ID */
#define ADP8870_DEVID(x) ((x) & 0xF)
#define ADP8870_MANID(x) ((x) >> 4)
/* MDCR Device mode and status */
#define D7ALSEN (1 << 7)
#define INT_CFG (1 << 6)
#define NSTBY (1 << 5)
#define DIM_EN (1 << 4)
#define GDWN_DIS (1 << 3)
#define SIS_EN (1 << 2)
#define CMP_AUTOEN (1 << 1)
#define BLEN (1 << 0)
/* ADP8870_ALS1_EN Main ALS comparator level enable */
#define L5_EN (1 << 3)
#define L4_EN (1 << 2)
#define L3_EN (1 << 1)
#define L2_EN (1 << 0)
#define CFGR_BLV_SHIFT 3
#define CFGR_BLV_MASK 0x7
#define ADP8870_FLAG_LED_MASK 0xFF
#define FADE_VAL(in, out) ((0xF & (in)) | ((0xF & (out)) << 4))
#define BL_CFGR_VAL(law, blv) ((((blv) & CFGR_BLV_MASK) << CFGR_BLV_SHIFT) | ((0x3 & (law)) << 1))
#define ALS_CMPR_CFG_VAL(filt) ((0x7 & (filt)) << 1)
struct adp8870_bl {
struct i2c_client *client;
struct backlight_device *bl;
struct adp8870_led *led;
struct adp8870_backlight_platform_data *pdata;
struct mutex lock;
unsigned long cached_daylight_max;
int id;
int revid;
int current_brightness;
};
struct adp8870_led {
struct led_classdev cdev;
struct work_struct work;
struct i2c_client *client;
enum led_brightness new_brightness;
int id;
int flags;
};
static int adp8870_read(struct i2c_client *client, int reg, uint8_t *val)
{
int ret;
ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0) {
dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
return ret;
}
*val = ret;
return 0;
}
static int adp8870_write(struct i2c_client *client, u8 reg, u8 val)
{
int ret = i2c_smbus_write_byte_data(client, reg, val);
if (ret)
dev_err(&client->dev, "failed to write\n");
return ret;
}
static int adp8870_set_bits(struct i2c_client *client, int reg, uint8_t bit_mask)
{
struct adp8870_bl *data = i2c_get_clientdata(client);
uint8_t reg_val;
int ret;
mutex_lock(&data->lock);
ret = adp8870_read(client, reg, &reg_val);
if (!ret && ((reg_val & bit_mask) != bit_mask)) {
reg_val |= bit_mask;
ret = adp8870_write(client, reg, reg_val);
}
mutex_unlock(&data->lock);
return ret;
}
static int adp8870_clr_bits(struct i2c_client *client, int reg, uint8_t bit_mask)
{
struct adp8870_bl *data = i2c_get_clientdata(client);
uint8_t reg_val;
int ret;
mutex_lock(&data->lock);
ret = adp8870_read(client, reg, &reg_val);
if (!ret && (reg_val & bit_mask)) {
reg_val &= ~bit_mask;
ret = adp8870_write(client, reg, reg_val);
}
mutex_unlock(&data->lock);
return ret;
}
/*
* Independent sink / LED
*/
#if defined(ADP8870_USE_LEDS)
static void adp8870_led_work(struct work_struct *work)
{
struct adp8870_led *led = container_of(work, struct adp8870_led, work);
adp8870_write(led->client, ADP8870_ISC1 + led->id - 1,
led->new_brightness >> 1);
}
static void adp8870_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct adp8870_led *led;
led = container_of(led_cdev, struct adp8870_led, cdev);
led->new_brightness = value;
/*
* Use workqueue for IO since I2C operations can sleep.
*/
schedule_work(&led->work);
}
static int adp8870_led_setup(struct adp8870_led *led)
{
struct i2c_client *client = led->client;
int ret = 0;
ret = adp8870_write(client, ADP8870_ISC1 + led->id - 1, 0);
if (ret)
return ret;
ret = adp8870_set_bits(client, ADP8870_ISCC, 1 << (led->id - 1));
if (ret)
return ret;
if (led->id > 4)
ret = adp8870_set_bits(client, ADP8870_ISCT1,
(led->flags & 0x3) << ((led->id - 5) * 2));
else
ret = adp8870_set_bits(client, ADP8870_ISCT2,
(led->flags & 0x3) << ((led->id - 1) * 2));
return ret;
}
static int adp8870_led_probe(struct i2c_client *client)
{
struct adp8870_backlight_platform_data *pdata =
dev_get_platdata(&client->dev);
struct adp8870_bl *data = i2c_get_clientdata(client);
struct adp8870_led *led, *led_dat;
struct led_info *cur_led;
int ret, i;
led = devm_kzalloc(&client->dev, pdata->num_leds * sizeof(*led),
GFP_KERNEL);
if (led == NULL)
return -ENOMEM;
ret = adp8870_write(client, ADP8870_ISCLAW, pdata->led_fade_law);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_ISCT1,
(pdata->led_on_time & 0x3) << 6);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_ISCF,
FADE_VAL(pdata->led_fade_in, pdata->led_fade_out));
if (ret)
return ret;
for (i = 0; i < pdata->num_leds; ++i) {
cur_led = &pdata->leds[i];
led_dat = &led[i];
led_dat->id = cur_led->flags & ADP8870_FLAG_LED_MASK;
if (led_dat->id > 7 || led_dat->id < 1) {
dev_err(&client->dev, "Invalid LED ID %d\n",
led_dat->id);
ret = -EINVAL;
goto err;
}
if (pdata->bl_led_assign & (1 << (led_dat->id - 1))) {
dev_err(&client->dev, "LED %d used by Backlight\n",
led_dat->id);
ret = -EBUSY;
goto err;
}
led_dat->cdev.name = cur_led->name;
led_dat->cdev.default_trigger = cur_led->default_trigger;
led_dat->cdev.brightness_set = adp8870_led_set;
led_dat->cdev.brightness = LED_OFF;
led_dat->flags = cur_led->flags >> FLAG_OFFT_SHIFT;
led_dat->client = client;
led_dat->new_brightness = LED_OFF;
INIT_WORK(&led_dat->work, adp8870_led_work);
ret = led_classdev_register(&client->dev, &led_dat->cdev);
if (ret) {
dev_err(&client->dev, "failed to register LED %d\n",
led_dat->id);
goto err;
}
ret = adp8870_led_setup(led_dat);
if (ret) {
dev_err(&client->dev, "failed to write\n");
i++;
goto err;
}
}
data->led = led;
return 0;
err:
for (i = i - 1; i >= 0; --i) {
led_classdev_unregister(&led[i].cdev);
cancel_work_sync(&led[i].work);
}
return ret;
}
static int adp8870_led_remove(struct i2c_client *client)
{
struct adp8870_backlight_platform_data *pdata =
dev_get_platdata(&client->dev);
struct adp8870_bl *data = i2c_get_clientdata(client);
int i;
for (i = 0; i < pdata->num_leds; i++) {
led_classdev_unregister(&data->led[i].cdev);
cancel_work_sync(&data->led[i].work);
}
return 0;
}
#else
static int adp8870_led_probe(struct i2c_client *client)
{
return 0;
}
static int adp8870_led_remove(struct i2c_client *client)
{
return 0;
}
#endif
static int adp8870_bl_set(struct backlight_device *bl, int brightness)
{
struct adp8870_bl *data = bl_get_data(bl);
struct i2c_client *client = data->client;
int ret = 0;
if (data->pdata->en_ambl_sens) {
if ((brightness > 0) && (brightness < ADP8870_MAX_BRIGHTNESS)) {
/* Disable Ambient Light auto adjust */
ret = adp8870_clr_bits(client, ADP8870_MDCR,
CMP_AUTOEN);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_BLMX1, brightness);
if (ret)
return ret;
} else {
/*
* MAX_BRIGHTNESS -> Enable Ambient Light auto adjust
* restore daylight l1 sysfs brightness
*/
ret = adp8870_write(client, ADP8870_BLMX1,
data->cached_daylight_max);
if (ret)
return ret;
ret = adp8870_set_bits(client, ADP8870_MDCR,
CMP_AUTOEN);
if (ret)
return ret;
}
} else {
ret = adp8870_write(client, ADP8870_BLMX1, brightness);
if (ret)
return ret;
}
if (data->current_brightness && brightness == 0)
ret = adp8870_set_bits(client,
ADP8870_MDCR, DIM_EN);
else if (data->current_brightness == 0 && brightness)
ret = adp8870_clr_bits(client,
ADP8870_MDCR, DIM_EN);
if (!ret)
data->current_brightness = brightness;
return ret;
}
static int adp8870_bl_update_status(struct backlight_device *bl)
{
int brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
return adp8870_bl_set(bl, brightness);
}
static int adp8870_bl_get_brightness(struct backlight_device *bl)
{
struct adp8870_bl *data = bl_get_data(bl);
return data->current_brightness;
}
static const struct backlight_ops adp8870_bl_ops = {
.update_status = adp8870_bl_update_status,
.get_brightness = adp8870_bl_get_brightness,
};
static int adp8870_bl_setup(struct backlight_device *bl)
{
struct adp8870_bl *data = bl_get_data(bl);
struct i2c_client *client = data->client;
struct adp8870_backlight_platform_data *pdata = data->pdata;
int ret = 0;
ret = adp8870_write(client, ADP8870_BLSEL, ~pdata->bl_led_assign);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_PWMLED, pdata->pwm_assign);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_BLMX1, pdata->l1_daylight_max);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_BLDM1, pdata->l1_daylight_dim);
if (ret)
return ret;
if (pdata->en_ambl_sens) {
data->cached_daylight_max = pdata->l1_daylight_max;
ret = adp8870_write(client, ADP8870_BLMX2,
pdata->l2_bright_max);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_BLDM2,
pdata->l2_bright_dim);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_BLMX3,
pdata->l3_office_max);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_BLDM3,
pdata->l3_office_dim);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_BLMX4,
pdata->l4_indoor_max);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_BLDM4,
pdata->l4_indor_dim);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_BLMX5,
pdata->l5_dark_max);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_BLDM5,
pdata->l5_dark_dim);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_L2TRP, pdata->l2_trip);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_L2HYS, pdata->l2_hyst);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_L3TRP, pdata->l3_trip);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_L3HYS, pdata->l3_hyst);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_L4TRP, pdata->l4_trip);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_L4HYS, pdata->l4_hyst);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_L5TRP, pdata->l5_trip);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_L5HYS, pdata->l5_hyst);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_ALS1_EN, L5_EN | L4_EN |
L3_EN | L2_EN);
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_CMP_CTL,
ALS_CMPR_CFG_VAL(pdata->abml_filt));
if (ret)
return ret;
}
ret = adp8870_write(client, ADP8870_CFGR,
BL_CFGR_VAL(pdata->bl_fade_law, 0));
if (ret)
return ret;
ret = adp8870_write(client, ADP8870_BLFR, FADE_VAL(pdata->bl_fade_in,
pdata->bl_fade_out));
if (ret)
return ret;
/*
* ADP8870 Rev0 requires GDWN_DIS bit set
*/
ret = adp8870_set_bits(client, ADP8870_MDCR, BLEN | DIM_EN | NSTBY |
(data->revid == 0 ? GDWN_DIS : 0));
return ret;
}
static ssize_t adp8870_show(struct device *dev, char *buf, int reg)
{
struct adp8870_bl *data = dev_get_drvdata(dev);
int error;
uint8_t reg_val;
mutex_lock(&data->lock);
error = adp8870_read(data->client, reg, &reg_val);
mutex_unlock(&data->lock);
if (error < 0)
return error;
return sprintf(buf, "%u\n", reg_val);
}
static ssize_t adp8870_store(struct device *dev, const char *buf,
size_t count, int reg)
{
struct adp8870_bl *data = dev_get_drvdata(dev);
unsigned long val;
int ret;
ret = kstrtoul(buf, 10, &val);
if (ret)
return ret;
mutex_lock(&data->lock);
adp8870_write(data->client, reg, val);
mutex_unlock(&data->lock);
return count;
}
static ssize_t adp8870_bl_l5_dark_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8870_show(dev, buf, ADP8870_BLMX5);
}
static ssize_t adp8870_bl_l5_dark_max_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
return adp8870_store(dev, buf, count, ADP8870_BLMX5);
}
static DEVICE_ATTR(l5_dark_max, 0664, adp8870_bl_l5_dark_max_show,
adp8870_bl_l5_dark_max_store);
static ssize_t adp8870_bl_l4_indoor_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8870_show(dev, buf, ADP8870_BLMX4);
}
static ssize_t adp8870_bl_l4_indoor_max_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
return adp8870_store(dev, buf, count, ADP8870_BLMX4);
}
static DEVICE_ATTR(l4_indoor_max, 0664, adp8870_bl_l4_indoor_max_show,
adp8870_bl_l4_indoor_max_store);
static ssize_t adp8870_bl_l3_office_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8870_show(dev, buf, ADP8870_BLMX3);
}
static ssize_t adp8870_bl_l3_office_max_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
return adp8870_store(dev, buf, count, ADP8870_BLMX3);
}
static DEVICE_ATTR(l3_office_max, 0664, adp8870_bl_l3_office_max_show,
adp8870_bl_l3_office_max_store);
static ssize_t adp8870_bl_l2_bright_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8870_show(dev, buf, ADP8870_BLMX2);
}
static ssize_t adp8870_bl_l2_bright_max_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
return adp8870_store(dev, buf, count, ADP8870_BLMX2);
}
static DEVICE_ATTR(l2_bright_max, 0664, adp8870_bl_l2_bright_max_show,
adp8870_bl_l2_bright_max_store);
static ssize_t adp8870_bl_l1_daylight_max_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8870_show(dev, buf, ADP8870_BLMX1);
}
static ssize_t adp8870_bl_l1_daylight_max_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct adp8870_bl *data = dev_get_drvdata(dev);
int ret = kstrtoul(buf, 10, &data->cached_daylight_max);
if (ret)
return ret;
return adp8870_store(dev, buf, count, ADP8870_BLMX1);
}
static DEVICE_ATTR(l1_daylight_max, 0664, adp8870_bl_l1_daylight_max_show,
adp8870_bl_l1_daylight_max_store);
static ssize_t adp8870_bl_l5_dark_dim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8870_show(dev, buf, ADP8870_BLDM5);
}
static ssize_t adp8870_bl_l5_dark_dim_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp8870_store(dev, buf, count, ADP8870_BLDM5);
}
static DEVICE_ATTR(l5_dark_dim, 0664, adp8870_bl_l5_dark_dim_show,
adp8870_bl_l5_dark_dim_store);
static ssize_t adp8870_bl_l4_indoor_dim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8870_show(dev, buf, ADP8870_BLDM4);
}
static ssize_t adp8870_bl_l4_indoor_dim_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp8870_store(dev, buf, count, ADP8870_BLDM4);
}
static DEVICE_ATTR(l4_indoor_dim, 0664, adp8870_bl_l4_indoor_dim_show,
adp8870_bl_l4_indoor_dim_store);
static ssize_t adp8870_bl_l3_office_dim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8870_show(dev, buf, ADP8870_BLDM3);
}
static ssize_t adp8870_bl_l3_office_dim_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp8870_store(dev, buf, count, ADP8870_BLDM3);
}
static DEVICE_ATTR(l3_office_dim, 0664, adp8870_bl_l3_office_dim_show,
adp8870_bl_l3_office_dim_store);
static ssize_t adp8870_bl_l2_bright_dim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8870_show(dev, buf, ADP8870_BLDM2);
}
static ssize_t adp8870_bl_l2_bright_dim_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp8870_store(dev, buf, count, ADP8870_BLDM2);
}
static DEVICE_ATTR(l2_bright_dim, 0664, adp8870_bl_l2_bright_dim_show,
adp8870_bl_l2_bright_dim_store);
static ssize_t adp8870_bl_l1_daylight_dim_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return adp8870_show(dev, buf, ADP8870_BLDM1);
}
static ssize_t adp8870_bl_l1_daylight_dim_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
return adp8870_store(dev, buf, count, ADP8870_BLDM1);
}
static DEVICE_ATTR(l1_daylight_dim, 0664, adp8870_bl_l1_daylight_dim_show,
adp8870_bl_l1_daylight_dim_store);
#ifdef ADP8870_EXT_FEATURES
static ssize_t adp8870_bl_ambient_light_level_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adp8870_bl *data = dev_get_drvdata(dev);
int error;
uint8_t reg_val;
uint16_t ret_val;
mutex_lock(&data->lock);
error = adp8870_read(data->client, ADP8870_PH1LEVL, &reg_val);
if (error < 0) {
mutex_unlock(&data->lock);
return error;
}
ret_val = reg_val;
error = adp8870_read(data->client, ADP8870_PH1LEVH, &reg_val);
mutex_unlock(&data->lock);
if (error < 0)
return error;
/* Return 13-bit conversion value for the first light sensor */
ret_val += (reg_val & 0x1F) << 8;
return sprintf(buf, "%u\n", ret_val);
}
static DEVICE_ATTR(ambient_light_level, 0444,
adp8870_bl_ambient_light_level_show, NULL);
static ssize_t adp8870_bl_ambient_light_zone_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct adp8870_bl *data = dev_get_drvdata(dev);
int error;
uint8_t reg_val;
mutex_lock(&data->lock);
error = adp8870_read(data->client, ADP8870_CFGR, &reg_val);
mutex_unlock(&data->lock);
if (error < 0)
return error;
return sprintf(buf, "%u\n",
((reg_val >> CFGR_BLV_SHIFT) & CFGR_BLV_MASK) + 1);
}
static ssize_t adp8870_bl_ambient_light_zone_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct adp8870_bl *data = dev_get_drvdata(dev);
unsigned long val;
uint8_t reg_val;
int ret;
ret = kstrtoul(buf, 10, &val);
if (ret)
return ret;
if (val == 0) {
/* Enable automatic ambient light sensing */
adp8870_set_bits(data->client, ADP8870_MDCR, CMP_AUTOEN);
} else if ((val > 0) && (val < 6)) {
/* Disable automatic ambient light sensing */
adp8870_clr_bits(data->client, ADP8870_MDCR, CMP_AUTOEN);
/* Set user supplied ambient light zone */
mutex_lock(&data->lock);
adp8870_read(data->client, ADP8870_CFGR, &reg_val);
reg_val &= ~(CFGR_BLV_MASK << CFGR_BLV_SHIFT);
reg_val |= (val - 1) << CFGR_BLV_SHIFT;
adp8870_write(data->client, ADP8870_CFGR, reg_val);
mutex_unlock(&data->lock);
}
return count;
}
static DEVICE_ATTR(ambient_light_zone, 0664,
adp8870_bl_ambient_light_zone_show,
adp8870_bl_ambient_light_zone_store);
#endif
static struct attribute *adp8870_bl_attributes[] = {
&dev_attr_l5_dark_max.attr,
&dev_attr_l5_dark_dim.attr,
&dev_attr_l4_indoor_max.attr,
&dev_attr_l4_indoor_dim.attr,
&dev_attr_l3_office_max.attr,
&dev_attr_l3_office_dim.attr,
&dev_attr_l2_bright_max.attr,
&dev_attr_l2_bright_dim.attr,
&dev_attr_l1_daylight_max.attr,
&dev_attr_l1_daylight_dim.attr,
#ifdef ADP8870_EXT_FEATURES
&dev_attr_ambient_light_level.attr,
&dev_attr_ambient_light_zone.attr,
#endif
NULL
};
static const struct attribute_group adp8870_bl_attr_group = {
.attrs = adp8870_bl_attributes,
};
static int adp8870_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct backlight_properties props;
struct backlight_device *bl;
struct adp8870_bl *data;
struct adp8870_backlight_platform_data *pdata =
dev_get_platdata(&client->dev);
uint8_t reg_val;
int ret;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev, "SMBUS Byte Data not Supported\n");
return -EIO;
}
if (!pdata) {
dev_err(&client->dev, "no platform data?\n");
return -EINVAL;
}
ret = adp8870_read(client, ADP8870_MFDVID, &reg_val);
if (ret < 0)
return -EIO;
if (ADP8870_MANID(reg_val) != ADP8870_MANUFID) {
dev_err(&client->dev, "failed to probe\n");
return -ENODEV;
}
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
data->revid = ADP8870_DEVID(reg_val);
data->client = client;
data->pdata = pdata;
data->id = id->driver_data;
data->current_brightness = 0;
i2c_set_clientdata(client, data);
mutex_init(&data->lock);
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW;
props.max_brightness = props.brightness = ADP8870_MAX_BRIGHTNESS;
bl = devm_backlight_device_register(&client->dev,
dev_driver_string(&client->dev),
&client->dev, data, &adp8870_bl_ops, &props);
if (IS_ERR(bl)) {
dev_err(&client->dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
data->bl = bl;
if (pdata->en_ambl_sens) {
ret = sysfs_create_group(&bl->dev.kobj,
&adp8870_bl_attr_group);
if (ret) {
dev_err(&client->dev, "failed to register sysfs\n");
return ret;
}
}
ret = adp8870_bl_setup(bl);
if (ret) {
ret = -EIO;
goto out;
}
backlight_update_status(bl);
dev_info(&client->dev, "Rev.%d Backlight\n", data->revid);
if (pdata->num_leds)
adp8870_led_probe(client);
return 0;
out:
if (data->pdata->en_ambl_sens)
sysfs_remove_group(&data->bl->dev.kobj,
&adp8870_bl_attr_group);
return ret;
}
static int adp8870_remove(struct i2c_client *client)
{
struct adp8870_bl *data = i2c_get_clientdata(client);
adp8870_clr_bits(client, ADP8870_MDCR, NSTBY);
if (data->led)
adp8870_led_remove(client);
if (data->pdata->en_ambl_sens)
sysfs_remove_group(&data->bl->dev.kobj,
&adp8870_bl_attr_group);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int adp8870_i2c_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
adp8870_clr_bits(client, ADP8870_MDCR, NSTBY);
return 0;
}
static int adp8870_i2c_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
adp8870_set_bits(client, ADP8870_MDCR, NSTBY | BLEN);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(adp8870_i2c_pm_ops, adp8870_i2c_suspend,
adp8870_i2c_resume);
static const struct i2c_device_id adp8870_id[] = {
{ "adp8870", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, adp8870_id);
static struct i2c_driver adp8870_driver = {
.driver = {
.name = KBUILD_MODNAME,
.pm = &adp8870_i2c_pm_ops,
},
.probe = adp8870_probe,
.remove = adp8870_remove,
.id_table = adp8870_id,
};
module_i2c_driver(adp8870_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
MODULE_DESCRIPTION("ADP8870 Backlight driver");
MODULE_ALIAS("i2c:adp8870-backlight");

View file

@ -0,0 +1,571 @@
/*
* ams369fg06 AMOLED LCD panel driver.
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* Author: Jingoo Han <jg1.han@samsung.com>
*
* Derived from drivers/video/s6e63m0.c
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/wait.h>
#define SLEEPMSEC 0x1000
#define ENDDEF 0x2000
#define DEFMASK 0xFF00
#define COMMAND_ONLY 0xFE
#define DATA_ONLY 0xFF
#define MAX_GAMMA_LEVEL 5
#define GAMMA_TABLE_COUNT 21
#define MIN_BRIGHTNESS 0
#define MAX_BRIGHTNESS 255
#define DEFAULT_BRIGHTNESS 150
struct ams369fg06 {
struct device *dev;
struct spi_device *spi;
unsigned int power;
struct lcd_device *ld;
struct backlight_device *bd;
struct lcd_platform_data *lcd_pd;
};
static const unsigned short seq_display_on[] = {
0x14, 0x03,
ENDDEF, 0x0000
};
static const unsigned short seq_display_off[] = {
0x14, 0x00,
ENDDEF, 0x0000
};
static const unsigned short seq_stand_by_on[] = {
0x1D, 0xA1,
SLEEPMSEC, 200,
ENDDEF, 0x0000
};
static const unsigned short seq_stand_by_off[] = {
0x1D, 0xA0,
SLEEPMSEC, 250,
ENDDEF, 0x0000
};
static const unsigned short seq_setting[] = {
0x31, 0x08,
0x32, 0x14,
0x30, 0x02,
0x27, 0x01,
0x12, 0x08,
0x13, 0x08,
0x15, 0x00,
0x16, 0x00,
0xef, 0xd0,
DATA_ONLY, 0xe8,
0x39, 0x44,
0x40, 0x00,
0x41, 0x3f,
0x42, 0x2a,
0x43, 0x27,
0x44, 0x27,
0x45, 0x1f,
0x46, 0x44,
0x50, 0x00,
0x51, 0x00,
0x52, 0x17,
0x53, 0x24,
0x54, 0x26,
0x55, 0x1f,
0x56, 0x43,
0x60, 0x00,
0x61, 0x3f,
0x62, 0x2a,
0x63, 0x25,
0x64, 0x24,
0x65, 0x1b,
0x66, 0x5c,
0x17, 0x22,
0x18, 0x33,
0x19, 0x03,
0x1a, 0x01,
0x22, 0xa4,
0x23, 0x00,
0x26, 0xa0,
0x1d, 0xa0,
SLEEPMSEC, 300,
0x14, 0x03,
ENDDEF, 0x0000
};
/* gamma value: 2.2 */
static const unsigned int ams369fg06_22_250[] = {
0x00, 0x3f, 0x2a, 0x27, 0x27, 0x1f, 0x44,
0x00, 0x00, 0x17, 0x24, 0x26, 0x1f, 0x43,
0x00, 0x3f, 0x2a, 0x25, 0x24, 0x1b, 0x5c,
};
static const unsigned int ams369fg06_22_200[] = {
0x00, 0x3f, 0x28, 0x29, 0x27, 0x21, 0x3e,
0x00, 0x00, 0x10, 0x25, 0x27, 0x20, 0x3d,
0x00, 0x3f, 0x28, 0x27, 0x25, 0x1d, 0x53,
};
static const unsigned int ams369fg06_22_150[] = {
0x00, 0x3f, 0x2d, 0x29, 0x28, 0x23, 0x37,
0x00, 0x00, 0x0b, 0x25, 0x28, 0x22, 0x36,
0x00, 0x3f, 0x2b, 0x28, 0x26, 0x1f, 0x4a,
};
static const unsigned int ams369fg06_22_100[] = {
0x00, 0x3f, 0x30, 0x2a, 0x2b, 0x24, 0x2f,
0x00, 0x00, 0x00, 0x25, 0x29, 0x24, 0x2e,
0x00, 0x3f, 0x2f, 0x29, 0x29, 0x21, 0x3f,
};
static const unsigned int ams369fg06_22_50[] = {
0x00, 0x3f, 0x3c, 0x2c, 0x2d, 0x27, 0x24,
0x00, 0x00, 0x00, 0x22, 0x2a, 0x27, 0x23,
0x00, 0x3f, 0x3b, 0x2c, 0x2b, 0x24, 0x31,
};
struct ams369fg06_gamma {
unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
};
static struct ams369fg06_gamma gamma_table = {
.gamma_22_table[0] = (unsigned int *)&ams369fg06_22_50,
.gamma_22_table[1] = (unsigned int *)&ams369fg06_22_100,
.gamma_22_table[2] = (unsigned int *)&ams369fg06_22_150,
.gamma_22_table[3] = (unsigned int *)&ams369fg06_22_200,
.gamma_22_table[4] = (unsigned int *)&ams369fg06_22_250,
};
static int ams369fg06_spi_write_byte(struct ams369fg06 *lcd, int addr, int data)
{
u16 buf[1];
struct spi_message msg;
struct spi_transfer xfer = {
.len = 2,
.tx_buf = buf,
};
buf[0] = (addr << 8) | data;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
return spi_sync(lcd->spi, &msg);
}
static int ams369fg06_spi_write(struct ams369fg06 *lcd, unsigned char address,
unsigned char command)
{
int ret = 0;
if (address != DATA_ONLY)
ret = ams369fg06_spi_write_byte(lcd, 0x70, address);
if (command != COMMAND_ONLY)
ret = ams369fg06_spi_write_byte(lcd, 0x72, command);
return ret;
}
static int ams369fg06_panel_send_sequence(struct ams369fg06 *lcd,
const unsigned short *wbuf)
{
int ret = 0, i = 0;
while ((wbuf[i] & DEFMASK) != ENDDEF) {
if ((wbuf[i] & DEFMASK) != SLEEPMSEC) {
ret = ams369fg06_spi_write(lcd, wbuf[i], wbuf[i+1]);
if (ret)
break;
} else {
msleep(wbuf[i+1]);
}
i += 2;
}
return ret;
}
static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd,
const unsigned int *gamma)
{
unsigned int i = 0;
int ret = 0;
for (i = 0 ; i < GAMMA_TABLE_COUNT / 3; i++) {
ret = ams369fg06_spi_write(lcd, 0x40 + i, gamma[i]);
ret = ams369fg06_spi_write(lcd, 0x50 + i, gamma[i+7*1]);
ret = ams369fg06_spi_write(lcd, 0x60 + i, gamma[i+7*2]);
if (ret) {
dev_err(lcd->dev, "failed to set gamma table.\n");
goto gamma_err;
}
}
gamma_err:
return ret;
}
static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness)
{
int ret = 0;
int gamma = 0;
if ((brightness >= 0) && (brightness <= 50))
gamma = 0;
else if ((brightness > 50) && (brightness <= 100))
gamma = 1;
else if ((brightness > 100) && (brightness <= 150))
gamma = 2;
else if ((brightness > 150) && (brightness <= 200))
gamma = 3;
else if ((brightness > 200) && (brightness <= 255))
gamma = 4;
ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
return ret;
}
static int ams369fg06_ldi_init(struct ams369fg06 *lcd)
{
int ret, i;
static const unsigned short *init_seq[] = {
seq_setting,
seq_stand_by_off,
};
for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
if (ret)
break;
}
return ret;
}
static int ams369fg06_ldi_enable(struct ams369fg06 *lcd)
{
int ret, i;
static const unsigned short *init_seq[] = {
seq_stand_by_off,
seq_display_on,
};
for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
if (ret)
break;
}
return ret;
}
static int ams369fg06_ldi_disable(struct ams369fg06 *lcd)
{
int ret, i;
static const unsigned short *init_seq[] = {
seq_display_off,
seq_stand_by_on,
};
for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
if (ret)
break;
}
return ret;
}
static int ams369fg06_power_is_on(int power)
{
return power <= FB_BLANK_NORMAL;
}
static int ams369fg06_power_on(struct ams369fg06 *lcd)
{
int ret = 0;
struct lcd_platform_data *pd;
struct backlight_device *bd;
pd = lcd->lcd_pd;
bd = lcd->bd;
if (pd->power_on) {
pd->power_on(lcd->ld, 1);
msleep(pd->power_on_delay);
}
if (!pd->reset) {
dev_err(lcd->dev, "reset is NULL.\n");
return -EINVAL;
}
pd->reset(lcd->ld);
msleep(pd->reset_delay);
ret = ams369fg06_ldi_init(lcd);
if (ret) {
dev_err(lcd->dev, "failed to initialize ldi.\n");
return ret;
}
ret = ams369fg06_ldi_enable(lcd);
if (ret) {
dev_err(lcd->dev, "failed to enable ldi.\n");
return ret;
}
/* set brightness to current value after power on or resume. */
ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
if (ret) {
dev_err(lcd->dev, "lcd gamma setting failed.\n");
return ret;
}
return 0;
}
static int ams369fg06_power_off(struct ams369fg06 *lcd)
{
int ret;
struct lcd_platform_data *pd;
pd = lcd->lcd_pd;
ret = ams369fg06_ldi_disable(lcd);
if (ret) {
dev_err(lcd->dev, "lcd setting failed.\n");
return -EIO;
}
msleep(pd->power_off_delay);
if (pd->power_on)
pd->power_on(lcd->ld, 0);
return 0;
}
static int ams369fg06_power(struct ams369fg06 *lcd, int power)
{
int ret = 0;
if (ams369fg06_power_is_on(power) &&
!ams369fg06_power_is_on(lcd->power))
ret = ams369fg06_power_on(lcd);
else if (!ams369fg06_power_is_on(power) &&
ams369fg06_power_is_on(lcd->power))
ret = ams369fg06_power_off(lcd);
if (!ret)
lcd->power = power;
return ret;
}
static int ams369fg06_get_power(struct lcd_device *ld)
{
struct ams369fg06 *lcd = lcd_get_data(ld);
return lcd->power;
}
static int ams369fg06_set_power(struct lcd_device *ld, int power)
{
struct ams369fg06 *lcd = lcd_get_data(ld);
if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
power != FB_BLANK_NORMAL) {
dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
return -EINVAL;
}
return ams369fg06_power(lcd, power);
}
static int ams369fg06_set_brightness(struct backlight_device *bd)
{
int ret = 0;
int brightness = bd->props.brightness;
struct ams369fg06 *lcd = bl_get_data(bd);
if (brightness < MIN_BRIGHTNESS ||
brightness > bd->props.max_brightness) {
dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
MIN_BRIGHTNESS, MAX_BRIGHTNESS);
return -EINVAL;
}
ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
if (ret) {
dev_err(&bd->dev, "lcd brightness setting failed.\n");
return -EIO;
}
return ret;
}
static struct lcd_ops ams369fg06_lcd_ops = {
.get_power = ams369fg06_get_power,
.set_power = ams369fg06_set_power,
};
static const struct backlight_ops ams369fg06_backlight_ops = {
.update_status = ams369fg06_set_brightness,
};
static int ams369fg06_probe(struct spi_device *spi)
{
int ret = 0;
struct ams369fg06 *lcd = NULL;
struct lcd_device *ld = NULL;
struct backlight_device *bd = NULL;
struct backlight_properties props;
lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
if (!lcd)
return -ENOMEM;
/* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
spi->bits_per_word = 16;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "spi setup failed.\n");
return ret;
}
lcd->spi = spi;
lcd->dev = &spi->dev;
lcd->lcd_pd = dev_get_platdata(&spi->dev);
if (!lcd->lcd_pd) {
dev_err(&spi->dev, "platform data is NULL\n");
return -EINVAL;
}
ld = devm_lcd_device_register(&spi->dev, "ams369fg06", &spi->dev, lcd,
&ams369fg06_lcd_ops);
if (IS_ERR(ld))
return PTR_ERR(ld);
lcd->ld = ld;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = MAX_BRIGHTNESS;
bd = devm_backlight_device_register(&spi->dev, "ams369fg06-bl",
&spi->dev, lcd,
&ams369fg06_backlight_ops, &props);
if (IS_ERR(bd))
return PTR_ERR(bd);
bd->props.brightness = DEFAULT_BRIGHTNESS;
lcd->bd = bd;
if (!lcd->lcd_pd->lcd_enabled) {
/*
* if lcd panel was off from bootloader then
* current lcd status is powerdown and then
* it enables lcd panel.
*/
lcd->power = FB_BLANK_POWERDOWN;
ams369fg06_power(lcd, FB_BLANK_UNBLANK);
} else {
lcd->power = FB_BLANK_UNBLANK;
}
spi_set_drvdata(spi, lcd);
dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
return 0;
}
static int ams369fg06_remove(struct spi_device *spi)
{
struct ams369fg06 *lcd = spi_get_drvdata(spi);
ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int ams369fg06_suspend(struct device *dev)
{
struct ams369fg06 *lcd = dev_get_drvdata(dev);
dev_dbg(dev, "lcd->power = %d\n", lcd->power);
/*
* when lcd panel is suspend, lcd panel becomes off
* regardless of status.
*/
return ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
}
static int ams369fg06_resume(struct device *dev)
{
struct ams369fg06 *lcd = dev_get_drvdata(dev);
lcd->power = FB_BLANK_POWERDOWN;
return ams369fg06_power(lcd, FB_BLANK_UNBLANK);
}
#endif
static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend,
ams369fg06_resume);
static void ams369fg06_shutdown(struct spi_device *spi)
{
struct ams369fg06 *lcd = spi_get_drvdata(spi);
ams369fg06_power(lcd, FB_BLANK_POWERDOWN);
}
static struct spi_driver ams369fg06_driver = {
.driver = {
.name = "ams369fg06",
.owner = THIS_MODULE,
.pm = &ams369fg06_pm_ops,
},
.probe = ams369fg06_probe,
.remove = ams369fg06_remove,
.shutdown = ams369fg06_shutdown,
};
module_spi_driver(ams369fg06_driver);
MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
MODULE_DESCRIPTION("ams369fg06 LCD Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,257 @@
/*
* Backlight Driver for Intel-based Apples
*
* Copyright (c) Red Hat <mjg@redhat.com>
* Based on code from Pommed:
* Copyright (C) 2006 Nicolas Boichat <nicolas @boichat.ch>
* Copyright (C) 2006 Felipe Alfaro Solana <felipe_alfaro @linuxmail.org>
* Copyright (C) 2007 Julien BLACHE <jb@jblache.org>
*
* 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.
*
* This driver triggers SMIs which cause the firmware to change the
* backlight brightness. This is icky in many ways, but it's impractical to
* get at the firmware code in order to figure out what it's actually doing.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/pci.h>
#include <linux/acpi.h>
#include <linux/atomic.h>
#include <linux/apple_bl.h>
static struct backlight_device *apple_backlight_device;
struct hw_data {
/* I/O resource to allocate. */
unsigned long iostart;
unsigned long iolen;
/* Backlight operations structure. */
const struct backlight_ops backlight_ops;
void (*set_brightness)(int);
};
static const struct hw_data *hw_data;
/* Module parameters. */
static int debug;
module_param_named(debug, debug, int, 0644);
MODULE_PARM_DESC(debug, "Set to one to enable debugging messages.");
/*
* Implementation for machines with Intel chipset.
*/
static void intel_chipset_set_brightness(int intensity)
{
outb(0x04 | (intensity << 4), 0xb3);
outb(0xbf, 0xb2);
}
static int intel_chipset_send_intensity(struct backlight_device *bd)
{
int intensity = bd->props.brightness;
if (debug)
pr_debug("setting brightness to %d\n", intensity);
intel_chipset_set_brightness(intensity);
return 0;
}
static int intel_chipset_get_intensity(struct backlight_device *bd)
{
int intensity;
outb(0x03, 0xb3);
outb(0xbf, 0xb2);
intensity = inb(0xb3) >> 4;
if (debug)
pr_debug("read brightness of %d\n", intensity);
return intensity;
}
static const struct hw_data intel_chipset_data = {
.iostart = 0xb2,
.iolen = 2,
.backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.get_brightness = intel_chipset_get_intensity,
.update_status = intel_chipset_send_intensity,
},
.set_brightness = intel_chipset_set_brightness,
};
/*
* Implementation for machines with Nvidia chipset.
*/
static void nvidia_chipset_set_brightness(int intensity)
{
outb(0x04 | (intensity << 4), 0x52f);
outb(0xbf, 0x52e);
}
static int nvidia_chipset_send_intensity(struct backlight_device *bd)
{
int intensity = bd->props.brightness;
if (debug)
pr_debug("setting brightness to %d\n", intensity);
nvidia_chipset_set_brightness(intensity);
return 0;
}
static int nvidia_chipset_get_intensity(struct backlight_device *bd)
{
int intensity;
outb(0x03, 0x52f);
outb(0xbf, 0x52e);
intensity = inb(0x52f) >> 4;
if (debug)
pr_debug("read brightness of %d\n", intensity);
return intensity;
}
static const struct hw_data nvidia_chipset_data = {
.iostart = 0x52e,
.iolen = 2,
.backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.get_brightness = nvidia_chipset_get_intensity,
.update_status = nvidia_chipset_send_intensity
},
.set_brightness = nvidia_chipset_set_brightness,
};
static int apple_bl_add(struct acpi_device *dev)
{
struct backlight_properties props;
struct pci_dev *host;
int intensity;
host = pci_get_bus_and_slot(0, 0);
if (!host) {
pr_err("unable to find PCI host\n");
return -ENODEV;
}
if (host->vendor == PCI_VENDOR_ID_INTEL)
hw_data = &intel_chipset_data;
else if (host->vendor == PCI_VENDOR_ID_NVIDIA)
hw_data = &nvidia_chipset_data;
pci_dev_put(host);
if (!hw_data) {
pr_err("unknown hardware\n");
return -ENODEV;
}
/* Check that the hardware responds - this may not work under EFI */
intensity = hw_data->backlight_ops.get_brightness(NULL);
if (!intensity) {
hw_data->set_brightness(1);
if (!hw_data->backlight_ops.get_brightness(NULL))
return -ENODEV;
hw_data->set_brightness(0);
}
if (!request_region(hw_data->iostart, hw_data->iolen,
"Apple backlight"))
return -ENXIO;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_PLATFORM;
props.max_brightness = 15;
apple_backlight_device = backlight_device_register("apple_backlight",
NULL, NULL, &hw_data->backlight_ops, &props);
if (IS_ERR(apple_backlight_device)) {
release_region(hw_data->iostart, hw_data->iolen);
return PTR_ERR(apple_backlight_device);
}
apple_backlight_device->props.brightness =
hw_data->backlight_ops.get_brightness(apple_backlight_device);
backlight_update_status(apple_backlight_device);
return 0;
}
static int apple_bl_remove(struct acpi_device *dev)
{
backlight_device_unregister(apple_backlight_device);
release_region(hw_data->iostart, hw_data->iolen);
hw_data = NULL;
return 0;
}
static const struct acpi_device_id apple_bl_ids[] = {
{"APP0002", 0},
{"", 0},
};
static struct acpi_driver apple_bl_driver = {
.name = "Apple backlight",
.ids = apple_bl_ids,
.ops = {
.add = apple_bl_add,
.remove = apple_bl_remove,
},
};
static atomic_t apple_bl_registered = ATOMIC_INIT(0);
int apple_bl_register(void)
{
if (atomic_xchg(&apple_bl_registered, 1) == 0)
return acpi_bus_register_driver(&apple_bl_driver);
return 0;
}
EXPORT_SYMBOL_GPL(apple_bl_register);
void apple_bl_unregister(void)
{
if (atomic_xchg(&apple_bl_registered, 0) == 1)
acpi_bus_unregister_driver(&apple_bl_driver);
}
EXPORT_SYMBOL_GPL(apple_bl_unregister);
static int __init apple_bl_init(void)
{
return apple_bl_register();
}
static void __exit apple_bl_exit(void)
{
apple_bl_unregister();
}
module_init(apple_bl_init);
module_exit(apple_bl_exit);
MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
MODULE_DESCRIPTION("Apple Backlight Driver");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(acpi, apple_bl_ids);
MODULE_ALIAS("mbp_nvidia_bl");

View file

@ -0,0 +1,479 @@
/*
* AS3711 PMIC backlight driver, using DCDC Step Up Converters
*
* Copyright (C) 2012 Renesas Electronics Corporation
* Author: Guennadi Liakhovetski, <g.liakhovetski@gmx.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the version 2 of the GNU General Public License as
* published by the Free Software Foundation
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/kernel.h>
#include <linux/mfd/as3711.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
enum as3711_bl_type {
AS3711_BL_SU1,
AS3711_BL_SU2,
};
struct as3711_bl_data {
bool powered;
const char *fb_name;
struct device *fb_dev;
enum as3711_bl_type type;
int brightness;
struct backlight_device *bl;
};
struct as3711_bl_supply {
struct as3711_bl_data su1;
struct as3711_bl_data su2;
const struct as3711_bl_pdata *pdata;
struct as3711 *as3711;
};
static struct as3711_bl_supply *to_supply(struct as3711_bl_data *su)
{
switch (su->type) {
case AS3711_BL_SU1:
return container_of(su, struct as3711_bl_supply, su1);
case AS3711_BL_SU2:
return container_of(su, struct as3711_bl_supply, su2);
}
return NULL;
}
static int as3711_set_brightness_auto_i(struct as3711_bl_data *data,
unsigned int brightness)
{
struct as3711_bl_supply *supply = to_supply(data);
struct as3711 *as3711 = supply->as3711;
const struct as3711_bl_pdata *pdata = supply->pdata;
int ret = 0;
/* Only all equal current values are supported */
if (pdata->su2_auto_curr1)
ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE,
brightness);
if (!ret && pdata->su2_auto_curr2)
ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE,
brightness);
if (!ret && pdata->su2_auto_curr3)
ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE,
brightness);
return ret;
}
static int as3711_set_brightness_v(struct as3711 *as3711,
unsigned int brightness,
unsigned int reg)
{
if (brightness > 31)
return -EINVAL;
return regmap_update_bits(as3711->regmap, reg, 0xf0,
brightness << 4);
}
static int as3711_bl_su2_reset(struct as3711_bl_supply *supply)
{
struct as3711 *as3711 = supply->as3711;
int ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_5,
3, supply->pdata->su2_fbprot);
if (!ret)
ret = regmap_update_bits(as3711->regmap,
AS3711_STEPUP_CONTROL_2, 1, 0);
if (!ret)
ret = regmap_update_bits(as3711->regmap,
AS3711_STEPUP_CONTROL_2, 1, 1);
return ret;
}
/*
* Someone with less fragile or less expensive hardware could try to simplify
* the brightness adjustment procedure.
*/
static int as3711_bl_update_status(struct backlight_device *bl)
{
struct as3711_bl_data *data = bl_get_data(bl);
struct as3711_bl_supply *supply = to_supply(data);
struct as3711 *as3711 = supply->as3711;
int brightness = bl->props.brightness;
int ret = 0;
dev_dbg(&bl->dev, "%s(): brightness %u, pwr %x, blank %x, state %x\n",
__func__, bl->props.brightness, bl->props.power,
bl->props.fb_blank, bl->props.state);
if (bl->props.power != FB_BLANK_UNBLANK ||
bl->props.fb_blank != FB_BLANK_UNBLANK ||
bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
brightness = 0;
if (data->type == AS3711_BL_SU1) {
ret = as3711_set_brightness_v(as3711, brightness,
AS3711_STEPUP_CONTROL_1);
} else {
const struct as3711_bl_pdata *pdata = supply->pdata;
switch (pdata->su2_feedback) {
case AS3711_SU2_VOLTAGE:
ret = as3711_set_brightness_v(as3711, brightness,
AS3711_STEPUP_CONTROL_2);
break;
case AS3711_SU2_CURR_AUTO:
ret = as3711_set_brightness_auto_i(data, brightness / 4);
if (ret < 0)
return ret;
if (brightness) {
ret = as3711_bl_su2_reset(supply);
if (ret < 0)
return ret;
udelay(500);
ret = as3711_set_brightness_auto_i(data, brightness);
} else {
ret = regmap_update_bits(as3711->regmap,
AS3711_STEPUP_CONTROL_2, 1, 0);
}
break;
/* Manual one current feedback pin below */
case AS3711_SU2_CURR1:
ret = regmap_write(as3711->regmap, AS3711_CURR1_VALUE,
brightness);
break;
case AS3711_SU2_CURR2:
ret = regmap_write(as3711->regmap, AS3711_CURR2_VALUE,
brightness);
break;
case AS3711_SU2_CURR3:
ret = regmap_write(as3711->regmap, AS3711_CURR3_VALUE,
brightness);
break;
default:
ret = -EINVAL;
}
}
if (!ret)
data->brightness = brightness;
return ret;
}
static int as3711_bl_get_brightness(struct backlight_device *bl)
{
struct as3711_bl_data *data = bl_get_data(bl);
return data->brightness;
}
static const struct backlight_ops as3711_bl_ops = {
.update_status = as3711_bl_update_status,
.get_brightness = as3711_bl_get_brightness,
};
static int as3711_bl_init_su2(struct as3711_bl_supply *supply)
{
struct as3711 *as3711 = supply->as3711;
const struct as3711_bl_pdata *pdata = supply->pdata;
u8 ctl = 0;
int ret;
dev_dbg(as3711->dev, "%s(): use %u\n", __func__, pdata->su2_feedback);
/* Turn SU2 off */
ret = regmap_write(as3711->regmap, AS3711_STEPUP_CONTROL_2, 0);
if (ret < 0)
return ret;
switch (pdata->su2_feedback) {
case AS3711_SU2_VOLTAGE:
ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 0);
break;
case AS3711_SU2_CURR1:
ctl = 1;
ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 1);
break;
case AS3711_SU2_CURR2:
ctl = 4;
ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 2);
break;
case AS3711_SU2_CURR3:
ctl = 0x10;
ret = regmap_update_bits(as3711->regmap, AS3711_STEPUP_CONTROL_4, 3, 3);
break;
case AS3711_SU2_CURR_AUTO:
if (pdata->su2_auto_curr1)
ctl = 2;
if (pdata->su2_auto_curr2)
ctl |= 8;
if (pdata->su2_auto_curr3)
ctl |= 0x20;
ret = 0;
break;
default:
return -EINVAL;
}
if (!ret)
ret = regmap_write(as3711->regmap, AS3711_CURR_CONTROL, ctl);
return ret;
}
static int as3711_bl_register(struct platform_device *pdev,
unsigned int max_brightness, struct as3711_bl_data *su)
{
struct backlight_properties props = {.type = BACKLIGHT_RAW,};
struct backlight_device *bl;
/* max tuning I = 31uA for voltage- and 38250uA for current-feedback */
props.max_brightness = max_brightness;
bl = devm_backlight_device_register(&pdev->dev,
su->type == AS3711_BL_SU1 ?
"as3711-su1" : "as3711-su2",
&pdev->dev, su,
&as3711_bl_ops, &props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
bl->props.brightness = props.max_brightness;
backlight_update_status(bl);
su->bl = bl;
return 0;
}
static int as3711_backlight_parse_dt(struct device *dev)
{
struct as3711_bl_pdata *pdata = dev_get_platdata(dev);
struct device_node *bl =
of_find_node_by_name(dev->parent->of_node, "backlight"), *fb;
int ret;
if (!bl) {
dev_dbg(dev, "backlight node not found\n");
return -ENODEV;
}
fb = of_parse_phandle(bl, "su1-dev", 0);
if (fb) {
pdata->su1_fb = fb->full_name;
ret = of_property_read_u32(bl, "su1-max-uA", &pdata->su1_max_uA);
if (pdata->su1_max_uA <= 0)
ret = -EINVAL;
if (ret < 0)
return ret;
}
fb = of_parse_phandle(bl, "su2-dev", 0);
if (fb) {
int count = 0;
pdata->su2_fb = fb->full_name;
ret = of_property_read_u32(bl, "su2-max-uA", &pdata->su2_max_uA);
if (pdata->su2_max_uA <= 0)
ret = -EINVAL;
if (ret < 0)
return ret;
if (of_find_property(bl, "su2-feedback-voltage", NULL)) {
pdata->su2_feedback = AS3711_SU2_VOLTAGE;
count++;
}
if (of_find_property(bl, "su2-feedback-curr1", NULL)) {
pdata->su2_feedback = AS3711_SU2_CURR1;
count++;
}
if (of_find_property(bl, "su2-feedback-curr2", NULL)) {
pdata->su2_feedback = AS3711_SU2_CURR2;
count++;
}
if (of_find_property(bl, "su2-feedback-curr3", NULL)) {
pdata->su2_feedback = AS3711_SU2_CURR3;
count++;
}
if (of_find_property(bl, "su2-feedback-curr-auto", NULL)) {
pdata->su2_feedback = AS3711_SU2_CURR_AUTO;
count++;
}
if (count != 1)
return -EINVAL;
count = 0;
if (of_find_property(bl, "su2-fbprot-lx-sd4", NULL)) {
pdata->su2_fbprot = AS3711_SU2_LX_SD4;
count++;
}
if (of_find_property(bl, "su2-fbprot-gpio2", NULL)) {
pdata->su2_fbprot = AS3711_SU2_GPIO2;
count++;
}
if (of_find_property(bl, "su2-fbprot-gpio3", NULL)) {
pdata->su2_fbprot = AS3711_SU2_GPIO3;
count++;
}
if (of_find_property(bl, "su2-fbprot-gpio4", NULL)) {
pdata->su2_fbprot = AS3711_SU2_GPIO4;
count++;
}
if (count != 1)
return -EINVAL;
count = 0;
if (of_find_property(bl, "su2-auto-curr1", NULL)) {
pdata->su2_auto_curr1 = true;
count++;
}
if (of_find_property(bl, "su2-auto-curr2", NULL)) {
pdata->su2_auto_curr2 = true;
count++;
}
if (of_find_property(bl, "su2-auto-curr3", NULL)) {
pdata->su2_auto_curr3 = true;
count++;
}
/*
* At least one su2-auto-curr* must be specified iff
* AS3711_SU2_CURR_AUTO is used
*/
if (!count ^ (pdata->su2_feedback != AS3711_SU2_CURR_AUTO))
return -EINVAL;
}
return 0;
}
static int as3711_backlight_probe(struct platform_device *pdev)
{
struct as3711_bl_pdata *pdata = dev_get_platdata(&pdev->dev);
struct as3711 *as3711 = dev_get_drvdata(pdev->dev.parent);
struct as3711_bl_supply *supply;
struct as3711_bl_data *su;
unsigned int max_brightness;
int ret;
if (!pdata) {
dev_err(&pdev->dev, "No platform data, exiting...\n");
return -ENODEV;
}
if (pdev->dev.parent->of_node) {
ret = as3711_backlight_parse_dt(&pdev->dev);
if (ret < 0) {
dev_err(&pdev->dev, "DT parsing failed: %d\n", ret);
return ret;
}
}
if (!pdata->su1_fb && !pdata->su2_fb) {
dev_err(&pdev->dev, "No framebuffer specified\n");
return -EINVAL;
}
/*
* Due to possible hardware damage I chose to block all modes,
* unsupported on my hardware. Anyone, wishing to use any of those modes
* will have to first review the code, then activate and test it.
*/
if (pdata->su1_fb ||
pdata->su2_fbprot != AS3711_SU2_GPIO4 ||
pdata->su2_feedback != AS3711_SU2_CURR_AUTO) {
dev_warn(&pdev->dev,
"Attention! An untested mode has been chosen!\n"
"Please, review the code, enable, test, and report success:-)\n");
return -EINVAL;
}
supply = devm_kzalloc(&pdev->dev, sizeof(*supply), GFP_KERNEL);
if (!supply)
return -ENOMEM;
supply->as3711 = as3711;
supply->pdata = pdata;
if (pdata->su1_fb) {
su = &supply->su1;
su->fb_name = pdata->su1_fb;
su->type = AS3711_BL_SU1;
max_brightness = min(pdata->su1_max_uA, 31);
ret = as3711_bl_register(pdev, max_brightness, su);
if (ret < 0)
return ret;
}
if (pdata->su2_fb) {
su = &supply->su2;
su->fb_name = pdata->su2_fb;
su->type = AS3711_BL_SU2;
switch (pdata->su2_fbprot) {
case AS3711_SU2_GPIO2:
case AS3711_SU2_GPIO3:
case AS3711_SU2_GPIO4:
case AS3711_SU2_LX_SD4:
break;
default:
return -EINVAL;
}
switch (pdata->su2_feedback) {
case AS3711_SU2_VOLTAGE:
max_brightness = min(pdata->su2_max_uA, 31);
break;
case AS3711_SU2_CURR1:
case AS3711_SU2_CURR2:
case AS3711_SU2_CURR3:
case AS3711_SU2_CURR_AUTO:
max_brightness = min(pdata->su2_max_uA / 150, 255);
break;
default:
return -EINVAL;
}
ret = as3711_bl_init_su2(supply);
if (ret < 0)
return ret;
ret = as3711_bl_register(pdev, max_brightness, su);
if (ret < 0)
return ret;
}
platform_set_drvdata(pdev, supply);
return 0;
}
static struct platform_driver as3711_backlight_driver = {
.driver = {
.name = "as3711-backlight",
},
.probe = as3711_backlight_probe,
};
module_platform_driver(as3711_backlight_driver);
MODULE_DESCRIPTION("Backlight Driver for AS3711 PMICs");
MODULE_AUTHOR("Guennadi Liakhovetski <g.liakhovetski@gmx.de");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:as3711-backlight");

View file

@ -0,0 +1,601 @@
/*
* Backlight Lowlevel Control Abstraction
*
* Copyright (C) 2003,2004 Hewlett-Packard Company
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/backlight.h>
#include <linux/notifier.h>
#include <linux/ctype.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/slab.h>
#ifdef CONFIG_PMAC_BACKLIGHT
#include <asm/backlight.h>
#endif
static struct list_head backlight_dev_list;
static struct mutex backlight_dev_list_mutex;
static struct blocking_notifier_head backlight_notifier;
static const char *const backlight_types[] = {
[BACKLIGHT_RAW] = "raw",
[BACKLIGHT_PLATFORM] = "platform",
[BACKLIGHT_FIRMWARE] = "firmware",
};
#if defined(CONFIG_FB) || (defined(CONFIG_FB_MODULE) && \
defined(CONFIG_BACKLIGHT_CLASS_DEVICE_MODULE))
/* This callback gets called when something important happens inside a
* framebuffer driver. We're looking if that important event is blanking,
* and if it is and necessary, we're switching backlight power as well ...
*/
static int fb_notifier_callback(struct notifier_block *self,
unsigned long event, void *data)
{
struct backlight_device *bd;
struct fb_event *evdata = data;
int node = evdata->info->node;
int fb_blank = 0;
/* If we aren't interested in this event, skip it immediately ... */
if (event != FB_EVENT_BLANK && event != FB_EVENT_CONBLANK)
return 0;
bd = container_of(self, struct backlight_device, fb_notif);
mutex_lock(&bd->ops_lock);
if (bd->ops)
if (!bd->ops->check_fb ||
bd->ops->check_fb(bd, evdata->info)) {
fb_blank = *(int *)evdata->data;
if (fb_blank == FB_BLANK_UNBLANK &&
!bd->fb_bl_on[node]) {
bd->fb_bl_on[node] = true;
if (!bd->use_count++) {
bd->props.state &= ~BL_CORE_FBBLANK;
bd->props.fb_blank = FB_BLANK_UNBLANK;
backlight_update_status(bd);
}
} else if (fb_blank != FB_BLANK_UNBLANK &&
bd->fb_bl_on[node]) {
bd->fb_bl_on[node] = false;
if (!(--bd->use_count)) {
bd->props.state |= BL_CORE_FBBLANK;
bd->props.fb_blank = fb_blank;
backlight_update_status(bd);
}
}
}
mutex_unlock(&bd->ops_lock);
return 0;
}
static int backlight_register_fb(struct backlight_device *bd)
{
memset(&bd->fb_notif, 0, sizeof(bd->fb_notif));
bd->fb_notif.notifier_call = fb_notifier_callback;
return fb_register_client(&bd->fb_notif);
}
static void backlight_unregister_fb(struct backlight_device *bd)
{
fb_unregister_client(&bd->fb_notif);
}
#else
static inline int backlight_register_fb(struct backlight_device *bd)
{
return 0;
}
static inline void backlight_unregister_fb(struct backlight_device *bd)
{
}
#endif /* CONFIG_FB */
static void backlight_generate_event(struct backlight_device *bd,
enum backlight_update_reason reason)
{
char *envp[2];
switch (reason) {
case BACKLIGHT_UPDATE_SYSFS:
envp[0] = "SOURCE=sysfs";
break;
case BACKLIGHT_UPDATE_HOTKEY:
envp[0] = "SOURCE=hotkey";
break;
default:
envp[0] = "SOURCE=unknown";
break;
}
envp[1] = NULL;
kobject_uevent_env(&bd->dev.kobj, KOBJ_CHANGE, envp);
sysfs_notify(&bd->dev.kobj, NULL, "actual_brightness");
}
static ssize_t bl_power_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%d\n", bd->props.power);
}
static ssize_t bl_power_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int rc;
struct backlight_device *bd = to_backlight_device(dev);
unsigned long power;
rc = kstrtoul(buf, 0, &power);
if (rc)
return rc;
rc = -ENXIO;
mutex_lock(&bd->ops_lock);
if (bd->ops) {
pr_debug("set power to %lu\n", power);
if (bd->props.power != power) {
bd->props.power = power;
backlight_update_status(bd);
}
rc = count;
}
mutex_unlock(&bd->ops_lock);
return rc;
}
static DEVICE_ATTR_RW(bl_power);
static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%d\n", bd->props.brightness);
}
static ssize_t brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int rc;
struct backlight_device *bd = to_backlight_device(dev);
unsigned long brightness;
rc = kstrtoul(buf, 0, &brightness);
if (rc)
return rc;
rc = -ENXIO;
mutex_lock(&bd->ops_lock);
if (bd->ops) {
if (brightness > bd->props.max_brightness)
rc = -EINVAL;
else {
pr_debug("set brightness to %lu\n", brightness);
bd->props.brightness = brightness;
backlight_update_status(bd);
rc = count;
}
}
mutex_unlock(&bd->ops_lock);
backlight_generate_event(bd, BACKLIGHT_UPDATE_SYSFS);
return rc;
}
static DEVICE_ATTR_RW(brightness);
static ssize_t type_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%s\n", backlight_types[bd->props.type]);
}
static DEVICE_ATTR_RO(type);
static ssize_t max_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct backlight_device *bd = to_backlight_device(dev);
return sprintf(buf, "%d\n", bd->props.max_brightness);
}
static DEVICE_ATTR_RO(max_brightness);
static ssize_t actual_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int rc = -ENXIO;
struct backlight_device *bd = to_backlight_device(dev);
mutex_lock(&bd->ops_lock);
if (bd->ops && bd->ops->get_brightness)
rc = sprintf(buf, "%d\n", bd->ops->get_brightness(bd));
else
rc = sprintf(buf, "%d\n", bd->props.brightness);
mutex_unlock(&bd->ops_lock);
return rc;
}
static DEVICE_ATTR_RO(actual_brightness);
static struct class *backlight_class;
#ifdef CONFIG_PM_SLEEP
static int backlight_suspend(struct device *dev)
{
struct backlight_device *bd = to_backlight_device(dev);
mutex_lock(&bd->ops_lock);
if (bd->ops && bd->ops->options & BL_CORE_SUSPENDRESUME) {
bd->props.state |= BL_CORE_SUSPENDED;
backlight_update_status(bd);
}
mutex_unlock(&bd->ops_lock);
return 0;
}
static int backlight_resume(struct device *dev)
{
struct backlight_device *bd = to_backlight_device(dev);
mutex_lock(&bd->ops_lock);
if (bd->ops && bd->ops->options & BL_CORE_SUSPENDRESUME) {
bd->props.state &= ~BL_CORE_SUSPENDED;
backlight_update_status(bd);
}
mutex_unlock(&bd->ops_lock);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(backlight_class_dev_pm_ops, backlight_suspend,
backlight_resume);
static void bl_device_release(struct device *dev)
{
struct backlight_device *bd = to_backlight_device(dev);
kfree(bd);
}
static struct attribute *bl_device_attrs[] = {
&dev_attr_bl_power.attr,
&dev_attr_brightness.attr,
&dev_attr_actual_brightness.attr,
&dev_attr_max_brightness.attr,
&dev_attr_type.attr,
NULL,
};
ATTRIBUTE_GROUPS(bl_device);
/**
* backlight_force_update - tell the backlight subsystem that hardware state
* has changed
* @bd: the backlight device to update
*
* Updates the internal state of the backlight in response to a hardware event,
* and generate a uevent to notify userspace
*/
void backlight_force_update(struct backlight_device *bd,
enum backlight_update_reason reason)
{
mutex_lock(&bd->ops_lock);
if (bd->ops && bd->ops->get_brightness)
bd->props.brightness = bd->ops->get_brightness(bd);
mutex_unlock(&bd->ops_lock);
backlight_generate_event(bd, reason);
}
EXPORT_SYMBOL(backlight_force_update);
/**
* backlight_device_register - create and register a new object of
* backlight_device class.
* @name: the name of the new object(must be the same as the name of the
* respective framebuffer device).
* @parent: a pointer to the parent device
* @devdata: an optional pointer to be stored for private driver use. The
* methods may retrieve it by using bl_get_data(bd).
* @ops: the backlight operations structure.
*
* Creates and registers new backlight device. Returns either an
* ERR_PTR() or a pointer to the newly allocated device.
*/
struct backlight_device *backlight_device_register(const char *name,
struct device *parent, void *devdata, const struct backlight_ops *ops,
const struct backlight_properties *props)
{
struct backlight_device *new_bd;
int rc;
pr_debug("backlight_device_register: name=%s\n", name);
new_bd = kzalloc(sizeof(struct backlight_device), GFP_KERNEL);
if (!new_bd)
return ERR_PTR(-ENOMEM);
mutex_init(&new_bd->update_lock);
mutex_init(&new_bd->ops_lock);
new_bd->dev.class = backlight_class;
new_bd->dev.parent = parent;
new_bd->dev.release = bl_device_release;
dev_set_name(&new_bd->dev, "%s", name);
dev_set_drvdata(&new_bd->dev, devdata);
/* Set default properties */
if (props) {
memcpy(&new_bd->props, props,
sizeof(struct backlight_properties));
if (props->type <= 0 || props->type >= BACKLIGHT_TYPE_MAX) {
WARN(1, "%s: invalid backlight type", name);
new_bd->props.type = BACKLIGHT_RAW;
}
} else {
new_bd->props.type = BACKLIGHT_RAW;
}
rc = device_register(&new_bd->dev);
if (rc) {
put_device(&new_bd->dev);
return ERR_PTR(rc);
}
rc = backlight_register_fb(new_bd);
if (rc) {
device_unregister(&new_bd->dev);
return ERR_PTR(rc);
}
new_bd->ops = ops;
#ifdef CONFIG_PMAC_BACKLIGHT
mutex_lock(&pmac_backlight_mutex);
if (!pmac_backlight)
pmac_backlight = new_bd;
mutex_unlock(&pmac_backlight_mutex);
#endif
mutex_lock(&backlight_dev_list_mutex);
list_add(&new_bd->entry, &backlight_dev_list);
mutex_unlock(&backlight_dev_list_mutex);
blocking_notifier_call_chain(&backlight_notifier,
BACKLIGHT_REGISTERED, new_bd);
return new_bd;
}
EXPORT_SYMBOL(backlight_device_register);
bool backlight_device_registered(enum backlight_type type)
{
bool found = false;
struct backlight_device *bd;
mutex_lock(&backlight_dev_list_mutex);
list_for_each_entry(bd, &backlight_dev_list, entry) {
if (bd->props.type == type) {
found = true;
break;
}
}
mutex_unlock(&backlight_dev_list_mutex);
return found;
}
EXPORT_SYMBOL(backlight_device_registered);
/**
* backlight_device_unregister - unregisters a backlight device object.
* @bd: the backlight device object to be unregistered and freed.
*
* Unregisters a previously registered via backlight_device_register object.
*/
void backlight_device_unregister(struct backlight_device *bd)
{
if (!bd)
return;
mutex_lock(&backlight_dev_list_mutex);
list_del(&bd->entry);
mutex_unlock(&backlight_dev_list_mutex);
#ifdef CONFIG_PMAC_BACKLIGHT
mutex_lock(&pmac_backlight_mutex);
if (pmac_backlight == bd)
pmac_backlight = NULL;
mutex_unlock(&pmac_backlight_mutex);
#endif
blocking_notifier_call_chain(&backlight_notifier,
BACKLIGHT_UNREGISTERED, bd);
mutex_lock(&bd->ops_lock);
bd->ops = NULL;
mutex_unlock(&bd->ops_lock);
backlight_unregister_fb(bd);
device_unregister(&bd->dev);
}
EXPORT_SYMBOL(backlight_device_unregister);
static void devm_backlight_device_release(struct device *dev, void *res)
{
struct backlight_device *backlight = *(struct backlight_device **)res;
backlight_device_unregister(backlight);
}
static int devm_backlight_device_match(struct device *dev, void *res,
void *data)
{
struct backlight_device **r = res;
return *r == data;
}
/**
* backlight_register_notifier - get notified of backlight (un)registration
* @nb: notifier block with the notifier to call on backlight (un)registration
*
* @return 0 on success, otherwise a negative error code
*
* Register a notifier to get notified when backlight devices get registered
* or unregistered.
*/
int backlight_register_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&backlight_notifier, nb);
}
EXPORT_SYMBOL(backlight_register_notifier);
/**
* backlight_unregister_notifier - unregister a backlight notifier
* @nb: notifier block to unregister
*
* @return 0 on success, otherwise a negative error code
*
* Register a notifier to get notified when backlight devices get registered
* or unregistered.
*/
int backlight_unregister_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&backlight_notifier, nb);
}
EXPORT_SYMBOL(backlight_unregister_notifier);
/**
* devm_backlight_device_register - resource managed backlight_device_register()
* @dev: the device to register
* @name: the name of the device
* @parent: a pointer to the parent device
* @devdata: an optional pointer to be stored for private driver use
* @ops: the backlight operations structure
* @props: the backlight properties
*
* @return a struct backlight on success, or an ERR_PTR on error
*
* Managed backlight_device_register(). The backlight_device returned
* from this function are automatically freed on driver detach.
* See backlight_device_register() for more information.
*/
struct backlight_device *devm_backlight_device_register(struct device *dev,
const char *name, struct device *parent, void *devdata,
const struct backlight_ops *ops,
const struct backlight_properties *props)
{
struct backlight_device **ptr, *backlight;
ptr = devres_alloc(devm_backlight_device_release, sizeof(*ptr),
GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
backlight = backlight_device_register(name, parent, devdata, ops,
props);
if (!IS_ERR(backlight)) {
*ptr = backlight;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return backlight;
}
EXPORT_SYMBOL(devm_backlight_device_register);
/**
* devm_backlight_device_unregister - resource managed backlight_device_unregister()
* @dev: the device to unregister
* @bd: the backlight device to unregister
*
* Deallocated a backlight allocated with devm_backlight_device_register().
* Normally this function will not need to be called and the resource management
* code will ensure that the resource is freed.
*/
void devm_backlight_device_unregister(struct device *dev,
struct backlight_device *bd)
{
int rc;
rc = devres_release(dev, devm_backlight_device_release,
devm_backlight_device_match, bd);
WARN_ON(rc);
}
EXPORT_SYMBOL(devm_backlight_device_unregister);
#ifdef CONFIG_OF
static int of_parent_match(struct device *dev, const void *data)
{
return dev->parent && dev->parent->of_node == data;
}
/**
* of_find_backlight_by_node() - find backlight device by device-tree node
* @node: device-tree node of the backlight device
*
* Returns a pointer to the backlight device corresponding to the given DT
* node or NULL if no such backlight device exists or if the device hasn't
* been probed yet.
*
* This function obtains a reference on the backlight device and it is the
* caller's responsibility to drop the reference by calling put_device() on
* the backlight device's .dev field.
*/
struct backlight_device *of_find_backlight_by_node(struct device_node *node)
{
struct device *dev;
dev = class_find_device(backlight_class, NULL, node, of_parent_match);
return dev ? to_backlight_device(dev) : NULL;
}
EXPORT_SYMBOL(of_find_backlight_by_node);
#endif
static void __exit backlight_class_exit(void)
{
class_destroy(backlight_class);
}
static int __init backlight_class_init(void)
{
backlight_class = class_create(THIS_MODULE, "backlight");
if (IS_ERR(backlight_class)) {
pr_warn("Unable to create backlight class; errno = %ld\n",
PTR_ERR(backlight_class));
return PTR_ERR(backlight_class);
}
backlight_class->dev_groups = bl_device_groups;
backlight_class->pm = &backlight_class_dev_pm_ops;
INIT_LIST_HEAD(&backlight_dev_list);
mutex_init(&backlight_dev_list_mutex);
BLOCKING_INIT_NOTIFIER_HEAD(&backlight_notifier);
return 0;
}
/*
* if this is compiled into the kernel, we need to ensure that the
* class is registered before users of the class try to register lcd's
*/
postcore_initcall(backlight_class_init);
module_exit(backlight_class_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jamey Hicks <jamey.hicks@hp.com>, Andrew Zabolotny <zap@homelink.ru>");
MODULE_DESCRIPTION("Backlight Lowlevel Control Abstraction");

View file

@ -0,0 +1,207 @@
/*
* ROHM Semiconductor BD6107 LED Driver
*
* Copyright (C) 2013 Ideas on board SPRL
*
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.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/backlight.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/platform_data/bd6107.h>
#include <linux/slab.h>
#define BD6107_PSCNT1 0x00
#define BD6107_PSCNT1_PSCNTREG2 (1 << 2)
#define BD6107_PSCNT1_PSCNTREG1 (1 << 0)
#define BD6107_REGVSET 0x02
#define BD6107_REGVSET_REG1VSET_2_85V (1 << 2)
#define BD6107_REGVSET_REG1VSET_2_80V (0 << 2)
#define BD6107_LEDCNT1 0x03
#define BD6107_LEDCNT1_LEDONOFF2 (1 << 1)
#define BD6107_LEDCNT1_LEDONOFF1 (1 << 0)
#define BD6107_PORTSEL 0x04
#define BD6107_PORTSEL_LEDM(n) (1 << (n))
#define BD6107_RGB1CNT1 0x05
#define BD6107_RGB1CNT2 0x06
#define BD6107_RGB1CNT3 0x07
#define BD6107_RGB1CNT4 0x08
#define BD6107_RGB1CNT5 0x09
#define BD6107_RGB1FLM 0x0a
#define BD6107_RGB2CNT1 0x0b
#define BD6107_RGB2CNT2 0x0c
#define BD6107_RGB2CNT3 0x0d
#define BD6107_RGB2CNT4 0x0e
#define BD6107_RGB2CNT5 0x0f
#define BD6107_RGB2FLM 0x10
#define BD6107_PSCONT3 0x11
#define BD6107_SMMONCNT 0x12
#define BD6107_DCDCCNT 0x13
#define BD6107_IOSEL 0x14
#define BD6107_OUT1 0x15
#define BD6107_OUT2 0x16
#define BD6107_MASK1 0x17
#define BD6107_MASK2 0x18
#define BD6107_FACTOR1 0x19
#define BD6107_FACTOR2 0x1a
#define BD6107_CLRFACT1 0x1b
#define BD6107_CLRFACT2 0x1c
#define BD6107_STATE1 0x1d
#define BD6107_LSIVER 0x1e
#define BD6107_GRPSEL 0x1f
#define BD6107_LEDCNT2 0x20
#define BD6107_LEDCNT3 0x21
#define BD6107_MCURRENT 0x22
#define BD6107_MAINCNT1 0x23
#define BD6107_MAINCNT2 0x24
#define BD6107_SLOPECNT 0x25
#define BD6107_MSLOPE 0x26
#define BD6107_RGBSLOPE 0x27
#define BD6107_TEST 0x29
#define BD6107_SFTRST 0x2a
#define BD6107_SFTRSTGD 0x2b
struct bd6107 {
struct i2c_client *client;
struct backlight_device *backlight;
struct bd6107_platform_data *pdata;
};
static int bd6107_write(struct bd6107 *bd, u8 reg, u8 data)
{
return i2c_smbus_write_byte_data(bd->client, reg, data);
}
static int bd6107_backlight_update_status(struct backlight_device *backlight)
{
struct bd6107 *bd = bl_get_data(backlight);
int brightness = backlight->props.brightness;
if (backlight->props.power != FB_BLANK_UNBLANK ||
backlight->props.fb_blank != FB_BLANK_UNBLANK ||
backlight->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
brightness = 0;
if (brightness) {
bd6107_write(bd, BD6107_PORTSEL, BD6107_PORTSEL_LEDM(2) |
BD6107_PORTSEL_LEDM(1) | BD6107_PORTSEL_LEDM(0));
bd6107_write(bd, BD6107_MAINCNT1, brightness);
bd6107_write(bd, BD6107_LEDCNT1, BD6107_LEDCNT1_LEDONOFF1);
} else {
gpio_set_value(bd->pdata->reset, 0);
msleep(24);
gpio_set_value(bd->pdata->reset, 1);
}
return 0;
}
static int bd6107_backlight_check_fb(struct backlight_device *backlight,
struct fb_info *info)
{
struct bd6107 *bd = bl_get_data(backlight);
return bd->pdata->fbdev == NULL || bd->pdata->fbdev == info->dev;
}
static const struct backlight_ops bd6107_backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = bd6107_backlight_update_status,
.check_fb = bd6107_backlight_check_fb,
};
static int bd6107_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct bd6107_platform_data *pdata = dev_get_platdata(&client->dev);
struct backlight_device *backlight;
struct backlight_properties props;
struct bd6107 *bd;
int ret;
if (pdata == NULL || !pdata->reset) {
dev_err(&client->dev, "No reset GPIO in platform data\n");
return -EINVAL;
}
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_warn(&client->dev,
"I2C adapter doesn't support I2C_FUNC_SMBUS_BYTE\n");
return -EIO;
}
bd = devm_kzalloc(&client->dev, sizeof(*bd), GFP_KERNEL);
if (!bd)
return -ENOMEM;
bd->client = client;
bd->pdata = pdata;
ret = devm_gpio_request_one(&client->dev, pdata->reset,
GPIOF_DIR_OUT | GPIOF_INIT_LOW, "reset");
if (ret < 0) {
dev_err(&client->dev, "unable to request reset GPIO\n");
return ret;
}
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW;
props.max_brightness = 128;
props.brightness = clamp_t(unsigned int, pdata->def_value, 0,
props.max_brightness);
backlight = devm_backlight_device_register(&client->dev,
dev_name(&client->dev),
&bd->client->dev, bd,
&bd6107_backlight_ops, &props);
if (IS_ERR(backlight)) {
dev_err(&client->dev, "failed to register backlight\n");
return PTR_ERR(backlight);
}
backlight_update_status(backlight);
i2c_set_clientdata(client, backlight);
return 0;
}
static int bd6107_remove(struct i2c_client *client)
{
struct backlight_device *backlight = i2c_get_clientdata(client);
backlight->props.brightness = 0;
backlight_update_status(backlight);
return 0;
}
static const struct i2c_device_id bd6107_ids[] = {
{ "bd6107", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, bd6107_ids);
static struct i2c_driver bd6107_driver = {
.driver = {
.name = "bd6107",
},
.probe = bd6107_probe,
.remove = bd6107_remove,
.id_table = bd6107_ids,
};
module_i2c_driver(bd6107_driver);
MODULE_DESCRIPTION("Rohm BD6107 Backlight Driver");
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,613 @@
/*
* 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 <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))
gpio_set_value_cansleep(lcd->gpio_backlight_cont, cont);
if (gpio_is_valid(lcd->gpio_backlight_on))
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 = dev_get_platdata(&spi->dev);
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)
return -ENOMEM;
lcd->spi_dev = spi;
lcd->lcd_dev = devm_lcd_device_register(&spi->dev, "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 = devm_backlight_device_register(&spi->dev, "corgi_bl",
&spi->dev, lcd, &corgi_bl_ops,
&props);
if (IS_ERR(lcd->bl_dev))
return PTR_ERR(lcd->bl_dev);
lcd->bl_dev->props.brightness = pdata->default_intensity;
lcd->bl_dev->props.power = FB_BLANK_UNBLANK;
ret = setup_gpio_backlight(lcd, pdata);
if (ret)
return ret;
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;
}
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);
corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN);
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");

View file

@ -0,0 +1,288 @@
/*
* Copyright (c) Intel Corp. 2007.
* All Rights Reserved.
*
* Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to
* develop this driver.
*
* This file is part of the Carillo Ranch video subsystem driver.
* The Carillo Ranch video subsystem driver is free software;
* you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The Carillo Ranch video subsystem driver is distributed
* in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this driver; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Authors:
* Thomas Hellstrom <thomas-at-tungstengraphics-dot-com>
* Alan Hourihane <alanh-at-tungstengraphics-dot-com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/mutex.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/lcd.h>
#include <linux/pci.h>
#include <linux/slab.h>
/* The LVDS- and panel power controls sits on the
* GPIO port of the ISA bridge.
*/
#define CRVML_DEVICE_LPC 0x27B8
#define CRVML_REG_GPIOBAR 0x48
#define CRVML_REG_GPIOEN 0x4C
#define CRVML_GPIOEN_BIT (1 << 4)
#define CRVML_PANEL_PORT 0x38
#define CRVML_LVDS_ON 0x00000001
#define CRVML_PANEL_ON 0x00000002
#define CRVML_BACKLIGHT_OFF 0x00000004
/* The PLL Clock register sits on Host bridge */
#define CRVML_DEVICE_MCH 0x5001
#define CRVML_REG_MCHBAR 0x44
#define CRVML_REG_MCHEN 0x54
#define CRVML_MCHEN_BIT (1 << 28)
#define CRVML_MCHMAP_SIZE 4096
#define CRVML_REG_CLOCK 0xc3c
#define CRVML_CLOCK_SHIFT 8
#define CRVML_CLOCK_MASK 0x00000f00
static struct pci_dev *lpc_dev;
static u32 gpio_bar;
struct cr_panel {
struct backlight_device *cr_backlight_device;
struct lcd_device *cr_lcd_device;
};
static int cr_backlight_set_intensity(struct backlight_device *bd)
{
int intensity = bd->props.brightness;
u32 addr = gpio_bar + CRVML_PANEL_PORT;
u32 cur = inl(addr);
if (bd->props.power == FB_BLANK_UNBLANK)
intensity = FB_BLANK_UNBLANK;
if (bd->props.fb_blank == FB_BLANK_UNBLANK)
intensity = FB_BLANK_UNBLANK;
if (bd->props.power == FB_BLANK_POWERDOWN)
intensity = FB_BLANK_POWERDOWN;
if (bd->props.fb_blank == FB_BLANK_POWERDOWN)
intensity = FB_BLANK_POWERDOWN;
if (intensity == FB_BLANK_UNBLANK) { /* FULL ON */
cur &= ~CRVML_BACKLIGHT_OFF;
outl(cur, addr);
} else if (intensity == FB_BLANK_POWERDOWN) { /* OFF */
cur |= CRVML_BACKLIGHT_OFF;
outl(cur, addr);
} /* anything else, don't bother */
return 0;
}
static int cr_backlight_get_intensity(struct backlight_device *bd)
{
u32 addr = gpio_bar + CRVML_PANEL_PORT;
u32 cur = inl(addr);
u8 intensity;
if (cur & CRVML_BACKLIGHT_OFF)
intensity = FB_BLANK_POWERDOWN;
else
intensity = FB_BLANK_UNBLANK;
return intensity;
}
static const struct backlight_ops cr_backlight_ops = {
.get_brightness = cr_backlight_get_intensity,
.update_status = cr_backlight_set_intensity,
};
static void cr_panel_on(void)
{
u32 addr = gpio_bar + CRVML_PANEL_PORT;
u32 cur = inl(addr);
if (!(cur & CRVML_PANEL_ON)) {
/* Make sure LVDS controller is down. */
if (cur & 0x00000001) {
cur &= ~CRVML_LVDS_ON;
outl(cur, addr);
}
/* Power up Panel */
schedule_timeout(HZ / 10);
cur |= CRVML_PANEL_ON;
outl(cur, addr);
}
/* Power up LVDS controller */
if (!(cur & CRVML_LVDS_ON)) {
schedule_timeout(HZ / 10);
outl(cur | CRVML_LVDS_ON, addr);
}
}
static void cr_panel_off(void)
{
u32 addr = gpio_bar + CRVML_PANEL_PORT;
u32 cur = inl(addr);
/* Power down LVDS controller first to avoid high currents */
if (cur & CRVML_LVDS_ON) {
cur &= ~CRVML_LVDS_ON;
outl(cur, addr);
}
if (cur & CRVML_PANEL_ON) {
schedule_timeout(HZ / 10);
outl(cur & ~CRVML_PANEL_ON, addr);
}
}
static int cr_lcd_set_power(struct lcd_device *ld, int power)
{
if (power == FB_BLANK_UNBLANK)
cr_panel_on();
if (power == FB_BLANK_POWERDOWN)
cr_panel_off();
return 0;
}
static struct lcd_ops cr_lcd_ops = {
.set_power = cr_lcd_set_power,
};
static int cr_backlight_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct backlight_device *bdp;
struct lcd_device *ldp;
struct cr_panel *crp;
u8 dev_en;
lpc_dev = pci_get_device(PCI_VENDOR_ID_INTEL,
CRVML_DEVICE_LPC, NULL);
if (!lpc_dev) {
pr_err("INTEL CARILLO RANCH LPC not found.\n");
return -ENODEV;
}
pci_read_config_byte(lpc_dev, CRVML_REG_GPIOEN, &dev_en);
if (!(dev_en & CRVML_GPIOEN_BIT)) {
pr_err("Carillo Ranch GPIO device was not enabled.\n");
pci_dev_put(lpc_dev);
return -ENODEV;
}
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
bdp = devm_backlight_device_register(&pdev->dev, "cr-backlight",
&pdev->dev, NULL, &cr_backlight_ops,
&props);
if (IS_ERR(bdp)) {
pci_dev_put(lpc_dev);
return PTR_ERR(bdp);
}
ldp = devm_lcd_device_register(&pdev->dev, "cr-lcd", &pdev->dev, NULL,
&cr_lcd_ops);
if (IS_ERR(ldp)) {
pci_dev_put(lpc_dev);
return PTR_ERR(ldp);
}
pci_read_config_dword(lpc_dev, CRVML_REG_GPIOBAR,
&gpio_bar);
gpio_bar &= ~0x3F;
crp = devm_kzalloc(&pdev->dev, sizeof(*crp), GFP_KERNEL);
if (!crp) {
pci_dev_put(lpc_dev);
return -ENOMEM;
}
crp->cr_backlight_device = bdp;
crp->cr_lcd_device = ldp;
crp->cr_backlight_device->props.power = FB_BLANK_UNBLANK;
crp->cr_backlight_device->props.brightness = 0;
cr_backlight_set_intensity(crp->cr_backlight_device);
cr_lcd_set_power(crp->cr_lcd_device, FB_BLANK_UNBLANK);
platform_set_drvdata(pdev, crp);
return 0;
}
static int cr_backlight_remove(struct platform_device *pdev)
{
struct cr_panel *crp = platform_get_drvdata(pdev);
crp->cr_backlight_device->props.power = FB_BLANK_POWERDOWN;
crp->cr_backlight_device->props.brightness = 0;
crp->cr_backlight_device->props.max_brightness = 0;
cr_backlight_set_intensity(crp->cr_backlight_device);
cr_lcd_set_power(crp->cr_lcd_device, FB_BLANK_POWERDOWN);
pci_dev_put(lpc_dev);
return 0;
}
static struct platform_driver cr_backlight_driver = {
.probe = cr_backlight_probe,
.remove = cr_backlight_remove,
.driver = {
.name = "cr_backlight",
},
};
static struct platform_device *crp;
static int __init cr_backlight_init(void)
{
int ret = platform_driver_register(&cr_backlight_driver);
if (ret)
return ret;
crp = platform_device_register_simple("cr_backlight", -1, NULL, 0);
if (IS_ERR(crp)) {
platform_driver_unregister(&cr_backlight_driver);
return PTR_ERR(crp);
}
pr_info("Carillo Ranch Backlight Driver Initialized.\n");
return 0;
}
static void __exit cr_backlight_exit(void)
{
platform_device_unregister(crp);
platform_driver_unregister(&cr_backlight_driver);
}
module_init(cr_backlight_init);
module_exit(cr_backlight_exit);
MODULE_AUTHOR("Tungsten Graphics Inc.");
MODULE_DESCRIPTION("Carillo Ranch Backlight Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,175 @@
/*
* Backlight driver for Dialog Semiconductor DA9030/DA9034
*
* Copyright (C) 2008 Compulab, Ltd.
* Mike Rapoport <mike@compulab.co.il>
*
* Copyright (C) 2006-2008 Marvell International Ltd.
* 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/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/mfd/da903x.h>
#include <linux/slab.h>
#include <linux/module.h>
#define DA9030_WLED_CONTROL 0x25
#define DA9030_WLED_CP_EN (1 << 6)
#define DA9030_WLED_TRIM(x) ((x) & 0x7)
#define DA9034_WLED_CONTROL1 0x3C
#define DA9034_WLED_CONTROL2 0x3D
#define DA9034_WLED_ISET(x) ((x) & 0x1f)
#define DA9034_WLED_BOOST_EN (1 << 5)
#define DA9030_MAX_BRIGHTNESS 7
#define DA9034_MAX_BRIGHTNESS 0x7f
struct da903x_backlight_data {
struct device *da903x_dev;
int id;
int current_brightness;
};
static int da903x_backlight_set(struct backlight_device *bl, int brightness)
{
struct da903x_backlight_data *data = bl_get_data(bl);
struct device *dev = data->da903x_dev;
uint8_t val;
int ret = 0;
switch (data->id) {
case DA9034_ID_WLED:
ret = da903x_update(dev, DA9034_WLED_CONTROL1,
brightness, 0x7f);
if (ret)
return ret;
if (data->current_brightness && brightness == 0)
ret = da903x_clr_bits(dev,
DA9034_WLED_CONTROL2,
DA9034_WLED_BOOST_EN);
if (data->current_brightness == 0 && brightness)
ret = da903x_set_bits(dev,
DA9034_WLED_CONTROL2,
DA9034_WLED_BOOST_EN);
break;
case DA9030_ID_WLED:
val = DA9030_WLED_TRIM(brightness);
val |= brightness ? DA9030_WLED_CP_EN : 0;
ret = da903x_write(dev, DA9030_WLED_CONTROL, val);
break;
}
if (ret)
return ret;
data->current_brightness = brightness;
return 0;
}
static int da903x_backlight_update_status(struct backlight_device *bl)
{
int brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.state & BL_CORE_SUSPENDED)
brightness = 0;
return da903x_backlight_set(bl, brightness);
}
static int da903x_backlight_get_brightness(struct backlight_device *bl)
{
struct da903x_backlight_data *data = bl_get_data(bl);
return data->current_brightness;
}
static const struct backlight_ops da903x_backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = da903x_backlight_update_status,
.get_brightness = da903x_backlight_get_brightness,
};
static int da903x_backlight_probe(struct platform_device *pdev)
{
struct da9034_backlight_pdata *pdata = dev_get_platdata(&pdev->dev);
struct da903x_backlight_data *data;
struct backlight_device *bl;
struct backlight_properties props;
int max_brightness;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
switch (pdev->id) {
case DA9030_ID_WLED:
max_brightness = DA9030_MAX_BRIGHTNESS;
break;
case DA9034_ID_WLED:
max_brightness = DA9034_MAX_BRIGHTNESS;
break;
default:
dev_err(&pdev->dev, "invalid backlight device ID(%d)\n",
pdev->id);
return -EINVAL;
}
data->id = pdev->id;
data->da903x_dev = pdev->dev.parent;
data->current_brightness = 0;
/* adjust the WLED output current */
if (pdata)
da903x_write(data->da903x_dev, DA9034_WLED_CONTROL2,
DA9034_WLED_ISET(pdata->output_current));
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW;
props.max_brightness = max_brightness;
bl = devm_backlight_device_register(&pdev->dev, pdev->name,
data->da903x_dev, data,
&da903x_backlight_ops, &props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
bl->props.brightness = max_brightness;
platform_set_drvdata(pdev, bl);
backlight_update_status(bl);
return 0;
}
static struct platform_driver da903x_backlight_driver = {
.driver = {
.name = "da903x-backlight",
},
.probe = da903x_backlight_probe,
};
module_platform_driver(da903x_backlight_driver);
MODULE_DESCRIPTION("Backlight Driver for Dialog Semiconductor DA9030/DA9034");
MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:da903x-backlight");

View file

@ -0,0 +1,184 @@
/*
* Backlight Driver for Dialog DA9052 PMICs
*
* Copyright(c) 2012 Dialog Semiconductor Ltd.
*
* Author: David Dajun Chen <dchen@diasemi.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mfd/da9052/da9052.h>
#include <linux/mfd/da9052/reg.h>
#define DA9052_MAX_BRIGHTNESS 0xFF
enum {
DA9052_WLEDS_OFF,
DA9052_WLEDS_ON,
};
enum {
DA9052_TYPE_WLED1,
DA9052_TYPE_WLED2,
DA9052_TYPE_WLED3,
};
static const unsigned char wled_bank[] = {
DA9052_LED1_CONF_REG,
DA9052_LED2_CONF_REG,
DA9052_LED3_CONF_REG,
};
struct da9052_bl {
struct da9052 *da9052;
uint brightness;
uint state;
uint led_reg;
};
static int da9052_adjust_wled_brightness(struct da9052_bl *wleds)
{
unsigned char boost_en;
unsigned char i_sink;
int ret;
boost_en = 0x3F;
i_sink = 0xFF;
if (wleds->state == DA9052_WLEDS_OFF) {
boost_en = 0x00;
i_sink = 0x00;
}
ret = da9052_reg_write(wleds->da9052, DA9052_BOOST_REG, boost_en);
if (ret < 0)
return ret;
ret = da9052_reg_write(wleds->da9052, DA9052_LED_CONT_REG, i_sink);
if (ret < 0)
return ret;
ret = da9052_reg_write(wleds->da9052, wled_bank[wleds->led_reg], 0x0);
if (ret < 0)
return ret;
usleep_range(10000, 11000);
if (wleds->brightness) {
ret = da9052_reg_write(wleds->da9052, wled_bank[wleds->led_reg],
wleds->brightness);
if (ret < 0)
return ret;
}
return 0;
}
static int da9052_backlight_update_status(struct backlight_device *bl)
{
int brightness = bl->props.brightness;
struct da9052_bl *wleds = bl_get_data(bl);
wleds->brightness = brightness;
wleds->state = DA9052_WLEDS_ON;
return da9052_adjust_wled_brightness(wleds);
}
static int da9052_backlight_get_brightness(struct backlight_device *bl)
{
struct da9052_bl *wleds = bl_get_data(bl);
return wleds->brightness;
}
static const struct backlight_ops da9052_backlight_ops = {
.update_status = da9052_backlight_update_status,
.get_brightness = da9052_backlight_get_brightness,
};
static int da9052_backlight_probe(struct platform_device *pdev)
{
struct backlight_device *bl;
struct backlight_properties props;
struct da9052_bl *wleds;
wleds = devm_kzalloc(&pdev->dev, sizeof(struct da9052_bl), GFP_KERNEL);
if (!wleds)
return -ENOMEM;
wleds->da9052 = dev_get_drvdata(pdev->dev.parent);
wleds->brightness = 0;
wleds->led_reg = platform_get_device_id(pdev)->driver_data;
wleds->state = DA9052_WLEDS_OFF;
props.type = BACKLIGHT_RAW;
props.max_brightness = DA9052_MAX_BRIGHTNESS;
bl = devm_backlight_device_register(&pdev->dev, pdev->name,
wleds->da9052->dev, wleds,
&da9052_backlight_ops, &props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "Failed to register backlight\n");
return PTR_ERR(bl);
}
bl->props.max_brightness = DA9052_MAX_BRIGHTNESS;
bl->props.brightness = 0;
platform_set_drvdata(pdev, bl);
return da9052_adjust_wled_brightness(wleds);
}
static int da9052_backlight_remove(struct platform_device *pdev)
{
struct backlight_device *bl = platform_get_drvdata(pdev);
struct da9052_bl *wleds = bl_get_data(bl);
wleds->brightness = 0;
wleds->state = DA9052_WLEDS_OFF;
da9052_adjust_wled_brightness(wleds);
return 0;
}
static struct platform_device_id da9052_wled_ids[] = {
{
.name = "da9052-wled1",
.driver_data = DA9052_TYPE_WLED1,
},
{
.name = "da9052-wled2",
.driver_data = DA9052_TYPE_WLED2,
},
{
.name = "da9052-wled3",
.driver_data = DA9052_TYPE_WLED3,
},
};
static struct platform_driver da9052_wled_driver = {
.probe = da9052_backlight_probe,
.remove = da9052_backlight_remove,
.id_table = da9052_wled_ids,
.driver = {
.name = "da9052-wled",
},
};
module_platform_driver(da9052_wled_driver);
MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
MODULE_DESCRIPTION("Backlight driver for DA9052 PMIC");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:da9052-backlight");

View file

@ -0,0 +1,141 @@
/*
* Driver for the Cirrus EP93xx lcd backlight
*
* Copyright (c) 2010 H Hartley Sweeten <hsweeten@visionengravers.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.
*
* This driver controls the pulse width modulated brightness control output,
* BRIGHT, on the Cirrus EP9307, EP9312, and EP9315 processors.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#define EP93XX_MAX_COUNT 255
#define EP93XX_MAX_BRIGHT 255
#define EP93XX_DEF_BRIGHT 128
struct ep93xxbl {
void __iomem *mmio;
int brightness;
};
static int ep93xxbl_set(struct backlight_device *bl, int brightness)
{
struct ep93xxbl *ep93xxbl = bl_get_data(bl);
writel((brightness << 8) | EP93XX_MAX_COUNT, ep93xxbl->mmio);
ep93xxbl->brightness = brightness;
return 0;
}
static int ep93xxbl_update_status(struct backlight_device *bl)
{
int brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK ||
bl->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
return ep93xxbl_set(bl, brightness);
}
static int ep93xxbl_get_brightness(struct backlight_device *bl)
{
struct ep93xxbl *ep93xxbl = bl_get_data(bl);
return ep93xxbl->brightness;
}
static const struct backlight_ops ep93xxbl_ops = {
.update_status = ep93xxbl_update_status,
.get_brightness = ep93xxbl_get_brightness,
};
static int ep93xxbl_probe(struct platform_device *dev)
{
struct ep93xxbl *ep93xxbl;
struct backlight_device *bl;
struct backlight_properties props;
struct resource *res;
ep93xxbl = devm_kzalloc(&dev->dev, sizeof(*ep93xxbl), GFP_KERNEL);
if (!ep93xxbl)
return -ENOMEM;
res = platform_get_resource(dev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
/*
* FIXME - We don't do a request_mem_region here because we are
* sharing the register space with the framebuffer driver (see
* drivers/video/ep93xx-fb.c) and doing so will cause the second
* loaded driver to return -EBUSY.
*
* NOTE: No locking is required; the framebuffer does not touch
* this register.
*/
ep93xxbl->mmio = devm_ioremap(&dev->dev, res->start,
resource_size(res));
if (!ep93xxbl->mmio)
return -ENXIO;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = EP93XX_MAX_BRIGHT;
bl = devm_backlight_device_register(&dev->dev, dev->name, &dev->dev,
ep93xxbl, &ep93xxbl_ops, &props);
if (IS_ERR(bl))
return PTR_ERR(bl);
bl->props.brightness = EP93XX_DEF_BRIGHT;
platform_set_drvdata(dev, bl);
ep93xxbl_update_status(bl);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int ep93xxbl_suspend(struct device *dev)
{
struct backlight_device *bl = dev_get_drvdata(dev);
return ep93xxbl_set(bl, 0);
}
static int ep93xxbl_resume(struct device *dev)
{
struct backlight_device *bl = dev_get_drvdata(dev);
backlight_update_status(bl);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(ep93xxbl_pm_ops, ep93xxbl_suspend, ep93xxbl_resume);
static struct platform_driver ep93xxbl_driver = {
.driver = {
.name = "ep93xx-bl",
.pm = &ep93xxbl_pm_ops,
},
.probe = ep93xxbl_probe,
};
module_platform_driver(ep93xxbl_driver);
MODULE_DESCRIPTION("EP93xx Backlight Driver");
MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ep93xx-bl");

View file

@ -0,0 +1,119 @@
/*
* Generic Backlight Driver
*
* Copyright (c) 2004-2008 Richard Purdie
*
* 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/platform_device.h>
#include <linux/mutex.h>
#include <linux/fb.h>
#include <linux/backlight.h>
static int genericbl_intensity;
static struct backlight_device *generic_backlight_device;
static struct generic_bl_info *bl_machinfo;
/* Flag to signal when the battery is low */
#define GENERICBL_BATTLOW BL_CORE_DRIVER1
static int genericbl_send_intensity(struct backlight_device *bd)
{
int intensity = bd->props.brightness;
if (bd->props.power != FB_BLANK_UNBLANK)
intensity = 0;
if (bd->props.state & BL_CORE_FBBLANK)
intensity = 0;
if (bd->props.state & BL_CORE_SUSPENDED)
intensity = 0;
if (bd->props.state & GENERICBL_BATTLOW)
intensity &= bl_machinfo->limit_mask;
bl_machinfo->set_bl_intensity(intensity);
genericbl_intensity = intensity;
if (bl_machinfo->kick_battery)
bl_machinfo->kick_battery();
return 0;
}
static int genericbl_get_intensity(struct backlight_device *bd)
{
return genericbl_intensity;
}
static const struct backlight_ops genericbl_ops = {
.options = BL_CORE_SUSPENDRESUME,
.get_brightness = genericbl_get_intensity,
.update_status = genericbl_send_intensity,
};
static int genericbl_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct generic_bl_info *machinfo = dev_get_platdata(&pdev->dev);
const char *name = "generic-bl";
struct backlight_device *bd;
bl_machinfo = machinfo;
if (!machinfo->limit_mask)
machinfo->limit_mask = -1;
if (machinfo->name)
name = machinfo->name;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = machinfo->max_intensity;
bd = devm_backlight_device_register(&pdev->dev, name, &pdev->dev,
NULL, &genericbl_ops, &props);
if (IS_ERR(bd))
return PTR_ERR(bd);
platform_set_drvdata(pdev, bd);
bd->props.power = FB_BLANK_UNBLANK;
bd->props.brightness = machinfo->default_intensity;
backlight_update_status(bd);
generic_backlight_device = bd;
dev_info(&pdev->dev, "Generic Backlight Driver Initialized.\n");
return 0;
}
static int genericbl_remove(struct platform_device *pdev)
{
struct backlight_device *bd = platform_get_drvdata(pdev);
bd->props.power = 0;
bd->props.brightness = 0;
backlight_update_status(bd);
dev_info(&pdev->dev, "Generic Backlight Driver Unloaded\n");
return 0;
}
static struct platform_driver genericbl_driver = {
.probe = genericbl_probe,
.remove = genericbl_remove,
.driver = {
.name = "generic-bl",
},
};
module_platform_driver(genericbl_driver);
MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>");
MODULE_DESCRIPTION("Generic Backlight Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,164 @@
/*
* gpio_backlight.c - Simple GPIO-controlled backlight
*
* 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/backlight.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_data/gpio_backlight.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
struct gpio_backlight {
struct device *dev;
struct device *fbdev;
int gpio;
int active;
int def_value;
};
static int gpio_backlight_update_status(struct backlight_device *bl)
{
struct gpio_backlight *gbl = bl_get_data(bl);
int brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK ||
bl->props.fb_blank != FB_BLANK_UNBLANK ||
bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
brightness = 0;
gpio_set_value_cansleep(gbl->gpio,
brightness ? gbl->active : !gbl->active);
return 0;
}
static int gpio_backlight_check_fb(struct backlight_device *bl,
struct fb_info *info)
{
struct gpio_backlight *gbl = bl_get_data(bl);
return gbl->fbdev == NULL || gbl->fbdev == info->dev;
}
static const struct backlight_ops gpio_backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = gpio_backlight_update_status,
.check_fb = gpio_backlight_check_fb,
};
static int gpio_backlight_probe_dt(struct platform_device *pdev,
struct gpio_backlight *gbl)
{
struct device_node *np = pdev->dev.of_node;
enum of_gpio_flags gpio_flags;
gbl->gpio = of_get_gpio_flags(np, 0, &gpio_flags);
if (!gpio_is_valid(gbl->gpio)) {
if (gbl->gpio != -EPROBE_DEFER) {
dev_err(&pdev->dev,
"Error: The gpios parameter is missing or invalid.\n");
}
return gbl->gpio;
}
gbl->active = (gpio_flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1;
gbl->def_value = of_property_read_bool(np, "default-on");
return 0;
}
static int gpio_backlight_probe(struct platform_device *pdev)
{
struct gpio_backlight_platform_data *pdata =
dev_get_platdata(&pdev->dev);
struct backlight_properties props;
struct backlight_device *bl;
struct gpio_backlight *gbl;
struct device_node *np = pdev->dev.of_node;
int ret;
if (!pdata && !np) {
dev_err(&pdev->dev,
"failed to find platform data or device tree node.\n");
return -ENODEV;
}
gbl = devm_kzalloc(&pdev->dev, sizeof(*gbl), GFP_KERNEL);
if (gbl == NULL)
return -ENOMEM;
gbl->dev = &pdev->dev;
if (np) {
ret = gpio_backlight_probe_dt(pdev, gbl);
if (ret)
return ret;
} else {
gbl->fbdev = pdata->fbdev;
gbl->gpio = pdata->gpio;
gbl->active = pdata->active_low ? 0 : 1;
gbl->def_value = pdata->def_value;
}
ret = devm_gpio_request_one(gbl->dev, gbl->gpio, GPIOF_DIR_OUT |
(gbl->active ? GPIOF_INIT_LOW
: GPIOF_INIT_HIGH),
pdata ? pdata->name : "backlight");
if (ret < 0) {
dev_err(&pdev->dev, "unable to request GPIO\n");
return ret;
}
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW;
props.max_brightness = 1;
bl = devm_backlight_device_register(&pdev->dev, dev_name(&pdev->dev),
&pdev->dev, gbl, &gpio_backlight_ops,
&props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
bl->props.brightness = gbl->def_value;
backlight_update_status(bl);
platform_set_drvdata(pdev, bl);
return 0;
}
#ifdef CONFIG_OF
static struct of_device_id gpio_backlight_of_match[] = {
{ .compatible = "gpio-backlight" },
{ /* sentinel */ }
};
#endif
static struct platform_driver gpio_backlight_driver = {
.driver = {
.name = "gpio-backlight",
.of_match_table = of_match_ptr(gpio_backlight_of_match),
},
.probe = gpio_backlight_probe,
};
module_platform_driver(gpio_backlight_driver);
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_DESCRIPTION("GPIO-based Backlight Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:gpio-backlight");

View file

@ -0,0 +1,175 @@
/*
* Backlight Driver for HP Jornada 680
*
* Copyright (c) 2005 Andriy Skulysh
*
* Based on Sharp's Corgi Backlight Driver
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <cpu/dac.h>
#include <mach/hp6xx.h>
#include <asm/hd64461.h>
#define HP680_MAX_INTENSITY 255
#define HP680_DEFAULT_INTENSITY 10
static int hp680bl_suspended;
static int current_intensity;
static DEFINE_SPINLOCK(bl_lock);
static void hp680bl_send_intensity(struct backlight_device *bd)
{
unsigned long flags;
u16 v;
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 (hp680bl_suspended)
intensity = 0;
spin_lock_irqsave(&bl_lock, flags);
if (intensity && current_intensity == 0) {
sh_dac_enable(DAC_LCD_BRIGHTNESS);
v = inw(HD64461_GPBDR);
v &= ~HD64461_GPBDR_LCDOFF;
outw(v, HD64461_GPBDR);
sh_dac_output(255-(u8)intensity, DAC_LCD_BRIGHTNESS);
} else if (intensity == 0 && current_intensity != 0) {
sh_dac_output(255-(u8)intensity, DAC_LCD_BRIGHTNESS);
sh_dac_disable(DAC_LCD_BRIGHTNESS);
v = inw(HD64461_GPBDR);
v |= HD64461_GPBDR_LCDOFF;
outw(v, HD64461_GPBDR);
} else if (intensity) {
sh_dac_output(255-(u8)intensity, DAC_LCD_BRIGHTNESS);
}
spin_unlock_irqrestore(&bl_lock, flags);
current_intensity = intensity;
}
#ifdef CONFIG_PM_SLEEP
static int hp680bl_suspend(struct device *dev)
{
struct backlight_device *bd = dev_get_drvdata(dev);
hp680bl_suspended = 1;
hp680bl_send_intensity(bd);
return 0;
}
static int hp680bl_resume(struct device *dev)
{
struct backlight_device *bd = dev_get_drvdata(dev);
hp680bl_suspended = 0;
hp680bl_send_intensity(bd);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(hp680bl_pm_ops, hp680bl_suspend, hp680bl_resume);
static int hp680bl_set_intensity(struct backlight_device *bd)
{
hp680bl_send_intensity(bd);
return 0;
}
static int hp680bl_get_intensity(struct backlight_device *bd)
{
return current_intensity;
}
static const struct backlight_ops hp680bl_ops = {
.get_brightness = hp680bl_get_intensity,
.update_status = hp680bl_set_intensity,
};
static int hp680bl_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct backlight_device *bd;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = HP680_MAX_INTENSITY;
bd = devm_backlight_device_register(&pdev->dev, "hp680-bl", &pdev->dev,
NULL, &hp680bl_ops, &props);
if (IS_ERR(bd))
return PTR_ERR(bd);
platform_set_drvdata(pdev, bd);
bd->props.brightness = HP680_DEFAULT_INTENSITY;
hp680bl_send_intensity(bd);
return 0;
}
static int hp680bl_remove(struct platform_device *pdev)
{
struct backlight_device *bd = platform_get_drvdata(pdev);
bd->props.brightness = 0;
bd->props.power = 0;
hp680bl_send_intensity(bd);
return 0;
}
static struct platform_driver hp680bl_driver = {
.probe = hp680bl_probe,
.remove = hp680bl_remove,
.driver = {
.name = "hp680-bl",
.pm = &hp680bl_pm_ops,
},
};
static struct platform_device *hp680bl_device;
static int __init hp680bl_init(void)
{
int ret;
ret = platform_driver_register(&hp680bl_driver);
if (ret)
return ret;
hp680bl_device = platform_device_register_simple("hp680-bl", -1,
NULL, 0);
if (IS_ERR(hp680bl_device)) {
platform_driver_unregister(&hp680bl_driver);
return PTR_ERR(hp680bl_device);
}
return 0;
}
static void __exit hp680bl_exit(void)
{
platform_device_unregister(hp680bl_device);
platform_driver_unregister(&hp680bl_driver);
}
module_init(hp680bl_init);
module_exit(hp680bl_exit);
MODULE_AUTHOR("Andriy Skulysh <askulysh@gmail.com>");
MODULE_DESCRIPTION("HP Jornada 680 Backlight Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,682 @@
/*
* Driver for the Himax HX-8357 LCD Controller
*
* Copyright 2012 Free Electrons
*
* Licensed under the GPLv2 or later.
*/
#include <linux/delay.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/spi/spi.h>
#define HX8357_NUM_IM_PINS 3
#define HX8357_SWRESET 0x01
#define HX8357_GET_RED_CHANNEL 0x06
#define HX8357_GET_GREEN_CHANNEL 0x07
#define HX8357_GET_BLUE_CHANNEL 0x08
#define HX8357_GET_POWER_MODE 0x0a
#define HX8357_GET_MADCTL 0x0b
#define HX8357_GET_PIXEL_FORMAT 0x0c
#define HX8357_GET_DISPLAY_MODE 0x0d
#define HX8357_GET_SIGNAL_MODE 0x0e
#define HX8357_GET_DIAGNOSTIC_RESULT 0x0f
#define HX8357_ENTER_SLEEP_MODE 0x10
#define HX8357_EXIT_SLEEP_MODE 0x11
#define HX8357_ENTER_PARTIAL_MODE 0x12
#define HX8357_ENTER_NORMAL_MODE 0x13
#define HX8357_EXIT_INVERSION_MODE 0x20
#define HX8357_ENTER_INVERSION_MODE 0x21
#define HX8357_SET_DISPLAY_OFF 0x28
#define HX8357_SET_DISPLAY_ON 0x29
#define HX8357_SET_COLUMN_ADDRESS 0x2a
#define HX8357_SET_PAGE_ADDRESS 0x2b
#define HX8357_WRITE_MEMORY_START 0x2c
#define HX8357_READ_MEMORY_START 0x2e
#define HX8357_SET_PARTIAL_AREA 0x30
#define HX8357_SET_SCROLL_AREA 0x33
#define HX8357_SET_TEAR_OFF 0x34
#define HX8357_SET_TEAR_ON 0x35
#define HX8357_SET_ADDRESS_MODE 0x36
#define HX8357_SET_SCROLL_START 0x37
#define HX8357_EXIT_IDLE_MODE 0x38
#define HX8357_ENTER_IDLE_MODE 0x39
#define HX8357_SET_PIXEL_FORMAT 0x3a
#define HX8357_SET_PIXEL_FORMAT_DBI_3BIT (0x1)
#define HX8357_SET_PIXEL_FORMAT_DBI_16BIT (0x5)
#define HX8357_SET_PIXEL_FORMAT_DBI_18BIT (0x6)
#define HX8357_SET_PIXEL_FORMAT_DPI_3BIT (0x1 << 4)
#define HX8357_SET_PIXEL_FORMAT_DPI_16BIT (0x5 << 4)
#define HX8357_SET_PIXEL_FORMAT_DPI_18BIT (0x6 << 4)
#define HX8357_WRITE_MEMORY_CONTINUE 0x3c
#define HX8357_READ_MEMORY_CONTINUE 0x3e
#define HX8357_SET_TEAR_SCAN_LINES 0x44
#define HX8357_GET_SCAN_LINES 0x45
#define HX8357_READ_DDB_START 0xa1
#define HX8357_SET_DISPLAY_MODE 0xb4
#define HX8357_SET_DISPLAY_MODE_RGB_THROUGH (0x3)
#define HX8357_SET_DISPLAY_MODE_RGB_INTERFACE (1 << 4)
#define HX8357_SET_PANEL_DRIVING 0xc0
#define HX8357_SET_DISPLAY_FRAME 0xc5
#define HX8357_SET_RGB 0xc6
#define HX8357_SET_RGB_ENABLE_HIGH (1 << 1)
#define HX8357_SET_GAMMA 0xc8
#define HX8357_SET_POWER 0xd0
#define HX8357_SET_VCOM 0xd1
#define HX8357_SET_POWER_NORMAL 0xd2
#define HX8357_SET_PANEL_RELATED 0xe9
#define HX8369_SET_DISPLAY_BRIGHTNESS 0x51
#define HX8369_WRITE_CABC_DISPLAY_VALUE 0x53
#define HX8369_WRITE_CABC_BRIGHT_CTRL 0x55
#define HX8369_WRITE_CABC_MIN_BRIGHTNESS 0x5e
#define HX8369_SET_POWER 0xb1
#define HX8369_SET_DISPLAY_MODE 0xb2
#define HX8369_SET_DISPLAY_WAVEFORM_CYC 0xb4
#define HX8369_SET_VCOM 0xb6
#define HX8369_SET_EXTENSION_COMMAND 0xb9
#define HX8369_SET_GIP 0xd5
#define HX8369_SET_GAMMA_CURVE_RELATED 0xe0
struct hx8357_data {
unsigned im_pins[HX8357_NUM_IM_PINS];
unsigned reset;
struct spi_device *spi;
int state;
bool use_im_pins;
};
static u8 hx8357_seq_power[] = {
HX8357_SET_POWER, 0x44, 0x41, 0x06,
};
static u8 hx8357_seq_vcom[] = {
HX8357_SET_VCOM, 0x40, 0x10,
};
static u8 hx8357_seq_power_normal[] = {
HX8357_SET_POWER_NORMAL, 0x05, 0x12,
};
static u8 hx8357_seq_panel_driving[] = {
HX8357_SET_PANEL_DRIVING, 0x14, 0x3b, 0x00, 0x02, 0x11,
};
static u8 hx8357_seq_display_frame[] = {
HX8357_SET_DISPLAY_FRAME, 0x0c,
};
static u8 hx8357_seq_panel_related[] = {
HX8357_SET_PANEL_RELATED, 0x01,
};
static u8 hx8357_seq_undefined1[] = {
0xea, 0x03, 0x00, 0x00,
};
static u8 hx8357_seq_undefined2[] = {
0xeb, 0x40, 0x54, 0x26, 0xdb,
};
static u8 hx8357_seq_gamma[] = {
HX8357_SET_GAMMA, 0x00, 0x15, 0x00, 0x22, 0x00,
0x08, 0x77, 0x26, 0x77, 0x22, 0x04, 0x00,
};
static u8 hx8357_seq_address_mode[] = {
HX8357_SET_ADDRESS_MODE, 0xc0,
};
static u8 hx8357_seq_pixel_format[] = {
HX8357_SET_PIXEL_FORMAT,
HX8357_SET_PIXEL_FORMAT_DPI_18BIT |
HX8357_SET_PIXEL_FORMAT_DBI_18BIT,
};
static u8 hx8357_seq_column_address[] = {
HX8357_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x01, 0x3f,
};
static u8 hx8357_seq_page_address[] = {
HX8357_SET_PAGE_ADDRESS, 0x00, 0x00, 0x01, 0xdf,
};
static u8 hx8357_seq_rgb[] = {
HX8357_SET_RGB, 0x02,
};
static u8 hx8357_seq_display_mode[] = {
HX8357_SET_DISPLAY_MODE,
HX8357_SET_DISPLAY_MODE_RGB_THROUGH |
HX8357_SET_DISPLAY_MODE_RGB_INTERFACE,
};
static u8 hx8369_seq_write_CABC_min_brightness[] = {
HX8369_WRITE_CABC_MIN_BRIGHTNESS, 0x00,
};
static u8 hx8369_seq_write_CABC_control[] = {
HX8369_WRITE_CABC_DISPLAY_VALUE, 0x24,
};
static u8 hx8369_seq_set_display_brightness[] = {
HX8369_SET_DISPLAY_BRIGHTNESS, 0xFF,
};
static u8 hx8369_seq_write_CABC_control_setting[] = {
HX8369_WRITE_CABC_BRIGHT_CTRL, 0x02,
};
static u8 hx8369_seq_extension_command[] = {
HX8369_SET_EXTENSION_COMMAND, 0xff, 0x83, 0x69,
};
static u8 hx8369_seq_display_related[] = {
HX8369_SET_DISPLAY_MODE, 0x00, 0x2b, 0x03, 0x03, 0x70, 0x00,
0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x01,
};
static u8 hx8369_seq_panel_waveform_cycle[] = {
HX8369_SET_DISPLAY_WAVEFORM_CYC, 0x0a, 0x1d, 0x80, 0x06, 0x02,
};
static u8 hx8369_seq_set_address_mode[] = {
HX8357_SET_ADDRESS_MODE, 0x00,
};
static u8 hx8369_seq_vcom[] = {
HX8369_SET_VCOM, 0x3e, 0x3e,
};
static u8 hx8369_seq_gip[] = {
HX8369_SET_GIP, 0x00, 0x01, 0x03, 0x25, 0x01, 0x02, 0x28, 0x70,
0x11, 0x13, 0x00, 0x00, 0x40, 0x26, 0x51, 0x37, 0x00, 0x00, 0x71,
0x35, 0x60, 0x24, 0x07, 0x0f, 0x04, 0x04,
};
static u8 hx8369_seq_power[] = {
HX8369_SET_POWER, 0x01, 0x00, 0x34, 0x03, 0x00, 0x11, 0x11, 0x32,
0x2f, 0x3f, 0x3f, 0x01, 0x3a, 0x01, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6,
};
static u8 hx8369_seq_gamma_curve_related[] = {
HX8369_SET_GAMMA_CURVE_RELATED, 0x00, 0x0d, 0x19, 0x2f, 0x3b, 0x3d,
0x2e, 0x4a, 0x08, 0x0e, 0x0f, 0x14, 0x16, 0x14, 0x14, 0x14, 0x1e,
0x00, 0x0d, 0x19, 0x2f, 0x3b, 0x3d, 0x2e, 0x4a, 0x08, 0x0e, 0x0f,
0x14, 0x16, 0x14, 0x14, 0x14, 0x1e,
};
static int hx8357_spi_write_then_read(struct lcd_device *lcdev,
u8 *txbuf, u16 txlen,
u8 *rxbuf, u16 rxlen)
{
struct hx8357_data *lcd = lcd_get_data(lcdev);
struct spi_message msg;
struct spi_transfer xfer[2];
u16 *local_txbuf = NULL;
int ret = 0;
memset(xfer, 0, sizeof(xfer));
spi_message_init(&msg);
if (txlen) {
int i;
local_txbuf = kcalloc(txlen, sizeof(*local_txbuf), GFP_KERNEL);
if (!local_txbuf)
return -ENOMEM;
for (i = 0; i < txlen; i++) {
local_txbuf[i] = txbuf[i];
if (i > 0)
local_txbuf[i] |= 1 << 8;
}
xfer[0].len = 2 * txlen;
xfer[0].bits_per_word = 9;
xfer[0].tx_buf = local_txbuf;
spi_message_add_tail(&xfer[0], &msg);
}
if (rxlen) {
xfer[1].len = rxlen;
xfer[1].bits_per_word = 8;
xfer[1].rx_buf = rxbuf;
spi_message_add_tail(&xfer[1], &msg);
}
ret = spi_sync(lcd->spi, &msg);
if (ret < 0)
dev_err(&lcdev->dev, "Couldn't send SPI data\n");
if (txlen)
kfree(local_txbuf);
return ret;
}
static inline int hx8357_spi_write_array(struct lcd_device *lcdev,
u8 *value, u8 len)
{
return hx8357_spi_write_then_read(lcdev, value, len, NULL, 0);
}
static inline int hx8357_spi_write_byte(struct lcd_device *lcdev,
u8 value)
{
return hx8357_spi_write_then_read(lcdev, &value, 1, NULL, 0);
}
static int hx8357_enter_standby(struct lcd_device *lcdev)
{
int ret;
ret = hx8357_spi_write_byte(lcdev, HX8357_SET_DISPLAY_OFF);
if (ret < 0)
return ret;
usleep_range(10000, 12000);
ret = hx8357_spi_write_byte(lcdev, HX8357_ENTER_SLEEP_MODE);
if (ret < 0)
return ret;
/*
* The controller needs 120ms when entering in sleep mode before we can
* send the command to go off sleep mode
*/
msleep(120);
return 0;
}
static int hx8357_exit_standby(struct lcd_device *lcdev)
{
int ret;
ret = hx8357_spi_write_byte(lcdev, HX8357_EXIT_SLEEP_MODE);
if (ret < 0)
return ret;
/*
* The controller needs 120ms when exiting from sleep mode before we
* can send the command to enter in sleep mode
*/
msleep(120);
ret = hx8357_spi_write_byte(lcdev, HX8357_SET_DISPLAY_ON);
if (ret < 0)
return ret;
return 0;
}
static void hx8357_lcd_reset(struct lcd_device *lcdev)
{
struct hx8357_data *lcd = lcd_get_data(lcdev);
/* Reset the screen */
gpio_set_value(lcd->reset, 1);
usleep_range(10000, 12000);
gpio_set_value(lcd->reset, 0);
usleep_range(10000, 12000);
gpio_set_value(lcd->reset, 1);
/* The controller needs 120ms to recover from reset */
msleep(120);
}
static int hx8357_lcd_init(struct lcd_device *lcdev)
{
struct hx8357_data *lcd = lcd_get_data(lcdev);
int ret;
/*
* Set the interface selection pins to SPI mode, with three
* wires
*/
if (lcd->use_im_pins) {
gpio_set_value_cansleep(lcd->im_pins[0], 1);
gpio_set_value_cansleep(lcd->im_pins[1], 0);
gpio_set_value_cansleep(lcd->im_pins[2], 1);
}
ret = hx8357_spi_write_array(lcdev, hx8357_seq_power,
ARRAY_SIZE(hx8357_seq_power));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_vcom,
ARRAY_SIZE(hx8357_seq_vcom));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_power_normal,
ARRAY_SIZE(hx8357_seq_power_normal));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_panel_driving,
ARRAY_SIZE(hx8357_seq_panel_driving));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_display_frame,
ARRAY_SIZE(hx8357_seq_display_frame));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_panel_related,
ARRAY_SIZE(hx8357_seq_panel_related));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_undefined1,
ARRAY_SIZE(hx8357_seq_undefined1));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_undefined2,
ARRAY_SIZE(hx8357_seq_undefined2));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_gamma,
ARRAY_SIZE(hx8357_seq_gamma));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_address_mode,
ARRAY_SIZE(hx8357_seq_address_mode));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_pixel_format,
ARRAY_SIZE(hx8357_seq_pixel_format));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_column_address,
ARRAY_SIZE(hx8357_seq_column_address));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_page_address,
ARRAY_SIZE(hx8357_seq_page_address));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_rgb,
ARRAY_SIZE(hx8357_seq_rgb));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8357_seq_display_mode,
ARRAY_SIZE(hx8357_seq_display_mode));
if (ret < 0)
return ret;
ret = hx8357_spi_write_byte(lcdev, HX8357_EXIT_SLEEP_MODE);
if (ret < 0)
return ret;
/*
* The controller needs 120ms to fully recover from exiting sleep mode
*/
msleep(120);
ret = hx8357_spi_write_byte(lcdev, HX8357_SET_DISPLAY_ON);
if (ret < 0)
return ret;
usleep_range(5000, 7000);
ret = hx8357_spi_write_byte(lcdev, HX8357_WRITE_MEMORY_START);
if (ret < 0)
return ret;
return 0;
}
static int hx8369_lcd_init(struct lcd_device *lcdev)
{
int ret;
ret = hx8357_spi_write_array(lcdev, hx8369_seq_extension_command,
ARRAY_SIZE(hx8369_seq_extension_command));
if (ret < 0)
return ret;
usleep_range(10000, 12000);
ret = hx8357_spi_write_array(lcdev, hx8369_seq_display_related,
ARRAY_SIZE(hx8369_seq_display_related));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8369_seq_panel_waveform_cycle,
ARRAY_SIZE(hx8369_seq_panel_waveform_cycle));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8369_seq_set_address_mode,
ARRAY_SIZE(hx8369_seq_set_address_mode));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8369_seq_vcom,
ARRAY_SIZE(hx8369_seq_vcom));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8369_seq_gip,
ARRAY_SIZE(hx8369_seq_gip));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev, hx8369_seq_power,
ARRAY_SIZE(hx8369_seq_power));
if (ret < 0)
return ret;
ret = hx8357_spi_write_byte(lcdev, HX8357_EXIT_SLEEP_MODE);
if (ret < 0)
return ret;
/*
* The controller needs 120ms to fully recover from exiting sleep mode
*/
msleep(120);
ret = hx8357_spi_write_array(lcdev, hx8369_seq_gamma_curve_related,
ARRAY_SIZE(hx8369_seq_gamma_curve_related));
if (ret < 0)
return ret;
ret = hx8357_spi_write_byte(lcdev, HX8357_EXIT_SLEEP_MODE);
if (ret < 0)
return ret;
usleep_range(1000, 1200);
ret = hx8357_spi_write_array(lcdev, hx8369_seq_write_CABC_control,
ARRAY_SIZE(hx8369_seq_write_CABC_control));
if (ret < 0)
return ret;
usleep_range(10000, 12000);
ret = hx8357_spi_write_array(lcdev,
hx8369_seq_write_CABC_control_setting,
ARRAY_SIZE(hx8369_seq_write_CABC_control_setting));
if (ret < 0)
return ret;
ret = hx8357_spi_write_array(lcdev,
hx8369_seq_write_CABC_min_brightness,
ARRAY_SIZE(hx8369_seq_write_CABC_min_brightness));
if (ret < 0)
return ret;
usleep_range(10000, 12000);
ret = hx8357_spi_write_array(lcdev, hx8369_seq_set_display_brightness,
ARRAY_SIZE(hx8369_seq_set_display_brightness));
if (ret < 0)
return ret;
ret = hx8357_spi_write_byte(lcdev, HX8357_SET_DISPLAY_ON);
if (ret < 0)
return ret;
return 0;
}
#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
static int hx8357_set_power(struct lcd_device *lcdev, int power)
{
struct hx8357_data *lcd = lcd_get_data(lcdev);
int ret = 0;
if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->state))
ret = hx8357_exit_standby(lcdev);
else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->state))
ret = hx8357_enter_standby(lcdev);
if (ret == 0)
lcd->state = power;
else
dev_warn(&lcdev->dev, "failed to set power mode %d\n", power);
return ret;
}
static int hx8357_get_power(struct lcd_device *lcdev)
{
struct hx8357_data *lcd = lcd_get_data(lcdev);
return lcd->state;
}
static struct lcd_ops hx8357_ops = {
.set_power = hx8357_set_power,
.get_power = hx8357_get_power,
};
static const struct of_device_id hx8357_dt_ids[] = {
{
.compatible = "himax,hx8357",
.data = hx8357_lcd_init,
},
{
.compatible = "himax,hx8369",
.data = hx8369_lcd_init,
},
{},
};
MODULE_DEVICE_TABLE(of, hx8357_dt_ids);
static int hx8357_probe(struct spi_device *spi)
{
struct lcd_device *lcdev;
struct hx8357_data *lcd;
const struct of_device_id *match;
int i, ret;
lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
if (!lcd)
return -ENOMEM;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "SPI setup failed.\n");
return ret;
}
lcd->spi = spi;
match = of_match_device(hx8357_dt_ids, &spi->dev);
if (!match || !match->data)
return -EINVAL;
lcd->reset = of_get_named_gpio(spi->dev.of_node, "gpios-reset", 0);
if (!gpio_is_valid(lcd->reset)) {
dev_err(&spi->dev, "Missing dt property: gpios-reset\n");
return -EINVAL;
}
ret = devm_gpio_request_one(&spi->dev, lcd->reset,
GPIOF_OUT_INIT_HIGH,
"hx8357-reset");
if (ret) {
dev_err(&spi->dev,
"failed to request gpio %d: %d\n",
lcd->reset, ret);
return -EINVAL;
}
if (of_find_property(spi->dev.of_node, "im-gpios", NULL)) {
lcd->use_im_pins = 1;
for (i = 0; i < HX8357_NUM_IM_PINS; i++) {
lcd->im_pins[i] = of_get_named_gpio(spi->dev.of_node,
"im-gpios", i);
if (lcd->im_pins[i] == -EPROBE_DEFER) {
dev_info(&spi->dev, "GPIO requested is not here yet, deferring the probe\n");
return -EPROBE_DEFER;
}
if (!gpio_is_valid(lcd->im_pins[i])) {
dev_err(&spi->dev, "Missing dt property: im-gpios\n");
return -EINVAL;
}
ret = devm_gpio_request_one(&spi->dev, lcd->im_pins[i],
GPIOF_OUT_INIT_LOW,
"im_pins");
if (ret) {
dev_err(&spi->dev, "failed to request gpio %d: %d\n",
lcd->im_pins[i], ret);
return -EINVAL;
}
}
} else {
lcd->use_im_pins = 0;
}
lcdev = devm_lcd_device_register(&spi->dev, "mxsfb", &spi->dev, lcd,
&hx8357_ops);
if (IS_ERR(lcdev)) {
ret = PTR_ERR(lcdev);
return ret;
}
spi_set_drvdata(spi, lcdev);
hx8357_lcd_reset(lcdev);
ret = ((int (*)(struct lcd_device *))match->data)(lcdev);
if (ret) {
dev_err(&spi->dev, "Couldn't initialize panel\n");
return ret;
}
dev_info(&spi->dev, "Panel probed\n");
return 0;
}
static struct spi_driver hx8357_driver = {
.probe = hx8357_probe,
.driver = {
.name = "hx8357",
.of_match_table = hx8357_dt_ids,
},
};
module_spi_driver(hx8357_driver);
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_DESCRIPTION("Himax HX-8357 LCD Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,551 @@
/*
* (C) Copyright 2008
* Stefano Babic, DENX Software Engineering, sbabic@denx.de.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This driver implements a lcd device for the ILITEK 922x display
* controller. The interface to the display is SPI and the display's
* memory is cyclically updated over the RGB interface.
*/
#include <linux/fb.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/string.h>
/* Register offset, see manual section 8.2 */
#define REG_START_OSCILLATION 0x00
#define REG_DRIVER_CODE_READ 0x00
#define REG_DRIVER_OUTPUT_CONTROL 0x01
#define REG_LCD_AC_DRIVEING_CONTROL 0x02
#define REG_ENTRY_MODE 0x03
#define REG_COMPARE_1 0x04
#define REG_COMPARE_2 0x05
#define REG_DISPLAY_CONTROL_1 0x07
#define REG_DISPLAY_CONTROL_2 0x08
#define REG_DISPLAY_CONTROL_3 0x09
#define REG_FRAME_CYCLE_CONTROL 0x0B
#define REG_EXT_INTF_CONTROL 0x0C
#define REG_POWER_CONTROL_1 0x10
#define REG_POWER_CONTROL_2 0x11
#define REG_POWER_CONTROL_3 0x12
#define REG_POWER_CONTROL_4 0x13
#define REG_RAM_ADDRESS_SET 0x21
#define REG_WRITE_DATA_TO_GRAM 0x22
#define REG_RAM_WRITE_MASK1 0x23
#define REG_RAM_WRITE_MASK2 0x24
#define REG_GAMMA_CONTROL_1 0x30
#define REG_GAMMA_CONTROL_2 0x31
#define REG_GAMMA_CONTROL_3 0x32
#define REG_GAMMA_CONTROL_4 0x33
#define REG_GAMMA_CONTROL_5 0x34
#define REG_GAMMA_CONTROL_6 0x35
#define REG_GAMMA_CONTROL_7 0x36
#define REG_GAMMA_CONTROL_8 0x37
#define REG_GAMMA_CONTROL_9 0x38
#define REG_GAMMA_CONTROL_10 0x39
#define REG_GATE_SCAN_CONTROL 0x40
#define REG_VERT_SCROLL_CONTROL 0x41
#define REG_FIRST_SCREEN_DRIVE_POS 0x42
#define REG_SECOND_SCREEN_DRIVE_POS 0x43
#define REG_RAM_ADDR_POS_H 0x44
#define REG_RAM_ADDR_POS_V 0x45
#define REG_OSCILLATOR_CONTROL 0x4F
#define REG_GPIO 0x60
#define REG_OTP_VCM_PROGRAMMING 0x61
#define REG_OTP_VCM_STATUS_ENABLE 0x62
#define REG_OTP_PROGRAMMING_ID_KEY 0x65
/*
* maximum frequency for register access
* (not for the GRAM access)
*/
#define ILITEK_MAX_FREQ_REG 4000000
/*
* Device ID as found in the datasheet (supports 9221 and 9222)
*/
#define ILITEK_DEVICE_ID 0x9220
#define ILITEK_DEVICE_ID_MASK 0xFFF0
/* Last two bits in the START BYTE */
#define START_RS_INDEX 0
#define START_RS_REG 1
#define START_RW_WRITE 0
#define START_RW_READ 1
/**
* START_BYTE(id, rs, rw)
*
* Set the start byte according to the required operation.
* The start byte is defined as:
* ----------------------------------
* | 0 | 1 | 1 | 1 | 0 | ID | RS | RW |
* ----------------------------------
* @id: display's id as set by the manufacturer
* @rs: operation type bit, one of:
* - START_RS_INDEX set the index register
* - START_RS_REG write/read registers/GRAM
* @rw: read/write operation
* - START_RW_WRITE write
* - START_RW_READ read
*/
#define START_BYTE(id, rs, rw) \
(0x70 | (((id) & 0x01) << 2) | (((rs) & 0x01) << 1) | ((rw) & 0x01))
/**
* CHECK_FREQ_REG(spi_device s, spi_transfer x) - Check the frequency
* for the SPI transfer. According to the datasheet, the controller
* accept higher frequency for the GRAM transfer, but it requires
* lower frequency when the registers are read/written.
* The macro sets the frequency in the spi_transfer structure if
* the frequency exceeds the maximum value.
*/
#define CHECK_FREQ_REG(s, x) \
do { \
if (s->max_speed_hz > ILITEK_MAX_FREQ_REG) \
((struct spi_transfer *)x)->speed_hz = \
ILITEK_MAX_FREQ_REG; \
} while (0)
#define CMD_BUFSIZE 16
#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
#define set_tx_byte(b) (tx_invert ? ~(b) : b)
/**
* ili922x_id - id as set by manufacturer
*/
static int ili922x_id = 1;
module_param(ili922x_id, int, 0);
static int tx_invert;
module_param(tx_invert, int, 0);
/**
* driver's private structure
*/
struct ili922x {
struct spi_device *spi;
struct lcd_device *ld;
int power;
};
/**
* ili922x_read_status - read status register from display
* @spi: spi device
* @rs: output value
*/
static int ili922x_read_status(struct spi_device *spi, u16 *rs)
{
struct spi_message msg;
struct spi_transfer xfer;
unsigned char tbuf[CMD_BUFSIZE];
unsigned char rbuf[CMD_BUFSIZE];
int ret, i;
memset(&xfer, 0, sizeof(struct spi_transfer));
spi_message_init(&msg);
xfer.tx_buf = tbuf;
xfer.rx_buf = rbuf;
xfer.cs_change = 1;
CHECK_FREQ_REG(spi, &xfer);
tbuf[0] = set_tx_byte(START_BYTE(ili922x_id, START_RS_INDEX,
START_RW_READ));
/*
* we need 4-byte xfer here due to invalid dummy byte
* received after start byte
*/
for (i = 1; i < 4; i++)
tbuf[i] = set_tx_byte(0); /* dummy */
xfer.bits_per_word = 8;
xfer.len = 4;
spi_message_add_tail(&xfer, &msg);
ret = spi_sync(spi, &msg);
if (ret < 0) {
dev_dbg(&spi->dev, "Error sending SPI message 0x%x", ret);
return ret;
}
*rs = (rbuf[2] << 8) + rbuf[3];
return 0;
}
/**
* ili922x_read - read register from display
* @spi: spi device
* @reg: offset of the register to be read
* @rx: output value
*/
static int ili922x_read(struct spi_device *spi, u8 reg, u16 *rx)
{
struct spi_message msg;
struct spi_transfer xfer_regindex, xfer_regvalue;
unsigned char tbuf[CMD_BUFSIZE];
unsigned char rbuf[CMD_BUFSIZE];
int ret, len = 0, send_bytes;
memset(&xfer_regindex, 0, sizeof(struct spi_transfer));
memset(&xfer_regvalue, 0, sizeof(struct spi_transfer));
spi_message_init(&msg);
xfer_regindex.tx_buf = tbuf;
xfer_regindex.rx_buf = rbuf;
xfer_regindex.cs_change = 1;
CHECK_FREQ_REG(spi, &xfer_regindex);
tbuf[0] = set_tx_byte(START_BYTE(ili922x_id, START_RS_INDEX,
START_RW_WRITE));
tbuf[1] = set_tx_byte(0);
tbuf[2] = set_tx_byte(reg);
xfer_regindex.bits_per_word = 8;
len = xfer_regindex.len = 3;
spi_message_add_tail(&xfer_regindex, &msg);
send_bytes = len;
tbuf[len++] = set_tx_byte(START_BYTE(ili922x_id, START_RS_REG,
START_RW_READ));
tbuf[len++] = set_tx_byte(0);
tbuf[len] = set_tx_byte(0);
xfer_regvalue.cs_change = 1;
xfer_regvalue.len = 3;
xfer_regvalue.tx_buf = &tbuf[send_bytes];
xfer_regvalue.rx_buf = &rbuf[send_bytes];
CHECK_FREQ_REG(spi, &xfer_regvalue);
spi_message_add_tail(&xfer_regvalue, &msg);
ret = spi_sync(spi, &msg);
if (ret < 0) {
dev_dbg(&spi->dev, "Error sending SPI message 0x%x", ret);
return ret;
}
*rx = (rbuf[1 + send_bytes] << 8) + rbuf[2 + send_bytes];
return 0;
}
/**
* ili922x_write - write a controller register
* @spi: struct spi_device *
* @reg: offset of the register to be written
* @value: value to be written
*/
static int ili922x_write(struct spi_device *spi, u8 reg, u16 value)
{
struct spi_message msg;
struct spi_transfer xfer_regindex, xfer_regvalue;
unsigned char tbuf[CMD_BUFSIZE];
unsigned char rbuf[CMD_BUFSIZE];
int ret, len = 0;
memset(&xfer_regindex, 0, sizeof(struct spi_transfer));
memset(&xfer_regvalue, 0, sizeof(struct spi_transfer));
spi_message_init(&msg);
xfer_regindex.tx_buf = tbuf;
xfer_regindex.rx_buf = rbuf;
xfer_regindex.cs_change = 1;
CHECK_FREQ_REG(spi, &xfer_regindex);
tbuf[0] = set_tx_byte(START_BYTE(ili922x_id, START_RS_INDEX,
START_RW_WRITE));
tbuf[1] = set_tx_byte(0);
tbuf[2] = set_tx_byte(reg);
xfer_regindex.bits_per_word = 8;
xfer_regindex.len = 3;
spi_message_add_tail(&xfer_regindex, &msg);
ret = spi_sync(spi, &msg);
spi_message_init(&msg);
len = 0;
tbuf[0] = set_tx_byte(START_BYTE(ili922x_id, START_RS_REG,
START_RW_WRITE));
tbuf[1] = set_tx_byte((value & 0xFF00) >> 8);
tbuf[2] = set_tx_byte(value & 0x00FF);
xfer_regvalue.cs_change = 1;
xfer_regvalue.len = 3;
xfer_regvalue.tx_buf = tbuf;
xfer_regvalue.rx_buf = rbuf;
CHECK_FREQ_REG(spi, &xfer_regvalue);
spi_message_add_tail(&xfer_regvalue, &msg);
ret = spi_sync(spi, &msg);
if (ret < 0) {
dev_err(&spi->dev, "Error sending SPI message 0x%x", ret);
return ret;
}
return 0;
}
#ifdef DEBUG
/**
* ili922x_reg_dump - dump all registers
*/
static void ili922x_reg_dump(struct spi_device *spi)
{
u8 reg;
u16 rx;
dev_dbg(&spi->dev, "ILI922x configuration registers:\n");
for (reg = REG_START_OSCILLATION;
reg <= REG_OTP_PROGRAMMING_ID_KEY; reg++) {
ili922x_read(spi, reg, &rx);
dev_dbg(&spi->dev, "reg @ 0x%02X: 0x%04X\n", reg, rx);
}
}
#else
static inline void ili922x_reg_dump(struct spi_device *spi) {}
#endif
/**
* set_write_to_gram_reg - initialize the display to write the GRAM
* @spi: spi device
*/
static void set_write_to_gram_reg(struct spi_device *spi)
{
struct spi_message msg;
struct spi_transfer xfer;
unsigned char tbuf[CMD_BUFSIZE];
memset(&xfer, 0, sizeof(struct spi_transfer));
spi_message_init(&msg);
xfer.tx_buf = tbuf;
xfer.rx_buf = NULL;
xfer.cs_change = 1;
tbuf[0] = START_BYTE(ili922x_id, START_RS_INDEX, START_RW_WRITE);
tbuf[1] = 0;
tbuf[2] = REG_WRITE_DATA_TO_GRAM;
xfer.bits_per_word = 8;
xfer.len = 3;
spi_message_add_tail(&xfer, &msg);
spi_sync(spi, &msg);
}
/**
* ili922x_poweron - turn the display on
* @spi: spi device
*
* The sequence to turn on the display is taken from
* the datasheet and/or the example code provided by the
* manufacturer.
*/
static int ili922x_poweron(struct spi_device *spi)
{
int ret;
/* Power on */
ret = ili922x_write(spi, REG_POWER_CONTROL_1, 0x0000);
usleep_range(10000, 10500);
ret += ili922x_write(spi, REG_POWER_CONTROL_2, 0x0000);
ret += ili922x_write(spi, REG_POWER_CONTROL_3, 0x0000);
msleep(40);
ret += ili922x_write(spi, REG_POWER_CONTROL_4, 0x0000);
msleep(40);
/* register 0x56 is not documented in the datasheet */
ret += ili922x_write(spi, 0x56, 0x080F);
ret += ili922x_write(spi, REG_POWER_CONTROL_1, 0x4240);
usleep_range(10000, 10500);
ret += ili922x_write(spi, REG_POWER_CONTROL_2, 0x0000);
ret += ili922x_write(spi, REG_POWER_CONTROL_3, 0x0014);
msleep(40);
ret += ili922x_write(spi, REG_POWER_CONTROL_4, 0x1319);
msleep(40);
return ret;
}
/**
* ili922x_poweroff - turn the display off
* @spi: spi device
*/
static int ili922x_poweroff(struct spi_device *spi)
{
int ret;
/* Power off */
ret = ili922x_write(spi, REG_POWER_CONTROL_1, 0x0000);
usleep_range(10000, 10500);
ret += ili922x_write(spi, REG_POWER_CONTROL_2, 0x0000);
ret += ili922x_write(spi, REG_POWER_CONTROL_3, 0x0000);
msleep(40);
ret += ili922x_write(spi, REG_POWER_CONTROL_4, 0x0000);
msleep(40);
return ret;
}
/**
* ili922x_display_init - initialize the display by setting
* the configuration registers
* @spi: spi device
*/
static void ili922x_display_init(struct spi_device *spi)
{
ili922x_write(spi, REG_START_OSCILLATION, 1);
usleep_range(10000, 10500);
ili922x_write(spi, REG_DRIVER_OUTPUT_CONTROL, 0x691B);
ili922x_write(spi, REG_LCD_AC_DRIVEING_CONTROL, 0x0700);
ili922x_write(spi, REG_ENTRY_MODE, 0x1030);
ili922x_write(spi, REG_COMPARE_1, 0x0000);
ili922x_write(spi, REG_COMPARE_2, 0x0000);
ili922x_write(spi, REG_DISPLAY_CONTROL_1, 0x0037);
ili922x_write(spi, REG_DISPLAY_CONTROL_2, 0x0202);
ili922x_write(spi, REG_DISPLAY_CONTROL_3, 0x0000);
ili922x_write(spi, REG_FRAME_CYCLE_CONTROL, 0x0000);
/* Set RGB interface */
ili922x_write(spi, REG_EXT_INTF_CONTROL, 0x0110);
ili922x_poweron(spi);
ili922x_write(spi, REG_GAMMA_CONTROL_1, 0x0302);
ili922x_write(spi, REG_GAMMA_CONTROL_2, 0x0407);
ili922x_write(spi, REG_GAMMA_CONTROL_3, 0x0304);
ili922x_write(spi, REG_GAMMA_CONTROL_4, 0x0203);
ili922x_write(spi, REG_GAMMA_CONTROL_5, 0x0706);
ili922x_write(spi, REG_GAMMA_CONTROL_6, 0x0407);
ili922x_write(spi, REG_GAMMA_CONTROL_7, 0x0706);
ili922x_write(spi, REG_GAMMA_CONTROL_8, 0x0000);
ili922x_write(spi, REG_GAMMA_CONTROL_9, 0x0C06);
ili922x_write(spi, REG_GAMMA_CONTROL_10, 0x0F00);
ili922x_write(spi, REG_RAM_ADDRESS_SET, 0x0000);
ili922x_write(spi, REG_GATE_SCAN_CONTROL, 0x0000);
ili922x_write(spi, REG_VERT_SCROLL_CONTROL, 0x0000);
ili922x_write(spi, REG_FIRST_SCREEN_DRIVE_POS, 0xDB00);
ili922x_write(spi, REG_SECOND_SCREEN_DRIVE_POS, 0xDB00);
ili922x_write(spi, REG_RAM_ADDR_POS_H, 0xAF00);
ili922x_write(spi, REG_RAM_ADDR_POS_V, 0xDB00);
ili922x_reg_dump(spi);
set_write_to_gram_reg(spi);
}
static int ili922x_lcd_power(struct ili922x *lcd, int power)
{
int ret = 0;
if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power))
ret = ili922x_poweron(lcd->spi);
else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power))
ret = ili922x_poweroff(lcd->spi);
if (!ret)
lcd->power = power;
return ret;
}
static int ili922x_set_power(struct lcd_device *ld, int power)
{
struct ili922x *ili = lcd_get_data(ld);
return ili922x_lcd_power(ili, power);
}
static int ili922x_get_power(struct lcd_device *ld)
{
struct ili922x *ili = lcd_get_data(ld);
return ili->power;
}
static struct lcd_ops ili922x_ops = {
.get_power = ili922x_get_power,
.set_power = ili922x_set_power,
};
static int ili922x_probe(struct spi_device *spi)
{
struct ili922x *ili;
struct lcd_device *lcd;
int ret;
u16 reg = 0;
ili = devm_kzalloc(&spi->dev, sizeof(*ili), GFP_KERNEL);
if (!ili)
return -ENOMEM;
ili->spi = spi;
spi_set_drvdata(spi, ili);
/* check if the device is connected */
ret = ili922x_read(spi, REG_DRIVER_CODE_READ, &reg);
if (ret || ((reg & ILITEK_DEVICE_ID_MASK) != ILITEK_DEVICE_ID)) {
dev_err(&spi->dev,
"no LCD found: Chip ID 0x%x, ret %d\n",
reg, ret);
return -ENODEV;
}
dev_info(&spi->dev, "ILI%x found, SPI freq %d, mode %d\n",
reg, spi->max_speed_hz, spi->mode);
ret = ili922x_read_status(spi, &reg);
if (ret) {
dev_err(&spi->dev, "reading RS failed...\n");
return ret;
}
dev_dbg(&spi->dev, "status: 0x%x\n", reg);
ili922x_display_init(spi);
ili->power = FB_BLANK_POWERDOWN;
lcd = devm_lcd_device_register(&spi->dev, "ili922xlcd", &spi->dev, ili,
&ili922x_ops);
if (IS_ERR(lcd)) {
dev_err(&spi->dev, "cannot register LCD\n");
return PTR_ERR(lcd);
}
ili->ld = lcd;
spi_set_drvdata(spi, ili);
ili922x_lcd_power(ili, FB_BLANK_UNBLANK);
return 0;
}
static int ili922x_remove(struct spi_device *spi)
{
ili922x_poweroff(spi);
return 0;
}
static struct spi_driver ili922x_driver = {
.driver = {
.name = "ili922x",
.owner = THIS_MODULE,
},
.probe = ili922x_probe,
.remove = ili922x_remove,
};
module_spi_driver(ili922x_driver);
MODULE_AUTHOR("Stefano Babic <sbabic@denx.de>");
MODULE_DESCRIPTION("ILI9221/9222 LCD driver");
MODULE_LICENSE("GPL");
MODULE_PARM_DESC(ili922x_id, "set controller identifier (default=1)");
MODULE_PARM_DESC(tx_invert, "invert bytes before sending");

View file

@ -0,0 +1,303 @@
/* drivers/video/backlight/ili9320.c
*
* ILI9320 LCD controller driver core.
*
* Copyright 2007 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* 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/delay.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <video/ili9320.h>
#include "ili9320.h"
static inline int ili9320_write_spi(struct ili9320 *ili,
unsigned int reg,
unsigned int value)
{
struct ili9320_spi *spi = &ili->access.spi;
unsigned char *addr = spi->buffer_addr;
unsigned char *data = spi->buffer_data;
/* spi message consits of:
* first byte: ID and operation
*/
addr[0] = spi->id | ILI9320_SPI_INDEX | ILI9320_SPI_WRITE;
addr[1] = reg >> 8;
addr[2] = reg;
/* second message is the data to transfer */
data[0] = spi->id | ILI9320_SPI_DATA | ILI9320_SPI_WRITE;
data[1] = value >> 8;
data[2] = value;
return spi_sync(spi->dev, &spi->message);
}
int ili9320_write(struct ili9320 *ili, unsigned int reg, unsigned int value)
{
dev_dbg(ili->dev, "write: reg=%02x, val=%04x\n", reg, value);
return ili->write(ili, reg, value);
}
EXPORT_SYMBOL_GPL(ili9320_write);
int ili9320_write_regs(struct ili9320 *ili,
const struct ili9320_reg *values,
int nr_values)
{
int index;
int ret;
for (index = 0; index < nr_values; index++, values++) {
ret = ili9320_write(ili, values->address, values->value);
if (ret != 0)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(ili9320_write_regs);
static void ili9320_reset(struct ili9320 *lcd)
{
struct ili9320_platdata *cfg = lcd->platdata;
cfg->reset(1);
mdelay(50);
cfg->reset(0);
mdelay(50);
cfg->reset(1);
mdelay(100);
}
static inline int ili9320_init_chip(struct ili9320 *lcd)
{
int ret;
ili9320_reset(lcd);
ret = lcd->client->init(lcd, lcd->platdata);
if (ret != 0) {
dev_err(lcd->dev, "failed to initialise display\n");
return ret;
}
lcd->initialised = 1;
return 0;
}
static inline int ili9320_power_on(struct ili9320 *lcd)
{
if (!lcd->initialised)
ili9320_init_chip(lcd);
lcd->display1 |= (ILI9320_DISPLAY1_D(3) | ILI9320_DISPLAY1_BASEE);
ili9320_write(lcd, ILI9320_DISPLAY1, lcd->display1);
return 0;
}
static inline int ili9320_power_off(struct ili9320 *lcd)
{
lcd->display1 &= ~(ILI9320_DISPLAY1_D(3) | ILI9320_DISPLAY1_BASEE);
ili9320_write(lcd, ILI9320_DISPLAY1, lcd->display1);
return 0;
}
#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
static int ili9320_power(struct ili9320 *lcd, int power)
{
int ret = 0;
dev_dbg(lcd->dev, "power %d => %d\n", lcd->power, power);
if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power))
ret = ili9320_power_on(lcd);
else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power))
ret = ili9320_power_off(lcd);
if (ret == 0)
lcd->power = power;
else
dev_warn(lcd->dev, "failed to set power mode %d\n", power);
return ret;
}
static inline struct ili9320 *to_our_lcd(struct lcd_device *lcd)
{
return lcd_get_data(lcd);
}
static int ili9320_set_power(struct lcd_device *ld, int power)
{
struct ili9320 *lcd = to_our_lcd(ld);
return ili9320_power(lcd, power);
}
static int ili9320_get_power(struct lcd_device *ld)
{
struct ili9320 *lcd = to_our_lcd(ld);
return lcd->power;
}
static struct lcd_ops ili9320_ops = {
.get_power = ili9320_get_power,
.set_power = ili9320_set_power,
};
static void ili9320_setup_spi(struct ili9320 *ili,
struct spi_device *dev)
{
struct ili9320_spi *spi = &ili->access.spi;
ili->write = ili9320_write_spi;
spi->dev = dev;
/* fill the two messages we are going to use to send the data
* with, the first the address followed by the data. The datasheet
* says they should be done as two distinct cycles of the SPI CS line.
*/
spi->xfer[0].tx_buf = spi->buffer_addr;
spi->xfer[1].tx_buf = spi->buffer_data;
spi->xfer[0].len = 3;
spi->xfer[1].len = 3;
spi->xfer[0].bits_per_word = 8;
spi->xfer[1].bits_per_word = 8;
spi->xfer[0].cs_change = 1;
spi_message_init(&spi->message);
spi_message_add_tail(&spi->xfer[0], &spi->message);
spi_message_add_tail(&spi->xfer[1], &spi->message);
}
int ili9320_probe_spi(struct spi_device *spi,
struct ili9320_client *client)
{
struct ili9320_platdata *cfg = dev_get_platdata(&spi->dev);
struct device *dev = &spi->dev;
struct ili9320 *ili;
struct lcd_device *lcd;
int ret = 0;
/* verify we where given some information */
if (cfg == NULL) {
dev_err(dev, "no platform data supplied\n");
return -EINVAL;
}
if (cfg->hsize <= 0 || cfg->vsize <= 0 || cfg->reset == NULL) {
dev_err(dev, "invalid platform data supplied\n");
return -EINVAL;
}
/* allocate and initialse our state */
ili = devm_kzalloc(&spi->dev, sizeof(struct ili9320), GFP_KERNEL);
if (ili == NULL)
return -ENOMEM;
ili->access.spi.id = ILI9320_SPI_IDCODE | ILI9320_SPI_ID(1);
ili->dev = dev;
ili->client = client;
ili->power = FB_BLANK_POWERDOWN;
ili->platdata = cfg;
spi_set_drvdata(spi, ili);
ili9320_setup_spi(ili, spi);
lcd = devm_lcd_device_register(&spi->dev, "ili9320", dev, ili,
&ili9320_ops);
if (IS_ERR(lcd)) {
dev_err(dev, "failed to register lcd device\n");
return PTR_ERR(lcd);
}
ili->lcd = lcd;
dev_info(dev, "initialising %s\n", client->name);
ret = ili9320_power(ili, FB_BLANK_UNBLANK);
if (ret != 0) {
dev_err(dev, "failed to set lcd power state\n");
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(ili9320_probe_spi);
int ili9320_remove(struct ili9320 *ili)
{
ili9320_power(ili, FB_BLANK_POWERDOWN);
return 0;
}
EXPORT_SYMBOL_GPL(ili9320_remove);
#ifdef CONFIG_PM_SLEEP
int ili9320_suspend(struct ili9320 *lcd)
{
int ret;
ret = ili9320_power(lcd, FB_BLANK_POWERDOWN);
if (lcd->platdata->suspend == ILI9320_SUSPEND_DEEP) {
ili9320_write(lcd, ILI9320_POWER1, lcd->power1 |
ILI9320_POWER1_SLP |
ILI9320_POWER1_DSTB);
lcd->initialised = 0;
}
return ret;
}
EXPORT_SYMBOL_GPL(ili9320_suspend);
int ili9320_resume(struct ili9320 *lcd)
{
dev_info(lcd->dev, "resuming from power state %d\n", lcd->power);
if (lcd->platdata->suspend == ILI9320_SUSPEND_DEEP)
ili9320_write(lcd, ILI9320_POWER1, 0x00);
return ili9320_power(lcd, FB_BLANK_UNBLANK);
}
EXPORT_SYMBOL_GPL(ili9320_resume);
#endif
/* Power down all displays on reboot, poweroff or halt */
void ili9320_shutdown(struct ili9320 *lcd)
{
ili9320_power(lcd, FB_BLANK_POWERDOWN);
}
EXPORT_SYMBOL_GPL(ili9320_shutdown);
MODULE_AUTHOR("Ben Dooks <ben-linux@fluff.org>");
MODULE_DESCRIPTION("ILI9320 LCD Driver");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,80 @@
/* drivers/video/backlight/ili9320.h
*
* ILI9320 LCD controller driver core.
*
* Copyright 2007 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>
*
* http://armlinux.simtec.co.uk/
*
* 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.
*/
/* Holder for register and value pairs. */
struct ili9320_reg {
unsigned short address;
unsigned short value;
};
struct ili9320;
struct ili9320_client {
const char *name;
int (*init)(struct ili9320 *ili, struct ili9320_platdata *cfg);
};
/* Device attached via an SPI bus. */
struct ili9320_spi {
struct spi_device *dev;
struct spi_message message;
struct spi_transfer xfer[2];
unsigned char id;
unsigned char buffer_addr[4];
unsigned char buffer_data[4];
};
/* ILI9320 device state. */
struct ili9320 {
union {
struct ili9320_spi spi; /* SPI attachged device. */
} access; /* Register access method. */
struct device *dev;
struct lcd_device *lcd; /* LCD device we created. */
struct ili9320_client *client;
struct ili9320_platdata *platdata;
int power; /* current power state. */
int initialised;
unsigned short display1;
unsigned short power1;
int (*write)(struct ili9320 *ili, unsigned int reg, unsigned int val);
};
/* ILI9320 register access routines */
extern int ili9320_write(struct ili9320 *ili,
unsigned int reg, unsigned int value);
extern int ili9320_write_regs(struct ili9320 *ili,
const struct ili9320_reg *values,
int nr_values);
/* Device probe */
extern int ili9320_probe_spi(struct spi_device *spi,
struct ili9320_client *cli);
extern int ili9320_remove(struct ili9320 *lcd);
extern void ili9320_shutdown(struct ili9320 *lcd);
/* PM */
extern int ili9320_suspend(struct ili9320 *lcd);
extern int ili9320_resume(struct ili9320 *lcd);

View file

@ -0,0 +1,83 @@
/*
* 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.
*
* iPAQ microcontroller backlight support
* Author : Linus Walleij <linus.walleij@linaro.org>
*/
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/mfd/ipaq-micro.h>
#include <linux/module.h>
#include <linux/platform_device.h>
static int micro_bl_update_status(struct backlight_device *bd)
{
struct ipaq_micro *micro = dev_get_drvdata(&bd->dev);
int intensity = bd->props.brightness;
struct ipaq_micro_msg msg = {
.id = MSG_BACKLIGHT,
.tx_len = 3,
};
if (bd->props.power != FB_BLANK_UNBLANK)
intensity = 0;
if (bd->props.state & (BL_CORE_FBBLANK | BL_CORE_SUSPENDED))
intensity = 0;
/*
* Message format:
* Byte 0: backlight instance (usually 1)
* Byte 1: on/off
* Byte 2: intensity, 0-255
*/
msg.tx_data[0] = 0x01;
msg.tx_data[1] = intensity > 0 ? 1 : 0;
msg.tx_data[2] = intensity;
return ipaq_micro_tx_msg_sync(micro, &msg);
}
static const struct backlight_ops micro_bl_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = micro_bl_update_status,
};
static struct backlight_properties micro_bl_props = {
.type = BACKLIGHT_RAW,
.max_brightness = 255,
.power = FB_BLANK_UNBLANK,
.brightness = 64,
};
static int micro_backlight_probe(struct platform_device *pdev)
{
struct backlight_device *bd;
struct ipaq_micro *micro = dev_get_drvdata(pdev->dev.parent);
bd = devm_backlight_device_register(&pdev->dev, "ipaq-micro-backlight",
&pdev->dev, micro, &micro_bl_ops,
&micro_bl_props);
if (IS_ERR(bd))
return PTR_ERR(bd);
platform_set_drvdata(pdev, bd);
backlight_update_status(bd);
return 0;
}
static struct platform_driver micro_backlight_device_driver = {
.driver = {
.name = "ipaq-micro-backlight",
},
.probe = micro_backlight_probe,
};
module_platform_driver(micro_backlight_device_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("driver for iPAQ Atmel micro backlight");
MODULE_ALIAS("platform:ipaq-micro-backlight");

View file

@ -0,0 +1,154 @@
/*
*
* Backlight driver for HP Jornada 700 series (710/720/728)
* Copyright (C) 2006-2009 Kristoffer Ericson <kristoffer.ericson@gmail.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 or any later version as published by the Free Software Foundation.
*
*/
#include <linux/backlight.h>
#include <linux/device.h>
#include <linux/fb.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <mach/jornada720.h>
#include <mach/hardware.h>
#include <video/s1d13xxxfb.h>
#define BL_MAX_BRIGHT 255
#define BL_DEF_BRIGHT 25
static int jornada_bl_get_brightness(struct backlight_device *bd)
{
int ret;
/* check if backlight is on */
if (!(PPSR & PPC_LDD1))
return 0;
jornada_ssp_start();
/* cmd should return txdummy */
ret = jornada_ssp_byte(GETBRIGHTNESS);
if (jornada_ssp_byte(GETBRIGHTNESS) != TXDUMMY) {
dev_err(&bd->dev, "get brightness timeout\n");
jornada_ssp_end();
return -ETIMEDOUT;
}
/* exchange txdummy for value */
ret = jornada_ssp_byte(TXDUMMY);
jornada_ssp_end();
return BL_MAX_BRIGHT - ret;
}
static int jornada_bl_update_status(struct backlight_device *bd)
{
int ret = 0;
jornada_ssp_start();
/* If backlight is off then really turn it off */
if ((bd->props.power != FB_BLANK_UNBLANK) || (bd->props.fb_blank != FB_BLANK_UNBLANK)) {
ret = jornada_ssp_byte(BRIGHTNESSOFF);
if (ret != TXDUMMY) {
dev_info(&bd->dev, "brightness off timeout\n");
/* turn off backlight */
PPSR &= ~PPC_LDD1;
PPDR |= PPC_LDD1;
ret = -ETIMEDOUT;
}
} else /* turn on backlight */
PPSR |= PPC_LDD1;
/* send command to our mcu */
if (jornada_ssp_byte(SETBRIGHTNESS) != TXDUMMY) {
dev_info(&bd->dev, "failed to set brightness\n");
ret = -ETIMEDOUT;
goto out;
}
/*
* at this point we expect that the mcu has accepted
* our command and is waiting for our new value
* please note that maximum brightness is 255,
* but due to physical layout it is equal to 0, so we simply
* invert the value (MAX VALUE - NEW VALUE).
*/
if (jornada_ssp_byte(BL_MAX_BRIGHT - bd->props.brightness)
!= TXDUMMY) {
dev_err(&bd->dev, "set brightness failed\n");
ret = -ETIMEDOUT;
}
/*
* If infact we get an TXDUMMY as output we are happy and dont
* make any further comments about it
*/
out:
jornada_ssp_end();
return ret;
}
static const struct backlight_ops jornada_bl_ops = {
.get_brightness = jornada_bl_get_brightness,
.update_status = jornada_bl_update_status,
.options = BL_CORE_SUSPENDRESUME,
};
static int jornada_bl_probe(struct platform_device *pdev)
{
struct backlight_properties props;
int ret;
struct backlight_device *bd;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = BL_MAX_BRIGHT;
bd = devm_backlight_device_register(&pdev->dev, S1D_DEVICENAME,
&pdev->dev, NULL, &jornada_bl_ops,
&props);
if (IS_ERR(bd)) {
ret = PTR_ERR(bd);
dev_err(&pdev->dev, "failed to register device, err=%x\n", ret);
return ret;
}
bd->props.power = FB_BLANK_UNBLANK;
bd->props.brightness = BL_DEF_BRIGHT;
/*
* note. make sure max brightness is set otherwise
* you will get seemingly non-related errors when
* trying to change brightness
*/
jornada_bl_update_status(bd);
platform_set_drvdata(pdev, bd);
dev_info(&pdev->dev, "HP Jornada 700 series backlight driver\n");
return 0;
}
static struct platform_driver jornada_bl_driver = {
.probe = jornada_bl_probe,
.driver = {
.name = "jornada_bl",
},
};
module_platform_driver(jornada_bl_driver);
MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson>");
MODULE_DESCRIPTION("HP Jornada 710/720/728 Backlight driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,131 @@
/*
*
* LCD driver for HP Jornada 700 series (710/720/728)
* Copyright (C) 2006-2009 Kristoffer Ericson <kristoffer.ericson@gmail.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 or any later version as published by the Free Software Foundation.
*
*/
#include <linux/device.h>
#include <linux/fb.h>
#include <linux/kernel.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <mach/jornada720.h>
#include <mach/hardware.h>
#include <video/s1d13xxxfb.h>
#define LCD_MAX_CONTRAST 0xff
#define LCD_DEF_CONTRAST 0x80
static int jornada_lcd_get_power(struct lcd_device *ld)
{
return PPSR & PPC_LDD2 ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
}
static int jornada_lcd_get_contrast(struct lcd_device *ld)
{
int ret;
if (jornada_lcd_get_power(ld) != FB_BLANK_UNBLANK)
return 0;
jornada_ssp_start();
if (jornada_ssp_byte(GETCONTRAST) == TXDUMMY) {
ret = jornada_ssp_byte(TXDUMMY);
goto success;
}
dev_err(&ld->dev, "failed to set contrast\n");
ret = -ETIMEDOUT;
success:
jornada_ssp_end();
return ret;
}
static int jornada_lcd_set_contrast(struct lcd_device *ld, int value)
{
int ret = 0;
jornada_ssp_start();
/* start by sending our set contrast cmd to mcu */
if (jornada_ssp_byte(SETCONTRAST) == TXDUMMY) {
/* if successful push the new value */
if (jornada_ssp_byte(value) == TXDUMMY)
goto success;
}
dev_err(&ld->dev, "failed to set contrast\n");
ret = -ETIMEDOUT;
success:
jornada_ssp_end();
return ret;
}
static int jornada_lcd_set_power(struct lcd_device *ld, int power)
{
if (power != FB_BLANK_UNBLANK) {
PPSR &= ~PPC_LDD2;
PPDR |= PPC_LDD2;
} else {
PPSR |= PPC_LDD2;
}
return 0;
}
static struct lcd_ops jornada_lcd_props = {
.get_contrast = jornada_lcd_get_contrast,
.set_contrast = jornada_lcd_set_contrast,
.get_power = jornada_lcd_get_power,
.set_power = jornada_lcd_set_power,
};
static int jornada_lcd_probe(struct platform_device *pdev)
{
struct lcd_device *lcd_device;
int ret;
lcd_device = devm_lcd_device_register(&pdev->dev, S1D_DEVICENAME,
&pdev->dev, NULL, &jornada_lcd_props);
if (IS_ERR(lcd_device)) {
ret = PTR_ERR(lcd_device);
dev_err(&pdev->dev, "failed to register device\n");
return ret;
}
platform_set_drvdata(pdev, lcd_device);
/* lets set our default values */
jornada_lcd_set_contrast(lcd_device, LCD_DEF_CONTRAST);
jornada_lcd_set_power(lcd_device, FB_BLANK_UNBLANK);
/* give it some time to startup */
msleep(100);
return 0;
}
static struct platform_driver jornada_lcd_driver = {
.probe = jornada_lcd_probe,
.driver = {
.name = "jornada_lcd",
},
};
module_platform_driver(jornada_lcd_driver);
MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson@gmail.com>");
MODULE_DESCRIPTION("HP Jornada 710/720/728 LCD driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,197 @@
/*
* Backlight Driver for the KB3886 Backlight
*
* Copyright (c) 2007-2008 Claudio Nieder
*
* Based on corgi_bl.c by Richard Purdie and kb3886 driver by Robert Woerle
*
* 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/platform_device.h>
#include <linux/mutex.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#define KB3886_PARENT 0x64
#define KB3886_IO 0x60
#define KB3886_ADC_DAC_PWM 0xC4
#define KB3886_PWM0_WRITE 0x81
#define KB3886_PWM0_READ 0x41
static DEFINE_MUTEX(bl_mutex);
static void kb3886_bl_set_intensity(int intensity)
{
mutex_lock(&bl_mutex);
intensity = intensity&0xff;
outb(KB3886_ADC_DAC_PWM, KB3886_PARENT);
usleep_range(10000, 11000);
outb(KB3886_PWM0_WRITE, KB3886_IO);
usleep_range(10000, 11000);
outb(intensity, KB3886_IO);
mutex_unlock(&bl_mutex);
}
struct kb3886bl_machinfo {
int max_intensity;
int default_intensity;
int limit_mask;
void (*set_bl_intensity)(int intensity);
};
static struct kb3886bl_machinfo kb3886_bl_machinfo = {
.max_intensity = 0xff,
.default_intensity = 0xa0,
.limit_mask = 0x7f,
.set_bl_intensity = kb3886_bl_set_intensity,
};
static struct platform_device kb3886bl_device = {
.name = "kb3886-bl",
.dev = {
.platform_data = &kb3886_bl_machinfo,
},
.id = -1,
};
static struct platform_device *devices[] __initdata = {
&kb3886bl_device,
};
/*
* Back to driver
*/
static int kb3886bl_intensity;
static struct backlight_device *kb3886_backlight_device;
static struct kb3886bl_machinfo *bl_machinfo;
static unsigned long kb3886bl_flags;
#define KB3886BL_SUSPENDED 0x01
static struct dmi_system_id kb3886bl_device_table[] __initdata = {
{
.ident = "Sahara Touch-iT",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "SDV"),
DMI_MATCH(DMI_PRODUCT_NAME, "iTouch T201"),
},
},
{ }
};
static int kb3886bl_send_intensity(struct backlight_device *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 (kb3886bl_flags & KB3886BL_SUSPENDED)
intensity = 0;
bl_machinfo->set_bl_intensity(intensity);
kb3886bl_intensity = intensity;
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int kb3886bl_suspend(struct device *dev)
{
struct backlight_device *bd = dev_get_drvdata(dev);
kb3886bl_flags |= KB3886BL_SUSPENDED;
backlight_update_status(bd);
return 0;
}
static int kb3886bl_resume(struct device *dev)
{
struct backlight_device *bd = dev_get_drvdata(dev);
kb3886bl_flags &= ~KB3886BL_SUSPENDED;
backlight_update_status(bd);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(kb3886bl_pm_ops, kb3886bl_suspend, kb3886bl_resume);
static int kb3886bl_get_intensity(struct backlight_device *bd)
{
return kb3886bl_intensity;
}
static const struct backlight_ops kb3886bl_ops = {
.get_brightness = kb3886bl_get_intensity,
.update_status = kb3886bl_send_intensity,
};
static int kb3886bl_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct kb3886bl_machinfo *machinfo = dev_get_platdata(&pdev->dev);
bl_machinfo = machinfo;
if (!machinfo->limit_mask)
machinfo->limit_mask = -1;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = machinfo->max_intensity;
kb3886_backlight_device = devm_backlight_device_register(&pdev->dev,
"kb3886-bl", &pdev->dev,
NULL, &kb3886bl_ops,
&props);
if (IS_ERR(kb3886_backlight_device))
return PTR_ERR(kb3886_backlight_device);
platform_set_drvdata(pdev, kb3886_backlight_device);
kb3886_backlight_device->props.power = FB_BLANK_UNBLANK;
kb3886_backlight_device->props.brightness = machinfo->default_intensity;
backlight_update_status(kb3886_backlight_device);
return 0;
}
static struct platform_driver kb3886bl_driver = {
.probe = kb3886bl_probe,
.driver = {
.name = "kb3886-bl",
.pm = &kb3886bl_pm_ops,
},
};
static int __init kb3886_init(void)
{
if (!dmi_check_system(kb3886bl_device_table))
return -ENODEV;
platform_add_devices(devices, ARRAY_SIZE(devices));
return platform_driver_register(&kb3886bl_driver);
}
static void __exit kb3886_exit(void)
{
platform_driver_unregister(&kb3886bl_driver);
}
module_init(kb3886_init);
module_exit(kb3886_exit);
MODULE_AUTHOR("Claudio Nieder <private@claudio.ch>");
MODULE_DESCRIPTION("Tabletkiosk Sahara Touch-iT Backlight Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("dmi:*:svnSDV:pniTouchT201:*");

View file

@ -0,0 +1,269 @@
/*
* l4f00242t03.c -- support for Epson L4F00242T03 LCD
*
* Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved.
*
* Copyright (c) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
* Inspired by Marek Vasut work in l4f00242t03.c
*
* 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.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/lcd.h>
#include <linux/slab.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <linux/spi/l4f00242t03.h>
struct l4f00242t03_priv {
struct spi_device *spi;
struct lcd_device *ld;
int lcd_state;
struct regulator *io_reg;
struct regulator *core_reg;
};
static void l4f00242t03_reset(unsigned int gpio)
{
pr_debug("l4f00242t03_reset.\n");
gpio_set_value(gpio, 1);
mdelay(100);
gpio_set_value(gpio, 0);
mdelay(10); /* tRES >= 100us */
gpio_set_value(gpio, 1);
mdelay(20);
}
#define param(x) ((x) | 0x100)
static void l4f00242t03_lcd_init(struct spi_device *spi)
{
struct l4f00242t03_pdata *pdata = dev_get_platdata(&spi->dev);
struct l4f00242t03_priv *priv = spi_get_drvdata(spi);
const u16 cmd[] = { 0x36, param(0), 0x3A, param(0x60) };
int ret;
dev_dbg(&spi->dev, "initializing LCD\n");
ret = regulator_set_voltage(priv->io_reg, 1800000, 1800000);
if (ret) {
dev_err(&spi->dev, "failed to set the IO regulator voltage.\n");
return;
}
ret = regulator_enable(priv->io_reg);
if (ret) {
dev_err(&spi->dev, "failed to enable the IO regulator.\n");
return;
}
ret = regulator_set_voltage(priv->core_reg, 2800000, 2800000);
if (ret) {
dev_err(&spi->dev, "failed to set the core regulator voltage.\n");
regulator_disable(priv->io_reg);
return;
}
ret = regulator_enable(priv->core_reg);
if (ret) {
dev_err(&spi->dev, "failed to enable the core regulator.\n");
regulator_disable(priv->io_reg);
return;
}
l4f00242t03_reset(pdata->reset_gpio);
gpio_set_value(pdata->data_enable_gpio, 1);
msleep(60);
spi_write(spi, (const u8 *)cmd, ARRAY_SIZE(cmd) * sizeof(u16));
}
static void l4f00242t03_lcd_powerdown(struct spi_device *spi)
{
struct l4f00242t03_pdata *pdata = dev_get_platdata(&spi->dev);
struct l4f00242t03_priv *priv = spi_get_drvdata(spi);
dev_dbg(&spi->dev, "Powering down LCD\n");
gpio_set_value(pdata->data_enable_gpio, 0);
regulator_disable(priv->io_reg);
regulator_disable(priv->core_reg);
}
static int l4f00242t03_lcd_power_get(struct lcd_device *ld)
{
struct l4f00242t03_priv *priv = lcd_get_data(ld);
return priv->lcd_state;
}
static int l4f00242t03_lcd_power_set(struct lcd_device *ld, int power)
{
struct l4f00242t03_priv *priv = lcd_get_data(ld);
struct spi_device *spi = priv->spi;
const u16 slpout = 0x11;
const u16 dison = 0x29;
const u16 slpin = 0x10;
const u16 disoff = 0x28;
if (power <= FB_BLANK_NORMAL) {
if (priv->lcd_state <= FB_BLANK_NORMAL) {
/* Do nothing, the LCD is running */
} else if (priv->lcd_state < FB_BLANK_POWERDOWN) {
dev_dbg(&spi->dev, "Resuming LCD\n");
spi_write(spi, (const u8 *)&slpout, sizeof(u16));
msleep(60);
spi_write(spi, (const u8 *)&dison, sizeof(u16));
} else {
/* priv->lcd_state == FB_BLANK_POWERDOWN */
l4f00242t03_lcd_init(spi);
priv->lcd_state = FB_BLANK_VSYNC_SUSPEND;
l4f00242t03_lcd_power_set(priv->ld, power);
}
} else if (power < FB_BLANK_POWERDOWN) {
if (priv->lcd_state <= FB_BLANK_NORMAL) {
/* Send the display in standby */
dev_dbg(&spi->dev, "Standby the LCD\n");
spi_write(spi, (const u8 *)&disoff, sizeof(u16));
msleep(60);
spi_write(spi, (const u8 *)&slpin, sizeof(u16));
} else if (priv->lcd_state < FB_BLANK_POWERDOWN) {
/* Do nothing, the LCD is already in standby */
} else {
/* priv->lcd_state == FB_BLANK_POWERDOWN */
l4f00242t03_lcd_init(spi);
priv->lcd_state = FB_BLANK_UNBLANK;
l4f00242t03_lcd_power_set(ld, power);
}
} else {
/* power == FB_BLANK_POWERDOWN */
if (priv->lcd_state != FB_BLANK_POWERDOWN) {
/* Clear the screen before shutting down */
spi_write(spi, (const u8 *)&disoff, sizeof(u16));
msleep(60);
l4f00242t03_lcd_powerdown(spi);
}
}
priv->lcd_state = power;
return 0;
}
static struct lcd_ops l4f_ops = {
.set_power = l4f00242t03_lcd_power_set,
.get_power = l4f00242t03_lcd_power_get,
};
static int l4f00242t03_probe(struct spi_device *spi)
{
struct l4f00242t03_priv *priv;
struct l4f00242t03_pdata *pdata = dev_get_platdata(&spi->dev);
int ret;
if (pdata == NULL) {
dev_err(&spi->dev, "Uninitialized platform data.\n");
return -EINVAL;
}
priv = devm_kzalloc(&spi->dev, sizeof(struct l4f00242t03_priv),
GFP_KERNEL);
if (priv == NULL)
return -ENOMEM;
spi_set_drvdata(spi, priv);
spi->bits_per_word = 9;
spi_setup(spi);
priv->spi = spi;
ret = devm_gpio_request_one(&spi->dev, pdata->reset_gpio,
GPIOF_OUT_INIT_HIGH, "lcd l4f00242t03 reset");
if (ret) {
dev_err(&spi->dev,
"Unable to get the lcd l4f00242t03 reset gpio.\n");
return ret;
}
ret = devm_gpio_request_one(&spi->dev, pdata->data_enable_gpio,
GPIOF_OUT_INIT_LOW, "lcd l4f00242t03 data enable");
if (ret) {
dev_err(&spi->dev,
"Unable to get the lcd l4f00242t03 data en gpio.\n");
return ret;
}
priv->io_reg = devm_regulator_get(&spi->dev, "vdd");
if (IS_ERR(priv->io_reg)) {
dev_err(&spi->dev, "%s: Unable to get the IO regulator\n",
__func__);
return PTR_ERR(priv->io_reg);
}
priv->core_reg = devm_regulator_get(&spi->dev, "vcore");
if (IS_ERR(priv->core_reg)) {
dev_err(&spi->dev, "%s: Unable to get the core regulator\n",
__func__);
return PTR_ERR(priv->core_reg);
}
priv->ld = devm_lcd_device_register(&spi->dev, "l4f00242t03", &spi->dev,
priv, &l4f_ops);
if (IS_ERR(priv->ld))
return PTR_ERR(priv->ld);
/* Init the LCD */
l4f00242t03_lcd_init(spi);
priv->lcd_state = FB_BLANK_VSYNC_SUSPEND;
l4f00242t03_lcd_power_set(priv->ld, FB_BLANK_UNBLANK);
dev_info(&spi->dev, "Epson l4f00242t03 lcd probed.\n");
return 0;
}
static int l4f00242t03_remove(struct spi_device *spi)
{
struct l4f00242t03_priv *priv = spi_get_drvdata(spi);
l4f00242t03_lcd_power_set(priv->ld, FB_BLANK_POWERDOWN);
return 0;
}
static void l4f00242t03_shutdown(struct spi_device *spi)
{
struct l4f00242t03_priv *priv = spi_get_drvdata(spi);
if (priv)
l4f00242t03_lcd_power_set(priv->ld, FB_BLANK_POWERDOWN);
}
static struct spi_driver l4f00242t03_driver = {
.driver = {
.name = "l4f00242t03",
.owner = THIS_MODULE,
},
.probe = l4f00242t03_probe,
.remove = l4f00242t03_remove,
.shutdown = l4f00242t03_shutdown,
};
module_spi_driver(l4f00242t03_driver);
MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
MODULE_DESCRIPTION("EPSON L4F00242T03 LCD");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,364 @@
/*
* LCD Lowlevel Control Abstraction
*
* Copyright (C) 2003,2004 Hewlett-Packard Company
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/lcd.h>
#include <linux/notifier.h>
#include <linux/ctype.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/slab.h>
#if defined(CONFIG_FB) || (defined(CONFIG_FB_MODULE) && \
defined(CONFIG_LCD_CLASS_DEVICE_MODULE))
/* This callback gets called when something important happens inside a
* framebuffer driver. We're looking if that important event is blanking,
* and if it is, we're switching lcd power as well ...
*/
static int fb_notifier_callback(struct notifier_block *self,
unsigned long event, void *data)
{
struct lcd_device *ld;
struct fb_event *evdata = data;
/* If we aren't interested in this event, skip it immediately ... */
switch (event) {
case FB_EVENT_BLANK:
case FB_EVENT_MODE_CHANGE:
case FB_EVENT_MODE_CHANGE_ALL:
case FB_EARLY_EVENT_BLANK:
case FB_R_EARLY_EVENT_BLANK:
break;
default:
return 0;
}
ld = container_of(self, struct lcd_device, fb_notif);
if (!ld->ops)
return 0;
mutex_lock(&ld->ops_lock);
if (!ld->ops->check_fb || ld->ops->check_fb(ld, evdata->info)) {
if (event == FB_EVENT_BLANK) {
if (ld->ops->set_power)
ld->ops->set_power(ld, *(int *)evdata->data);
} else if (event == FB_EARLY_EVENT_BLANK) {
if (ld->ops->early_set_power)
ld->ops->early_set_power(ld,
*(int *)evdata->data);
} else if (event == FB_R_EARLY_EVENT_BLANK) {
if (ld->ops->r_early_set_power)
ld->ops->r_early_set_power(ld,
*(int *)evdata->data);
} else {
if (ld->ops->set_mode)
ld->ops->set_mode(ld, evdata->data);
}
}
mutex_unlock(&ld->ops_lock);
return 0;
}
static int lcd_register_fb(struct lcd_device *ld)
{
memset(&ld->fb_notif, 0, sizeof(ld->fb_notif));
ld->fb_notif.notifier_call = fb_notifier_callback;
return fb_register_client(&ld->fb_notif);
}
static void lcd_unregister_fb(struct lcd_device *ld)
{
fb_unregister_client(&ld->fb_notif);
}
#else
static int lcd_register_fb(struct lcd_device *ld)
{
return 0;
}
static inline void lcd_unregister_fb(struct lcd_device *ld)
{
}
#endif /* CONFIG_FB */
static ssize_t lcd_power_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
int rc;
struct lcd_device *ld = to_lcd_device(dev);
mutex_lock(&ld->ops_lock);
if (ld->ops && ld->ops->get_power)
rc = sprintf(buf, "%d\n", ld->ops->get_power(ld));
else
rc = -ENXIO;
mutex_unlock(&ld->ops_lock);
return rc;
}
static ssize_t lcd_power_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int rc;
struct lcd_device *ld = to_lcd_device(dev);
unsigned long power;
rc = kstrtoul(buf, 0, &power);
if (rc)
return rc;
rc = -ENXIO;
mutex_lock(&ld->ops_lock);
if (ld->ops && ld->ops->set_power) {
pr_debug("set power to %lu\n", power);
ld->ops->set_power(ld, power);
rc = count;
}
mutex_unlock(&ld->ops_lock);
return rc;
}
static DEVICE_ATTR_RW(lcd_power);
static ssize_t contrast_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
int rc = -ENXIO;
struct lcd_device *ld = to_lcd_device(dev);
mutex_lock(&ld->ops_lock);
if (ld->ops && ld->ops->get_contrast)
rc = sprintf(buf, "%d\n", ld->ops->get_contrast(ld));
mutex_unlock(&ld->ops_lock);
return rc;
}
static ssize_t contrast_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
int rc;
struct lcd_device *ld = to_lcd_device(dev);
unsigned long contrast;
rc = kstrtoul(buf, 0, &contrast);
if (rc)
return rc;
rc = -ENXIO;
mutex_lock(&ld->ops_lock);
if (ld->ops && ld->ops->set_contrast) {
pr_debug("set contrast to %lu\n", contrast);
ld->ops->set_contrast(ld, contrast);
rc = count;
}
mutex_unlock(&ld->ops_lock);
return rc;
}
static DEVICE_ATTR_RW(contrast);
static ssize_t max_contrast_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lcd_device *ld = to_lcd_device(dev);
return sprintf(buf, "%d\n", ld->props.max_contrast);
}
static DEVICE_ATTR_RO(max_contrast);
static struct class *lcd_class;
static void lcd_device_release(struct device *dev)
{
struct lcd_device *ld = to_lcd_device(dev);
kfree(ld);
}
static struct attribute *lcd_device_attrs[] = {
&dev_attr_lcd_power.attr,
&dev_attr_contrast.attr,
&dev_attr_max_contrast.attr,
NULL,
};
ATTRIBUTE_GROUPS(lcd_device);
/**
* lcd_device_register - register a new object of lcd_device class.
* @name: the name of the new object(must be the same as the name of the
* respective framebuffer device).
* @devdata: an optional pointer to be stored in the device. The
* methods may retrieve it by using lcd_get_data(ld).
* @ops: the lcd operations structure.
*
* Creates and registers a new lcd device. Returns either an ERR_PTR()
* or a pointer to the newly allocated device.
*/
struct lcd_device *lcd_device_register(const char *name, struct device *parent,
void *devdata, struct lcd_ops *ops)
{
struct lcd_device *new_ld;
int rc;
pr_debug("lcd_device_register: name=%s\n", name);
new_ld = kzalloc(sizeof(struct lcd_device), GFP_KERNEL);
if (!new_ld)
return ERR_PTR(-ENOMEM);
mutex_init(&new_ld->ops_lock);
mutex_init(&new_ld->update_lock);
new_ld->dev.class = lcd_class;
new_ld->dev.parent = parent;
new_ld->dev.release = lcd_device_release;
dev_set_name(&new_ld->dev, "%s", name);
dev_set_drvdata(&new_ld->dev, devdata);
rc = device_register(&new_ld->dev);
if (rc) {
put_device(&new_ld->dev);
return ERR_PTR(rc);
}
rc = lcd_register_fb(new_ld);
if (rc) {
device_unregister(&new_ld->dev);
return ERR_PTR(rc);
}
new_ld->ops = ops;
return new_ld;
}
EXPORT_SYMBOL(lcd_device_register);
/**
* lcd_device_unregister - unregisters a object of lcd_device class.
* @ld: the lcd device object to be unregistered and freed.
*
* Unregisters a previously registered via lcd_device_register object.
*/
void lcd_device_unregister(struct lcd_device *ld)
{
if (!ld)
return;
mutex_lock(&ld->ops_lock);
ld->ops = NULL;
mutex_unlock(&ld->ops_lock);
lcd_unregister_fb(ld);
device_unregister(&ld->dev);
}
EXPORT_SYMBOL(lcd_device_unregister);
static void devm_lcd_device_release(struct device *dev, void *res)
{
struct lcd_device *lcd = *(struct lcd_device **)res;
lcd_device_unregister(lcd);
}
static int devm_lcd_device_match(struct device *dev, void *res, void *data)
{
struct lcd_device **r = res;
return *r == data;
}
/**
* devm_lcd_device_register - resource managed lcd_device_register()
* @dev: the device to register
* @name: the name of the device
* @parent: a pointer to the parent device
* @devdata: an optional pointer to be stored for private driver use
* @ops: the lcd operations structure
*
* @return a struct lcd on success, or an ERR_PTR on error
*
* Managed lcd_device_register(). The lcd_device returned from this function
* are automatically freed on driver detach. See lcd_device_register()
* for more information.
*/
struct lcd_device *devm_lcd_device_register(struct device *dev,
const char *name, struct device *parent,
void *devdata, struct lcd_ops *ops)
{
struct lcd_device **ptr, *lcd;
ptr = devres_alloc(devm_lcd_device_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
lcd = lcd_device_register(name, parent, devdata, ops);
if (!IS_ERR(lcd)) {
*ptr = lcd;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return lcd;
}
EXPORT_SYMBOL(devm_lcd_device_register);
/**
* devm_lcd_device_unregister - resource managed lcd_device_unregister()
* @dev: the device to unregister
* @ld: the lcd device to unregister
*
* Deallocated a lcd allocated with devm_lcd_device_register(). Normally
* this function will not need to be called and the resource management
* code will ensure that the resource is freed.
*/
void devm_lcd_device_unregister(struct device *dev, struct lcd_device *ld)
{
int rc;
rc = devres_release(dev, devm_lcd_device_release,
devm_lcd_device_match, ld);
WARN_ON(rc);
}
EXPORT_SYMBOL(devm_lcd_device_unregister);
static void __exit lcd_class_exit(void)
{
class_destroy(lcd_class);
}
static int __init lcd_class_init(void)
{
lcd_class = class_create(THIS_MODULE, "lcd");
if (IS_ERR(lcd_class)) {
pr_warn("Unable to create backlight class; errno = %ld\n",
PTR_ERR(lcd_class));
return PTR_ERR(lcd_class);
}
lcd_class->dev_groups = lcd_device_groups;
return 0;
}
/*
* if this is compiled into the kernel, we need to ensure that the
* class is registered before users of the class try to register lcd's
*/
postcore_initcall(lcd_class_init);
module_exit(lcd_class_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jamey Hicks <jamey.hicks@hp.com>, Andrew Zabolotny <zap@homelink.ru>");
MODULE_DESCRIPTION("LCD Lowlevel Control Abstraction");

View file

@ -0,0 +1,812 @@
/*
* ld9040 AMOLED LCD panel driver.
*
* Copyright (c) 2011 Samsung Electronics
* Author: Donghwa Lee <dh09.lee@samsung.com>
* Derived from drivers/video/backlight/s6e63m0.c
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <linux/wait.h>
#include "ld9040_gamma.h"
#define SLEEPMSEC 0x1000
#define ENDDEF 0x2000
#define DEFMASK 0xFF00
#define COMMAND_ONLY 0xFE
#define DATA_ONLY 0xFF
#define MIN_BRIGHTNESS 0
#define MAX_BRIGHTNESS 24
struct ld9040 {
struct device *dev;
struct spi_device *spi;
unsigned int power;
unsigned int current_brightness;
struct lcd_device *ld;
struct backlight_device *bd;
struct lcd_platform_data *lcd_pd;
struct mutex lock;
bool enabled;
};
static struct regulator_bulk_data supplies[] = {
{ .supply = "vdd3", },
{ .supply = "vci", },
};
static void ld9040_regulator_enable(struct ld9040 *lcd)
{
int ret = 0;
struct lcd_platform_data *pd = NULL;
pd = lcd->lcd_pd;
mutex_lock(&lcd->lock);
if (!lcd->enabled) {
ret = regulator_bulk_enable(ARRAY_SIZE(supplies), supplies);
if (ret)
goto out;
lcd->enabled = true;
}
msleep(pd->power_on_delay);
out:
mutex_unlock(&lcd->lock);
}
static void ld9040_regulator_disable(struct ld9040 *lcd)
{
int ret = 0;
mutex_lock(&lcd->lock);
if (lcd->enabled) {
ret = regulator_bulk_disable(ARRAY_SIZE(supplies), supplies);
if (ret)
goto out;
lcd->enabled = false;
}
out:
mutex_unlock(&lcd->lock);
}
static const unsigned short seq_swreset[] = {
0x01, COMMAND_ONLY,
ENDDEF, 0x00
};
static const unsigned short seq_user_setting[] = {
0xF0, 0x5A,
DATA_ONLY, 0x5A,
ENDDEF, 0x00
};
static const unsigned short seq_elvss_on[] = {
0xB1, 0x0D,
DATA_ONLY, 0x00,
DATA_ONLY, 0x16,
ENDDEF, 0x00
};
static const unsigned short seq_gtcon[] = {
0xF7, 0x09,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
ENDDEF, 0x00
};
static const unsigned short seq_panel_condition[] = {
0xF8, 0x05,
DATA_ONLY, 0x65,
DATA_ONLY, 0x96,
DATA_ONLY, 0x71,
DATA_ONLY, 0x7D,
DATA_ONLY, 0x19,
DATA_ONLY, 0x3B,
DATA_ONLY, 0x0D,
DATA_ONLY, 0x19,
DATA_ONLY, 0x7E,
DATA_ONLY, 0x0D,
DATA_ONLY, 0xE2,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x7E,
DATA_ONLY, 0x7D,
DATA_ONLY, 0x07,
DATA_ONLY, 0x07,
DATA_ONLY, 0x20,
DATA_ONLY, 0x20,
DATA_ONLY, 0x20,
DATA_ONLY, 0x02,
DATA_ONLY, 0x02,
ENDDEF, 0x00
};
static const unsigned short seq_gamma_set1[] = {
0xF9, 0x00,
DATA_ONLY, 0xA7,
DATA_ONLY, 0xB4,
DATA_ONLY, 0xAE,
DATA_ONLY, 0xBF,
DATA_ONLY, 0x00,
DATA_ONLY, 0x91,
DATA_ONLY, 0x00,
DATA_ONLY, 0xB2,
DATA_ONLY, 0xB4,
DATA_ONLY, 0xAA,
DATA_ONLY, 0xBB,
DATA_ONLY, 0x00,
DATA_ONLY, 0xAC,
DATA_ONLY, 0x00,
DATA_ONLY, 0xB3,
DATA_ONLY, 0xB1,
DATA_ONLY, 0xAA,
DATA_ONLY, 0xBC,
DATA_ONLY, 0x00,
DATA_ONLY, 0xB3,
ENDDEF, 0x00
};
static const unsigned short seq_gamma_ctrl[] = {
0xFB, 0x02,
DATA_ONLY, 0x5A,
ENDDEF, 0x00
};
static const unsigned short seq_gamma_start[] = {
0xF9, COMMAND_ONLY,
ENDDEF, 0x00
};
static const unsigned short seq_apon[] = {
0xF3, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x0A,
DATA_ONLY, 0x02,
ENDDEF, 0x00
};
static const unsigned short seq_display_ctrl[] = {
0xF2, 0x02,
DATA_ONLY, 0x08,
DATA_ONLY, 0x08,
DATA_ONLY, 0x10,
DATA_ONLY, 0x10,
ENDDEF, 0x00
};
static const unsigned short seq_manual_pwr[] = {
0xB0, 0x04,
ENDDEF, 0x00
};
static const unsigned short seq_pwr_ctrl[] = {
0xF4, 0x0A,
DATA_ONLY, 0x87,
DATA_ONLY, 0x25,
DATA_ONLY, 0x6A,
DATA_ONLY, 0x44,
DATA_ONLY, 0x02,
DATA_ONLY, 0x88,
ENDDEF, 0x00
};
static const unsigned short seq_sleep_out[] = {
0x11, COMMAND_ONLY,
ENDDEF, 0x00
};
static const unsigned short seq_sleep_in[] = {
0x10, COMMAND_ONLY,
ENDDEF, 0x00
};
static const unsigned short seq_display_on[] = {
0x29, COMMAND_ONLY,
ENDDEF, 0x00
};
static const unsigned short seq_display_off[] = {
0x28, COMMAND_ONLY,
ENDDEF, 0x00
};
static const unsigned short seq_vci1_1st_en[] = {
0xF3, 0x10,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x02,
ENDDEF, 0x00
};
static const unsigned short seq_vl1_en[] = {
0xF3, 0x11,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x02,
ENDDEF, 0x00
};
static const unsigned short seq_vl2_en[] = {
0xF3, 0x13,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x02,
ENDDEF, 0x00
};
static const unsigned short seq_vci1_2nd_en[] = {
0xF3, 0x33,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x02,
ENDDEF, 0x00
};
static const unsigned short seq_vl3_en[] = {
0xF3, 0x37,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x02,
ENDDEF, 0x00
};
static const unsigned short seq_vreg1_amp_en[] = {
0xF3, 0x37,
DATA_ONLY, 0x01,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x02,
ENDDEF, 0x00
};
static const unsigned short seq_vgh_amp_en[] = {
0xF3, 0x37,
DATA_ONLY, 0x11,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x02,
ENDDEF, 0x00
};
static const unsigned short seq_vgl_amp_en[] = {
0xF3, 0x37,
DATA_ONLY, 0x31,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x02,
ENDDEF, 0x00
};
static const unsigned short seq_vmos_amp_en[] = {
0xF3, 0x37,
DATA_ONLY, 0xB1,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x03,
ENDDEF, 0x00
};
static const unsigned short seq_vint_amp_en[] = {
0xF3, 0x37,
DATA_ONLY, 0xF1,
/* DATA_ONLY, 0x71, VMOS/VBL/VBH not used */
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x03,
/* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
ENDDEF, 0x00
};
static const unsigned short seq_vbh_amp_en[] = {
0xF3, 0x37,
DATA_ONLY, 0xF9,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x03,
ENDDEF, 0x00
};
static const unsigned short seq_vbl_amp_en[] = {
0xF3, 0x37,
DATA_ONLY, 0xFD,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x03,
ENDDEF, 0x00
};
static const unsigned short seq_gam_amp_en[] = {
0xF3, 0x37,
DATA_ONLY, 0xFF,
/* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x03,
/* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
ENDDEF, 0x00
};
static const unsigned short seq_sd_amp_en[] = {
0xF3, 0x37,
DATA_ONLY, 0xFF,
/* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */
DATA_ONLY, 0x80,
DATA_ONLY, 0x00,
DATA_ONLY, 0x03,
/* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
ENDDEF, 0x00
};
static const unsigned short seq_gls_en[] = {
0xF3, 0x37,
DATA_ONLY, 0xFF,
/* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */
DATA_ONLY, 0x81,
DATA_ONLY, 0x00,
DATA_ONLY, 0x03,
/* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
ENDDEF, 0x00
};
static const unsigned short seq_els_en[] = {
0xF3, 0x37,
DATA_ONLY, 0xFF,
/* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */
DATA_ONLY, 0x83,
DATA_ONLY, 0x00,
DATA_ONLY, 0x03,
/* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
ENDDEF, 0x00
};
static const unsigned short seq_el_on[] = {
0xF3, 0x37,
DATA_ONLY, 0xFF,
/* DATA_ONLY, 0x73, VMOS/VBL/VBH not used */
DATA_ONLY, 0x87,
DATA_ONLY, 0x00,
DATA_ONLY, 0x03,
/* DATA_ONLY, 0x02, VMOS/VBL/VBH not used */
ENDDEF, 0x00
};
static int ld9040_spi_write_byte(struct ld9040 *lcd, int addr, int data)
{
u16 buf[1];
struct spi_message msg;
struct spi_transfer xfer = {
.len = 2,
.tx_buf = buf,
};
buf[0] = (addr << 8) | data;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
return spi_sync(lcd->spi, &msg);
}
static int ld9040_spi_write(struct ld9040 *lcd, unsigned char address,
unsigned char command)
{
int ret = 0;
if (address != DATA_ONLY)
ret = ld9040_spi_write_byte(lcd, 0x0, address);
if (command != COMMAND_ONLY)
ret = ld9040_spi_write_byte(lcd, 0x1, command);
return ret;
}
static int ld9040_panel_send_sequence(struct ld9040 *lcd,
const unsigned short *wbuf)
{
int ret = 0, i = 0;
while ((wbuf[i] & DEFMASK) != ENDDEF) {
if ((wbuf[i] & DEFMASK) != SLEEPMSEC) {
ret = ld9040_spi_write(lcd, wbuf[i], wbuf[i+1]);
if (ret)
break;
} else {
msleep(wbuf[i+1]);
}
i += 2;
}
return ret;
}
static int _ld9040_gamma_ctl(struct ld9040 *lcd, const unsigned int *gamma)
{
unsigned int i = 0;
int ret = 0;
/* start gamma table updating. */
ret = ld9040_panel_send_sequence(lcd, seq_gamma_start);
if (ret) {
dev_err(lcd->dev, "failed to disable gamma table updating.\n");
goto gamma_err;
}
for (i = 0 ; i < GAMMA_TABLE_COUNT; i++) {
ret = ld9040_spi_write(lcd, DATA_ONLY, gamma[i]);
if (ret) {
dev_err(lcd->dev, "failed to set gamma table.\n");
goto gamma_err;
}
}
/* update gamma table. */
ret = ld9040_panel_send_sequence(lcd, seq_gamma_ctrl);
if (ret)
dev_err(lcd->dev, "failed to update gamma table.\n");
gamma_err:
return ret;
}
static int ld9040_gamma_ctl(struct ld9040 *lcd, int gamma)
{
return _ld9040_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
}
static int ld9040_ldi_init(struct ld9040 *lcd)
{
int ret, i;
static const unsigned short *init_seq[] = {
seq_user_setting,
seq_panel_condition,
seq_display_ctrl,
seq_manual_pwr,
seq_elvss_on,
seq_gtcon,
seq_gamma_set1,
seq_gamma_ctrl,
seq_sleep_out,
};
for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
ret = ld9040_panel_send_sequence(lcd, init_seq[i]);
/* workaround: minimum delay time for transferring CMD */
usleep_range(300, 310);
if (ret)
break;
}
return ret;
}
static int ld9040_ldi_enable(struct ld9040 *lcd)
{
return ld9040_panel_send_sequence(lcd, seq_display_on);
}
static int ld9040_ldi_disable(struct ld9040 *lcd)
{
int ret;
ret = ld9040_panel_send_sequence(lcd, seq_display_off);
ret = ld9040_panel_send_sequence(lcd, seq_sleep_in);
return ret;
}
static int ld9040_power_is_on(int power)
{
return power <= FB_BLANK_NORMAL;
}
static int ld9040_power_on(struct ld9040 *lcd)
{
int ret = 0;
struct lcd_platform_data *pd;
pd = lcd->lcd_pd;
/* lcd power on */
ld9040_regulator_enable(lcd);
if (!pd->reset) {
dev_err(lcd->dev, "reset is NULL.\n");
return -EINVAL;
}
pd->reset(lcd->ld);
msleep(pd->reset_delay);
ret = ld9040_ldi_init(lcd);
if (ret) {
dev_err(lcd->dev, "failed to initialize ldi.\n");
return ret;
}
ret = ld9040_ldi_enable(lcd);
if (ret) {
dev_err(lcd->dev, "failed to enable ldi.\n");
return ret;
}
return 0;
}
static int ld9040_power_off(struct ld9040 *lcd)
{
int ret;
struct lcd_platform_data *pd;
pd = lcd->lcd_pd;
ret = ld9040_ldi_disable(lcd);
if (ret) {
dev_err(lcd->dev, "lcd setting failed.\n");
return -EIO;
}
msleep(pd->power_off_delay);
/* lcd power off */
ld9040_regulator_disable(lcd);
return 0;
}
static int ld9040_power(struct ld9040 *lcd, int power)
{
int ret = 0;
if (ld9040_power_is_on(power) && !ld9040_power_is_on(lcd->power))
ret = ld9040_power_on(lcd);
else if (!ld9040_power_is_on(power) && ld9040_power_is_on(lcd->power))
ret = ld9040_power_off(lcd);
if (!ret)
lcd->power = power;
return ret;
}
static int ld9040_set_power(struct lcd_device *ld, int power)
{
struct ld9040 *lcd = lcd_get_data(ld);
if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
power != FB_BLANK_NORMAL) {
dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
return -EINVAL;
}
return ld9040_power(lcd, power);
}
static int ld9040_get_power(struct lcd_device *ld)
{
struct ld9040 *lcd = lcd_get_data(ld);
return lcd->power;
}
static int ld9040_set_brightness(struct backlight_device *bd)
{
int ret = 0, brightness = bd->props.brightness;
struct ld9040 *lcd = bl_get_data(bd);
if (brightness < MIN_BRIGHTNESS ||
brightness > bd->props.max_brightness) {
dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
MIN_BRIGHTNESS, MAX_BRIGHTNESS);
return -EINVAL;
}
ret = ld9040_gamma_ctl(lcd, bd->props.brightness);
if (ret) {
dev_err(&bd->dev, "lcd brightness setting failed.\n");
return -EIO;
}
return ret;
}
static struct lcd_ops ld9040_lcd_ops = {
.set_power = ld9040_set_power,
.get_power = ld9040_get_power,
};
static const struct backlight_ops ld9040_backlight_ops = {
.update_status = ld9040_set_brightness,
};
static int ld9040_probe(struct spi_device *spi)
{
int ret = 0;
struct ld9040 *lcd = NULL;
struct lcd_device *ld = NULL;
struct backlight_device *bd = NULL;
struct backlight_properties props;
lcd = devm_kzalloc(&spi->dev, sizeof(struct ld9040), GFP_KERNEL);
if (!lcd)
return -ENOMEM;
/* ld9040 lcd panel uses 3-wire 9bits SPI Mode. */
spi->bits_per_word = 9;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "spi setup failed.\n");
return ret;
}
lcd->spi = spi;
lcd->dev = &spi->dev;
lcd->lcd_pd = dev_get_platdata(&spi->dev);
if (!lcd->lcd_pd) {
dev_err(&spi->dev, "platform data is NULL.\n");
return -EINVAL;
}
mutex_init(&lcd->lock);
ret = devm_regulator_bulk_get(lcd->dev, ARRAY_SIZE(supplies), supplies);
if (ret) {
dev_err(lcd->dev, "Failed to get regulators: %d\n", ret);
return ret;
}
ld = devm_lcd_device_register(&spi->dev, "ld9040", &spi->dev, lcd,
&ld9040_lcd_ops);
if (IS_ERR(ld))
return PTR_ERR(ld);
lcd->ld = ld;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = MAX_BRIGHTNESS;
bd = devm_backlight_device_register(&spi->dev, "ld9040-bl", &spi->dev,
lcd, &ld9040_backlight_ops, &props);
if (IS_ERR(bd))
return PTR_ERR(bd);
bd->props.brightness = MAX_BRIGHTNESS;
lcd->bd = bd;
/*
* if lcd panel was on from bootloader like u-boot then
* do not lcd on.
*/
if (!lcd->lcd_pd->lcd_enabled) {
/*
* if lcd panel was off from bootloader then
* current lcd status is powerdown and then
* it enables lcd panel.
*/
lcd->power = FB_BLANK_POWERDOWN;
ld9040_power(lcd, FB_BLANK_UNBLANK);
} else {
lcd->power = FB_BLANK_UNBLANK;
}
spi_set_drvdata(spi, lcd);
dev_info(&spi->dev, "ld9040 panel driver has been probed.\n");
return 0;
}
static int ld9040_remove(struct spi_device *spi)
{
struct ld9040 *lcd = spi_get_drvdata(spi);
ld9040_power(lcd, FB_BLANK_POWERDOWN);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int ld9040_suspend(struct device *dev)
{
struct ld9040 *lcd = dev_get_drvdata(dev);
dev_dbg(dev, "lcd->power = %d\n", lcd->power);
/*
* when lcd panel is suspend, lcd panel becomes off
* regardless of status.
*/
return ld9040_power(lcd, FB_BLANK_POWERDOWN);
}
static int ld9040_resume(struct device *dev)
{
struct ld9040 *lcd = dev_get_drvdata(dev);
lcd->power = FB_BLANK_POWERDOWN;
return ld9040_power(lcd, FB_BLANK_UNBLANK);
}
#endif
static SIMPLE_DEV_PM_OPS(ld9040_pm_ops, ld9040_suspend, ld9040_resume);
/* Power down all displays on reboot, poweroff or halt. */
static void ld9040_shutdown(struct spi_device *spi)
{
struct ld9040 *lcd = spi_get_drvdata(spi);
ld9040_power(lcd, FB_BLANK_POWERDOWN);
}
static struct spi_driver ld9040_driver = {
.driver = {
.name = "ld9040",
.owner = THIS_MODULE,
.pm = &ld9040_pm_ops,
},
.probe = ld9040_probe,
.remove = ld9040_remove,
.shutdown = ld9040_shutdown,
};
module_spi_driver(ld9040_driver);
MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>");
MODULE_DESCRIPTION("ld9040 LCD Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,202 @@
/*
* Gamma level definitions.
*
* Copyright (c) 2011 Samsung Electronics
* InKi Dae <inki.dae@samsung.com>
* Donghwa Lee <dh09.lee@samsung.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.
*/
#ifndef _LD9040_BRIGHTNESS_H
#define _LD9040_BRIGHTNESS_H
#define MAX_GAMMA_LEVEL 25
#define GAMMA_TABLE_COUNT 21
/* gamma value: 2.2 */
static const unsigned int ld9040_22_300[] = {
0x00, 0xa7, 0xb4, 0xae, 0xbf, 0x00, 0x91,
0x00, 0xb2, 0xb4, 0xaa, 0xbb, 0x00, 0xac,
0x00, 0xb3, 0xb1, 0xaa, 0xbc, 0x00, 0xb3
};
static const unsigned int ld9040_22_290[] = {
0x00, 0xa9, 0xb7, 0xae, 0xbd, 0x00, 0x89,
0x00, 0xb7, 0xb6, 0xa8, 0xba, 0x00, 0xa4,
0x00, 0xb1, 0xb4, 0xaa, 0xbb, 0x00, 0xaa
};
static const unsigned int ld9040_22_280[] = {
0x00, 0xa9, 0xb6, 0xad, 0xbf, 0x00, 0x86,
0x00, 0xb8, 0xb5, 0xa8, 0xbc, 0x00, 0xa0,
0x00, 0xb3, 0xb3, 0xa9, 0xbc, 0x00, 0xa7
};
static const unsigned int ld9040_22_270[] = {
0x00, 0xa8, 0xb8, 0xae, 0xbe, 0x00, 0x84,
0x00, 0xb9, 0xb7, 0xa8, 0xbc, 0x00, 0x9d,
0x00, 0xb2, 0xb5, 0xaa, 0xbc, 0x00, 0xa4
};
static const unsigned int ld9040_22_260[] = {
0x00, 0xa4, 0xb8, 0xb0, 0xbf, 0x00, 0x80,
0x00, 0xb8, 0xb6, 0xaa, 0xbc, 0x00, 0x9a,
0x00, 0xb0, 0xb5, 0xab, 0xbd, 0x00, 0xa0
};
static const unsigned int ld9040_22_250[] = {
0x00, 0xa4, 0xb9, 0xaf, 0xc1, 0x00, 0x7d,
0x00, 0xb9, 0xb6, 0xaa, 0xbb, 0x00, 0x97,
0x00, 0xb1, 0xb5, 0xaa, 0xbf, 0x00, 0x9d
};
static const unsigned int ld9040_22_240[] = {
0x00, 0xa2, 0xb9, 0xaf, 0xc2, 0x00, 0x7a,
0x00, 0xb9, 0xb7, 0xaa, 0xbd, 0x00, 0x94,
0x00, 0xb0, 0xb5, 0xab, 0xbf, 0x00, 0x9a
};
static const unsigned int ld9040_22_230[] = {
0x00, 0xa0, 0xb9, 0xaf, 0xc3, 0x00, 0x77,
0x00, 0xb9, 0xb7, 0xab, 0xbe, 0x00, 0x90,
0x00, 0xb0, 0xb6, 0xab, 0xbf, 0x00, 0x97
};
static const unsigned int ld9040_22_220[] = {
0x00, 0x9e, 0xba, 0xb0, 0xc2, 0x00, 0x75,
0x00, 0xb9, 0xb8, 0xab, 0xbe, 0x00, 0x8e,
0x00, 0xb0, 0xb6, 0xac, 0xbf, 0x00, 0x94
};
static const unsigned int ld9040_22_210[] = {
0x00, 0x9c, 0xb9, 0xb0, 0xc4, 0x00, 0x72,
0x00, 0xb8, 0xb8, 0xac, 0xbf, 0x00, 0x8a,
0x00, 0xb0, 0xb6, 0xac, 0xc0, 0x00, 0x91
};
static const unsigned int ld9040_22_200[] = {
0x00, 0x9a, 0xba, 0xb1, 0xc4, 0x00, 0x6f,
0x00, 0xb8, 0xb8, 0xad, 0xc0, 0x00, 0x86,
0x00, 0xb0, 0xb7, 0xad, 0xc0, 0x00, 0x8d
};
static const unsigned int ld9040_22_190[] = {
0x00, 0x97, 0xba, 0xb2, 0xc5, 0x00, 0x6c,
0x00, 0xb8, 0xb8, 0xae, 0xc1, 0x00, 0x82,
0x00, 0xb0, 0xb6, 0xae, 0xc2, 0x00, 0x89
};
static const unsigned int ld9040_22_180[] = {
0x00, 0x93, 0xba, 0xb3, 0xc5, 0x00, 0x69,
0x00, 0xb8, 0xb9, 0xae, 0xc1, 0x00, 0x7f,
0x00, 0xb0, 0xb6, 0xae, 0xc3, 0x00, 0x85
};
static const unsigned int ld9040_22_170[] = {
0x00, 0x8b, 0xb9, 0xb3, 0xc7, 0x00, 0x65,
0x00, 0xb7, 0xb8, 0xaf, 0xc3, 0x00, 0x7a,
0x00, 0x80, 0xb6, 0xae, 0xc4, 0x00, 0x81
};
static const unsigned int ld9040_22_160[] = {
0x00, 0x89, 0xba, 0xb3, 0xc8, 0x00, 0x62,
0x00, 0xb6, 0xba, 0xaf, 0xc3, 0x00, 0x76,
0x00, 0xaf, 0xb7, 0xae, 0xc4, 0x00, 0x7e
};
static const unsigned int ld9040_22_150[] = {
0x00, 0x82, 0xba, 0xb4, 0xc7, 0x00, 0x5f,
0x00, 0xb5, 0xba, 0xb0, 0xc3, 0x00, 0x72,
0x00, 0xae, 0xb8, 0xb0, 0xc3, 0x00, 0x7a
};
static const unsigned int ld9040_22_140[] = {
0x00, 0x7b, 0xbb, 0xb4, 0xc8, 0x00, 0x5b,
0x00, 0xb5, 0xba, 0xb1, 0xc4, 0x00, 0x6e,
0x00, 0xae, 0xb9, 0xb0, 0xc5, 0x00, 0x75
};
static const unsigned int ld9040_22_130[] = {
0x00, 0x71, 0xbb, 0xb5, 0xc8, 0x00, 0x57,
0x00, 0xb5, 0xbb, 0xb0, 0xc5, 0x00, 0x6a,
0x00, 0xae, 0xb9, 0xb1, 0xc6, 0x00, 0x70
};
static const unsigned int ld9040_22_120[] = {
0x00, 0x47, 0xba, 0xb6, 0xca, 0x00, 0x53,
0x00, 0xb5, 0xbb, 0xb3, 0xc6, 0x00, 0x65,
0x00, 0xae, 0xb8, 0xb3, 0xc7, 0x00, 0x6c
};
static const unsigned int ld9040_22_110[] = {
0x00, 0x13, 0xbb, 0xb7, 0xca, 0x00, 0x4f,
0x00, 0xb4, 0xbb, 0xb3, 0xc7, 0x00, 0x60,
0x00, 0xad, 0xb8, 0xb4, 0xc7, 0x00, 0x67
};
static const unsigned int ld9040_22_100[] = {
0x00, 0x13, 0xba, 0xb8, 0xcb, 0x00, 0x4b,
0x00, 0xb3, 0xbc, 0xb4, 0xc7, 0x00, 0x5c,
0x00, 0xac, 0xb8, 0xb4, 0xc8, 0x00, 0x62
};
static const unsigned int ld9040_22_90[] = {
0x00, 0x13, 0xb9, 0xb8, 0xcd, 0x00, 0x46,
0x00, 0xb1, 0xbc, 0xb5, 0xc8, 0x00, 0x56,
0x00, 0xaa, 0xb8, 0xb4, 0xc9, 0x00, 0x5d
};
static const unsigned int ld9040_22_80[] = {
0x00, 0x13, 0xba, 0xb9, 0xcd, 0x00, 0x41,
0x00, 0xb0, 0xbe, 0xb5, 0xc9, 0x00, 0x51,
0x00, 0xa9, 0xb9, 0xb5, 0xca, 0x00, 0x57
};
static const unsigned int ld9040_22_70[] = {
0x00, 0x13, 0xb9, 0xb9, 0xd0, 0x00, 0x3c,
0x00, 0xaf, 0xbf, 0xb6, 0xcb, 0x00, 0x4b,
0x00, 0xa8, 0xb9, 0xb5, 0xcc, 0x00, 0x52
};
static const unsigned int ld9040_22_50[] = {
0x00, 0x13, 0xb2, 0xba, 0xd2, 0x00, 0x30,
0x00, 0xaf, 0xc0, 0xb8, 0xcd, 0x00, 0x3d,
0x00, 0xa8, 0xb8, 0xb7, 0xcd, 0x00, 0x44
};
struct ld9040_gamma {
unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
};
static struct ld9040_gamma gamma_table = {
.gamma_22_table[0] = (unsigned int *)&ld9040_22_50,
.gamma_22_table[1] = (unsigned int *)&ld9040_22_70,
.gamma_22_table[2] = (unsigned int *)&ld9040_22_80,
.gamma_22_table[3] = (unsigned int *)&ld9040_22_90,
.gamma_22_table[4] = (unsigned int *)&ld9040_22_100,
.gamma_22_table[5] = (unsigned int *)&ld9040_22_110,
.gamma_22_table[6] = (unsigned int *)&ld9040_22_120,
.gamma_22_table[7] = (unsigned int *)&ld9040_22_130,
.gamma_22_table[8] = (unsigned int *)&ld9040_22_140,
.gamma_22_table[9] = (unsigned int *)&ld9040_22_150,
.gamma_22_table[10] = (unsigned int *)&ld9040_22_160,
.gamma_22_table[11] = (unsigned int *)&ld9040_22_170,
.gamma_22_table[12] = (unsigned int *)&ld9040_22_180,
.gamma_22_table[13] = (unsigned int *)&ld9040_22_190,
.gamma_22_table[14] = (unsigned int *)&ld9040_22_200,
.gamma_22_table[15] = (unsigned int *)&ld9040_22_210,
.gamma_22_table[16] = (unsigned int *)&ld9040_22_220,
.gamma_22_table[17] = (unsigned int *)&ld9040_22_230,
.gamma_22_table[18] = (unsigned int *)&ld9040_22_240,
.gamma_22_table[19] = (unsigned int *)&ld9040_22_250,
.gamma_22_table[20] = (unsigned int *)&ld9040_22_260,
.gamma_22_table[21] = (unsigned int *)&ld9040_22_270,
.gamma_22_table[22] = (unsigned int *)&ld9040_22_280,
.gamma_22_table[23] = (unsigned int *)&ld9040_22_290,
.gamma_22_table[24] = (unsigned int *)&ld9040_22_300,
};
#endif

View file

@ -0,0 +1,411 @@
/*
* lm3533-bl.c -- LM3533 Backlight driver
*
* Copyright (C) 2011-2012 Texas Instruments
*
* Author: Johan Hovold <jhovold@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/backlight.h>
#include <linux/fb.h>
#include <linux/slab.h>
#include <linux/mfd/lm3533.h>
#define LM3533_HVCTRLBANK_COUNT 2
#define LM3533_BL_MAX_BRIGHTNESS 255
#define LM3533_REG_CTRLBANK_AB_BCONF 0x1a
struct lm3533_bl {
struct lm3533 *lm3533;
struct lm3533_ctrlbank cb;
struct backlight_device *bd;
int id;
};
static inline int lm3533_bl_get_ctrlbank_id(struct lm3533_bl *bl)
{
return bl->id;
}
static int lm3533_bl_update_status(struct backlight_device *bd)
{
struct lm3533_bl *bl = bl_get_data(bd);
int brightness = bd->props.brightness;
if (bd->props.power != FB_BLANK_UNBLANK)
brightness = 0;
if (bd->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
return lm3533_ctrlbank_set_brightness(&bl->cb, (u8)brightness);
}
static int lm3533_bl_get_brightness(struct backlight_device *bd)
{
struct lm3533_bl *bl = bl_get_data(bd);
u8 val;
int ret;
ret = lm3533_ctrlbank_get_brightness(&bl->cb, &val);
if (ret)
return ret;
return val;
}
static const struct backlight_ops lm3533_bl_ops = {
.get_brightness = lm3533_bl_get_brightness,
.update_status = lm3533_bl_update_status,
};
static ssize_t show_id(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lm3533_bl *bl = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%d\n", bl->id);
}
static ssize_t show_als_channel(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lm3533_bl *bl = dev_get_drvdata(dev);
unsigned channel = lm3533_bl_get_ctrlbank_id(bl);
return scnprintf(buf, PAGE_SIZE, "%u\n", channel);
}
static ssize_t show_als_en(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lm3533_bl *bl = dev_get_drvdata(dev);
int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
u8 val;
u8 mask;
bool enable;
int ret;
ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
if (ret)
return ret;
mask = 1 << (2 * ctrlbank);
enable = val & mask;
return scnprintf(buf, PAGE_SIZE, "%d\n", enable);
}
static ssize_t store_als_en(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct lm3533_bl *bl = dev_get_drvdata(dev);
int ctrlbank = lm3533_bl_get_ctrlbank_id(bl);
int enable;
u8 val;
u8 mask;
int ret;
if (kstrtoint(buf, 0, &enable))
return -EINVAL;
mask = 1 << (2 * ctrlbank);
if (enable)
val = mask;
else
val = 0;
ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
mask);
if (ret)
return ret;
return len;
}
static ssize_t show_linear(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lm3533_bl *bl = dev_get_drvdata(dev);
u8 val;
u8 mask;
int linear;
int ret;
ret = lm3533_read(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, &val);
if (ret)
return ret;
mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
if (val & mask)
linear = 1;
else
linear = 0;
return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
}
static ssize_t store_linear(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct lm3533_bl *bl = dev_get_drvdata(dev);
unsigned long linear;
u8 mask;
u8 val;
int ret;
if (kstrtoul(buf, 0, &linear))
return -EINVAL;
mask = 1 << (2 * lm3533_bl_get_ctrlbank_id(bl) + 1);
if (linear)
val = mask;
else
val = 0;
ret = lm3533_update(bl->lm3533, LM3533_REG_CTRLBANK_AB_BCONF, val,
mask);
if (ret)
return ret;
return len;
}
static ssize_t show_pwm(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lm3533_bl *bl = dev_get_drvdata(dev);
u8 val;
int ret;
ret = lm3533_ctrlbank_get_pwm(&bl->cb, &val);
if (ret)
return ret;
return scnprintf(buf, PAGE_SIZE, "%u\n", val);
}
static ssize_t store_pwm(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct lm3533_bl *bl = dev_get_drvdata(dev);
u8 val;
int ret;
if (kstrtou8(buf, 0, &val))
return -EINVAL;
ret = lm3533_ctrlbank_set_pwm(&bl->cb, val);
if (ret)
return ret;
return len;
}
static LM3533_ATTR_RO(als_channel);
static LM3533_ATTR_RW(als_en);
static LM3533_ATTR_RO(id);
static LM3533_ATTR_RW(linear);
static LM3533_ATTR_RW(pwm);
static struct attribute *lm3533_bl_attributes[] = {
&dev_attr_als_channel.attr,
&dev_attr_als_en.attr,
&dev_attr_id.attr,
&dev_attr_linear.attr,
&dev_attr_pwm.attr,
NULL,
};
static umode_t lm3533_bl_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct lm3533_bl *bl = dev_get_drvdata(dev);
umode_t mode = attr->mode;
if (attr == &dev_attr_als_channel.attr ||
attr == &dev_attr_als_en.attr) {
if (!bl->lm3533->have_als)
mode = 0;
}
return mode;
};
static struct attribute_group lm3533_bl_attribute_group = {
.is_visible = lm3533_bl_attr_is_visible,
.attrs = lm3533_bl_attributes
};
static int lm3533_bl_setup(struct lm3533_bl *bl,
struct lm3533_bl_platform_data *pdata)
{
int ret;
ret = lm3533_ctrlbank_set_max_current(&bl->cb, pdata->max_current);
if (ret)
return ret;
return lm3533_ctrlbank_set_pwm(&bl->cb, pdata->pwm);
}
static int lm3533_bl_probe(struct platform_device *pdev)
{
struct lm3533 *lm3533;
struct lm3533_bl_platform_data *pdata;
struct lm3533_bl *bl;
struct backlight_device *bd;
struct backlight_properties props;
int ret;
dev_dbg(&pdev->dev, "%s\n", __func__);
lm3533 = dev_get_drvdata(pdev->dev.parent);
if (!lm3533)
return -EINVAL;
pdata = dev_get_platdata(&pdev->dev);
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
if (pdev->id < 0 || pdev->id >= LM3533_HVCTRLBANK_COUNT) {
dev_err(&pdev->dev, "illegal backlight id %d\n", pdev->id);
return -EINVAL;
}
bl = devm_kzalloc(&pdev->dev, sizeof(*bl), GFP_KERNEL);
if (!bl)
return -ENOMEM;
bl->lm3533 = lm3533;
bl->id = pdev->id;
bl->cb.lm3533 = lm3533;
bl->cb.id = lm3533_bl_get_ctrlbank_id(bl);
bl->cb.dev = NULL; /* until registered */
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW;
props.max_brightness = LM3533_BL_MAX_BRIGHTNESS;
props.brightness = pdata->default_brightness;
bd = devm_backlight_device_register(&pdev->dev, pdata->name,
pdev->dev.parent, bl, &lm3533_bl_ops,
&props);
if (IS_ERR(bd)) {
dev_err(&pdev->dev, "failed to register backlight device\n");
return PTR_ERR(bd);
}
bl->bd = bd;
bl->cb.dev = &bl->bd->dev;
platform_set_drvdata(pdev, bl);
ret = sysfs_create_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
if (ret < 0) {
dev_err(&pdev->dev, "failed to create sysfs attributes\n");
return ret;
}
backlight_update_status(bd);
ret = lm3533_bl_setup(bl, pdata);
if (ret)
goto err_sysfs_remove;
ret = lm3533_ctrlbank_enable(&bl->cb);
if (ret)
goto err_sysfs_remove;
return 0;
err_sysfs_remove:
sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
return ret;
}
static int lm3533_bl_remove(struct platform_device *pdev)
{
struct lm3533_bl *bl = platform_get_drvdata(pdev);
struct backlight_device *bd = bl->bd;
dev_dbg(&bd->dev, "%s\n", __func__);
bd->props.power = FB_BLANK_POWERDOWN;
bd->props.brightness = 0;
lm3533_ctrlbank_disable(&bl->cb);
sysfs_remove_group(&bd->dev.kobj, &lm3533_bl_attribute_group);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int lm3533_bl_suspend(struct device *dev)
{
struct lm3533_bl *bl = dev_get_drvdata(dev);
dev_dbg(dev, "%s\n", __func__);
return lm3533_ctrlbank_disable(&bl->cb);
}
static int lm3533_bl_resume(struct device *dev)
{
struct lm3533_bl *bl = dev_get_drvdata(dev);
dev_dbg(dev, "%s\n", __func__);
return lm3533_ctrlbank_enable(&bl->cb);
}
#endif
static SIMPLE_DEV_PM_OPS(lm3533_bl_pm_ops, lm3533_bl_suspend, lm3533_bl_resume);
static void lm3533_bl_shutdown(struct platform_device *pdev)
{
struct lm3533_bl *bl = platform_get_drvdata(pdev);
dev_dbg(&pdev->dev, "%s\n", __func__);
lm3533_ctrlbank_disable(&bl->cb);
}
static struct platform_driver lm3533_bl_driver = {
.driver = {
.name = "lm3533-backlight",
.pm = &lm3533_bl_pm_ops,
},
.probe = lm3533_bl_probe,
.remove = lm3533_bl_remove,
.shutdown = lm3533_bl_shutdown,
};
module_platform_driver(lm3533_bl_driver);
MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
MODULE_DESCRIPTION("LM3533 Backlight driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:lm3533-backlight");

View file

@ -0,0 +1,483 @@
/*
* Simple driver for Texas Instruments LM3630A Backlight driver chip
* Copyright (C) 2012 Texas Instruments
*
* 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/slab.h>
#include <linux/i2c.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/regmap.h>
#include <linux/pwm.h>
#include <linux/platform_data/lm3630a_bl.h>
#define REG_CTRL 0x00
#define REG_BOOST 0x02
#define REG_CONFIG 0x01
#define REG_BRT_A 0x03
#define REG_BRT_B 0x04
#define REG_I_A 0x05
#define REG_I_B 0x06
#define REG_INT_STATUS 0x09
#define REG_INT_EN 0x0A
#define REG_FAULT 0x0B
#define REG_PWM_OUTLOW 0x12
#define REG_PWM_OUTHIGH 0x13
#define REG_MAX 0x1F
#define INT_DEBOUNCE_MSEC 10
struct lm3630a_chip {
struct device *dev;
struct delayed_work work;
int irq;
struct workqueue_struct *irqthread;
struct lm3630a_platform_data *pdata;
struct backlight_device *bleda;
struct backlight_device *bledb;
struct regmap *regmap;
struct pwm_device *pwmd;
};
/* i2c access */
static int lm3630a_read(struct lm3630a_chip *pchip, unsigned int reg)
{
int rval;
unsigned int reg_val;
rval = regmap_read(pchip->regmap, reg, &reg_val);
if (rval < 0)
return rval;
return reg_val & 0xFF;
}
static int lm3630a_write(struct lm3630a_chip *pchip,
unsigned int reg, unsigned int data)
{
return regmap_write(pchip->regmap, reg, data);
}
static int lm3630a_update(struct lm3630a_chip *pchip,
unsigned int reg, unsigned int mask,
unsigned int data)
{
return regmap_update_bits(pchip->regmap, reg, mask, data);
}
/* initialize chip */
static int lm3630a_chip_init(struct lm3630a_chip *pchip)
{
int rval;
struct lm3630a_platform_data *pdata = pchip->pdata;
usleep_range(1000, 2000);
/* set Filter Strength Register */
rval = lm3630a_write(pchip, 0x50, 0x03);
/* set Cofig. register */
rval |= lm3630a_update(pchip, REG_CONFIG, 0x07, pdata->pwm_ctrl);
/* set boost control */
rval |= lm3630a_write(pchip, REG_BOOST, 0x38);
/* set current A */
rval |= lm3630a_update(pchip, REG_I_A, 0x1F, 0x1F);
/* set current B */
rval |= lm3630a_write(pchip, REG_I_B, 0x1F);
/* set control */
rval |= lm3630a_update(pchip, REG_CTRL, 0x14, pdata->leda_ctrl);
rval |= lm3630a_update(pchip, REG_CTRL, 0x0B, pdata->ledb_ctrl);
usleep_range(1000, 2000);
/* set brightness A and B */
rval |= lm3630a_write(pchip, REG_BRT_A, pdata->leda_init_brt);
rval |= lm3630a_write(pchip, REG_BRT_B, pdata->ledb_init_brt);
if (rval < 0)
dev_err(pchip->dev, "i2c failed to access register\n");
return rval;
}
/* interrupt handling */
static void lm3630a_delayed_func(struct work_struct *work)
{
int rval;
struct lm3630a_chip *pchip;
pchip = container_of(work, struct lm3630a_chip, work.work);
rval = lm3630a_read(pchip, REG_INT_STATUS);
if (rval < 0) {
dev_err(pchip->dev,
"i2c failed to access REG_INT_STATUS Register\n");
return;
}
dev_info(pchip->dev, "REG_INT_STATUS Register is 0x%x\n", rval);
}
static irqreturn_t lm3630a_isr_func(int irq, void *chip)
{
int rval;
struct lm3630a_chip *pchip = chip;
unsigned long delay = msecs_to_jiffies(INT_DEBOUNCE_MSEC);
queue_delayed_work(pchip->irqthread, &pchip->work, delay);
rval = lm3630a_update(pchip, REG_CTRL, 0x80, 0x00);
if (rval < 0) {
dev_err(pchip->dev, "i2c failed to access register\n");
return IRQ_NONE;
}
return IRQ_HANDLED;
}
static int lm3630a_intr_config(struct lm3630a_chip *pchip)
{
int rval;
rval = lm3630a_write(pchip, REG_INT_EN, 0x87);
if (rval < 0)
return rval;
INIT_DELAYED_WORK(&pchip->work, lm3630a_delayed_func);
pchip->irqthread = create_singlethread_workqueue("lm3630a-irqthd");
if (!pchip->irqthread) {
dev_err(pchip->dev, "create irq thread fail\n");
return -ENOMEM;
}
if (request_threaded_irq
(pchip->irq, NULL, lm3630a_isr_func,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "lm3630a_irq", pchip)) {
dev_err(pchip->dev, "request threaded irq fail\n");
destroy_workqueue(pchip->irqthread);
return -ENOMEM;
}
return rval;
}
static void lm3630a_pwm_ctrl(struct lm3630a_chip *pchip, int br, int br_max)
{
unsigned int period = pwm_get_period(pchip->pwmd);
unsigned int duty = br * period / br_max;
pwm_config(pchip->pwmd, duty, period);
if (duty)
pwm_enable(pchip->pwmd);
else
pwm_disable(pchip->pwmd);
}
/* update and get brightness */
static int lm3630a_bank_a_update_status(struct backlight_device *bl)
{
int ret;
struct lm3630a_chip *pchip = bl_get_data(bl);
enum lm3630a_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl;
/* pwm control */
if ((pwm_ctrl & LM3630A_PWM_BANK_A) != 0) {
lm3630a_pwm_ctrl(pchip, bl->props.brightness,
bl->props.max_brightness);
return bl->props.brightness;
}
/* disable sleep */
ret = lm3630a_update(pchip, REG_CTRL, 0x80, 0x00);
if (ret < 0)
goto out_i2c_err;
usleep_range(1000, 2000);
/* minimum brightness is 0x04 */
ret = lm3630a_write(pchip, REG_BRT_A, bl->props.brightness);
if (bl->props.brightness < 0x4)
ret |= lm3630a_update(pchip, REG_CTRL, LM3630A_LEDA_ENABLE, 0);
else
ret |= lm3630a_update(pchip, REG_CTRL,
LM3630A_LEDA_ENABLE, LM3630A_LEDA_ENABLE);
if (ret < 0)
goto out_i2c_err;
return bl->props.brightness;
out_i2c_err:
dev_err(pchip->dev, "i2c failed to access\n");
return bl->props.brightness;
}
static int lm3630a_bank_a_get_brightness(struct backlight_device *bl)
{
int brightness, rval;
struct lm3630a_chip *pchip = bl_get_data(bl);
enum lm3630a_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl;
if ((pwm_ctrl & LM3630A_PWM_BANK_A) != 0) {
rval = lm3630a_read(pchip, REG_PWM_OUTHIGH);
if (rval < 0)
goto out_i2c_err;
brightness = (rval & 0x01) << 8;
rval = lm3630a_read(pchip, REG_PWM_OUTLOW);
if (rval < 0)
goto out_i2c_err;
brightness |= rval;
goto out;
}
/* disable sleep */
rval = lm3630a_update(pchip, REG_CTRL, 0x80, 0x00);
if (rval < 0)
goto out_i2c_err;
usleep_range(1000, 2000);
rval = lm3630a_read(pchip, REG_BRT_A);
if (rval < 0)
goto out_i2c_err;
brightness = rval;
out:
bl->props.brightness = brightness;
return bl->props.brightness;
out_i2c_err:
dev_err(pchip->dev, "i2c failed to access register\n");
return 0;
}
static const struct backlight_ops lm3630a_bank_a_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = lm3630a_bank_a_update_status,
.get_brightness = lm3630a_bank_a_get_brightness,
};
/* update and get brightness */
static int lm3630a_bank_b_update_status(struct backlight_device *bl)
{
int ret;
struct lm3630a_chip *pchip = bl_get_data(bl);
enum lm3630a_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl;
/* pwm control */
if ((pwm_ctrl & LM3630A_PWM_BANK_B) != 0) {
lm3630a_pwm_ctrl(pchip, bl->props.brightness,
bl->props.max_brightness);
return bl->props.brightness;
}
/* disable sleep */
ret = lm3630a_update(pchip, REG_CTRL, 0x80, 0x00);
if (ret < 0)
goto out_i2c_err;
usleep_range(1000, 2000);
/* minimum brightness is 0x04 */
ret = lm3630a_write(pchip, REG_BRT_B, bl->props.brightness);
if (bl->props.brightness < 0x4)
ret |= lm3630a_update(pchip, REG_CTRL, LM3630A_LEDB_ENABLE, 0);
else
ret |= lm3630a_update(pchip, REG_CTRL,
LM3630A_LEDB_ENABLE, LM3630A_LEDB_ENABLE);
if (ret < 0)
goto out_i2c_err;
return bl->props.brightness;
out_i2c_err:
dev_err(pchip->dev, "i2c failed to access REG_CTRL\n");
return bl->props.brightness;
}
static int lm3630a_bank_b_get_brightness(struct backlight_device *bl)
{
int brightness, rval;
struct lm3630a_chip *pchip = bl_get_data(bl);
enum lm3630a_pwm_ctrl pwm_ctrl = pchip->pdata->pwm_ctrl;
if ((pwm_ctrl & LM3630A_PWM_BANK_B) != 0) {
rval = lm3630a_read(pchip, REG_PWM_OUTHIGH);
if (rval < 0)
goto out_i2c_err;
brightness = (rval & 0x01) << 8;
rval = lm3630a_read(pchip, REG_PWM_OUTLOW);
if (rval < 0)
goto out_i2c_err;
brightness |= rval;
goto out;
}
/* disable sleep */
rval = lm3630a_update(pchip, REG_CTRL, 0x80, 0x00);
if (rval < 0)
goto out_i2c_err;
usleep_range(1000, 2000);
rval = lm3630a_read(pchip, REG_BRT_B);
if (rval < 0)
goto out_i2c_err;
brightness = rval;
out:
bl->props.brightness = brightness;
return bl->props.brightness;
out_i2c_err:
dev_err(pchip->dev, "i2c failed to access register\n");
return 0;
}
static const struct backlight_ops lm3630a_bank_b_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = lm3630a_bank_b_update_status,
.get_brightness = lm3630a_bank_b_get_brightness,
};
static int lm3630a_backlight_register(struct lm3630a_chip *pchip)
{
struct backlight_properties props;
struct lm3630a_platform_data *pdata = pchip->pdata;
props.type = BACKLIGHT_RAW;
if (pdata->leda_ctrl != LM3630A_LEDA_DISABLE) {
props.brightness = pdata->leda_init_brt;
props.max_brightness = pdata->leda_max_brt;
pchip->bleda =
devm_backlight_device_register(pchip->dev, "lm3630a_leda",
pchip->dev, pchip,
&lm3630a_bank_a_ops, &props);
if (IS_ERR(pchip->bleda))
return PTR_ERR(pchip->bleda);
}
if ((pdata->ledb_ctrl != LM3630A_LEDB_DISABLE) &&
(pdata->ledb_ctrl != LM3630A_LEDB_ON_A)) {
props.brightness = pdata->ledb_init_brt;
props.max_brightness = pdata->ledb_max_brt;
pchip->bledb =
devm_backlight_device_register(pchip->dev, "lm3630a_ledb",
pchip->dev, pchip,
&lm3630a_bank_b_ops, &props);
if (IS_ERR(pchip->bledb))
return PTR_ERR(pchip->bledb);
}
return 0;
}
static const struct regmap_config lm3630a_regmap = {
.reg_bits = 8,
.val_bits = 8,
.max_register = REG_MAX,
};
static int lm3630a_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct lm3630a_platform_data *pdata = dev_get_platdata(&client->dev);
struct lm3630a_chip *pchip;
int rval;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "fail : i2c functionality check\n");
return -EOPNOTSUPP;
}
pchip = devm_kzalloc(&client->dev, sizeof(struct lm3630a_chip),
GFP_KERNEL);
if (!pchip)
return -ENOMEM;
pchip->dev = &client->dev;
pchip->regmap = devm_regmap_init_i2c(client, &lm3630a_regmap);
if (IS_ERR(pchip->regmap)) {
rval = PTR_ERR(pchip->regmap);
dev_err(&client->dev, "fail : allocate reg. map: %d\n", rval);
return rval;
}
i2c_set_clientdata(client, pchip);
if (pdata == NULL) {
pdata = devm_kzalloc(pchip->dev,
sizeof(struct lm3630a_platform_data),
GFP_KERNEL);
if (pdata == NULL)
return -ENOMEM;
/* default values */
pdata->leda_ctrl = LM3630A_LEDA_ENABLE;
pdata->ledb_ctrl = LM3630A_LEDB_ENABLE;
pdata->leda_max_brt = LM3630A_MAX_BRIGHTNESS;
pdata->ledb_max_brt = LM3630A_MAX_BRIGHTNESS;
pdata->leda_init_brt = LM3630A_MAX_BRIGHTNESS;
pdata->ledb_init_brt = LM3630A_MAX_BRIGHTNESS;
}
pchip->pdata = pdata;
/* chip initialize */
rval = lm3630a_chip_init(pchip);
if (rval < 0) {
dev_err(&client->dev, "fail : init chip\n");
return rval;
}
/* backlight register */
rval = lm3630a_backlight_register(pchip);
if (rval < 0) {
dev_err(&client->dev, "fail : backlight register.\n");
return rval;
}
/* pwm */
if (pdata->pwm_ctrl != LM3630A_PWM_DISABLE) {
pchip->pwmd = devm_pwm_get(pchip->dev, "lm3630a-pwm");
if (IS_ERR(pchip->pwmd)) {
dev_err(&client->dev, "fail : get pwm device\n");
return PTR_ERR(pchip->pwmd);
}
}
pchip->pwmd->period = pdata->pwm_period;
/* interrupt enable : irq 0 is not allowed */
pchip->irq = client->irq;
if (pchip->irq) {
rval = lm3630a_intr_config(pchip);
if (rval < 0)
return rval;
}
dev_info(&client->dev, "LM3630A backlight register OK.\n");
return 0;
}
static int lm3630a_remove(struct i2c_client *client)
{
int rval;
struct lm3630a_chip *pchip = i2c_get_clientdata(client);
rval = lm3630a_write(pchip, REG_BRT_A, 0);
if (rval < 0)
dev_err(pchip->dev, "i2c failed to access register\n");
rval = lm3630a_write(pchip, REG_BRT_B, 0);
if (rval < 0)
dev_err(pchip->dev, "i2c failed to access register\n");
if (pchip->irq) {
free_irq(pchip->irq, pchip);
flush_workqueue(pchip->irqthread);
destroy_workqueue(pchip->irqthread);
}
return 0;
}
static const struct i2c_device_id lm3630a_id[] = {
{LM3630A_NAME, 0},
{}
};
MODULE_DEVICE_TABLE(i2c, lm3630a_id);
static struct i2c_driver lm3630a_i2c_driver = {
.driver = {
.name = LM3630A_NAME,
},
.probe = lm3630a_probe,
.remove = lm3630a_remove,
.id_table = lm3630a_id,
};
module_i2c_driver(lm3630a_i2c_driver);
MODULE_DESCRIPTION("Texas Instruments Backlight driver for LM3630A");
MODULE_AUTHOR("Daniel Jeong <gshark.jeong@gmail.com>");
MODULE_AUTHOR("LDD MLP <ldd-mlp@list.ti.com>");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,432 @@
/*
* Simple driver for Texas Instruments LM3639 Backlight + Flash LED driver chip
* Copyright (C) 2012 Texas Instruments
*
* 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/slab.h>
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/regmap.h>
#include <linux/platform_data/lm3639_bl.h>
#define REG_DEV_ID 0x00
#define REG_CHECKSUM 0x01
#define REG_BL_CONF_1 0x02
#define REG_BL_CONF_2 0x03
#define REG_BL_CONF_3 0x04
#define REG_BL_CONF_4 0x05
#define REG_FL_CONF_1 0x06
#define REG_FL_CONF_2 0x07
#define REG_FL_CONF_3 0x08
#define REG_IO_CTRL 0x09
#define REG_ENABLE 0x0A
#define REG_FLAG 0x0B
#define REG_MAX REG_FLAG
struct lm3639_chip_data {
struct device *dev;
struct lm3639_platform_data *pdata;
struct backlight_device *bled;
struct led_classdev cdev_flash;
struct led_classdev cdev_torch;
struct regmap *regmap;
unsigned int bled_mode;
unsigned int bled_map;
unsigned int last_flag;
};
/* initialize chip */
static int lm3639_chip_init(struct lm3639_chip_data *pchip)
{
int ret;
unsigned int reg_val;
struct lm3639_platform_data *pdata = pchip->pdata;
/* input pins config. */
ret =
regmap_update_bits(pchip->regmap, REG_BL_CONF_1, 0x08,
pdata->pin_pwm);
if (ret < 0)
goto out;
reg_val = (pdata->pin_pwm & 0x40) | pdata->pin_strobe | pdata->pin_tx;
ret = regmap_update_bits(pchip->regmap, REG_IO_CTRL, 0x7C, reg_val);
if (ret < 0)
goto out;
/* init brightness */
ret = regmap_write(pchip->regmap, REG_BL_CONF_4, pdata->init_brt_led);
if (ret < 0)
goto out;
ret = regmap_write(pchip->regmap, REG_BL_CONF_3, pdata->init_brt_led);
if (ret < 0)
goto out;
/* output pins config. */
if (!pdata->init_brt_led) {
reg_val = pdata->fled_pins;
reg_val |= pdata->bled_pins;
} else {
reg_val = pdata->fled_pins;
reg_val |= pdata->bled_pins | 0x01;
}
ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x79, reg_val);
if (ret < 0)
goto out;
return ret;
out:
dev_err(pchip->dev, "i2c failed to access register\n");
return ret;
}
/* update and get brightness */
static int lm3639_bled_update_status(struct backlight_device *bl)
{
int ret;
unsigned int reg_val;
struct lm3639_chip_data *pchip = bl_get_data(bl);
struct lm3639_platform_data *pdata = pchip->pdata;
ret = regmap_read(pchip->regmap, REG_FLAG, &reg_val);
if (ret < 0)
goto out;
if (reg_val != 0)
dev_info(pchip->dev, "last flag is 0x%x\n", reg_val);
/* pwm control */
if (pdata->pin_pwm) {
if (pdata->pwm_set_intensity)
pdata->pwm_set_intensity(bl->props.brightness,
pdata->max_brt_led);
else
dev_err(pchip->dev,
"No pwm control func. in plat-data\n");
return bl->props.brightness;
}
/* i2c control and set brigtness */
ret = regmap_write(pchip->regmap, REG_BL_CONF_4, bl->props.brightness);
if (ret < 0)
goto out;
ret = regmap_write(pchip->regmap, REG_BL_CONF_3, bl->props.brightness);
if (ret < 0)
goto out;
if (!bl->props.brightness)
ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x01, 0x00);
else
ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x01, 0x01);
if (ret < 0)
goto out;
return bl->props.brightness;
out:
dev_err(pchip->dev, "i2c failed to access registers\n");
return bl->props.brightness;
}
static int lm3639_bled_get_brightness(struct backlight_device *bl)
{
int ret;
unsigned int reg_val;
struct lm3639_chip_data *pchip = bl_get_data(bl);
struct lm3639_platform_data *pdata = pchip->pdata;
if (pdata->pin_pwm) {
if (pdata->pwm_get_intensity)
bl->props.brightness = pdata->pwm_get_intensity();
else
dev_err(pchip->dev,
"No pwm control func. in plat-data\n");
return bl->props.brightness;
}
ret = regmap_read(pchip->regmap, REG_BL_CONF_1, &reg_val);
if (ret < 0)
goto out;
if (reg_val & 0x10)
ret = regmap_read(pchip->regmap, REG_BL_CONF_4, &reg_val);
else
ret = regmap_read(pchip->regmap, REG_BL_CONF_3, &reg_val);
if (ret < 0)
goto out;
bl->props.brightness = reg_val;
return bl->props.brightness;
out:
dev_err(pchip->dev, "i2c failed to access register\n");
return bl->props.brightness;
}
static const struct backlight_ops lm3639_bled_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = lm3639_bled_update_status,
.get_brightness = lm3639_bled_get_brightness,
};
/* backlight mapping mode */
static ssize_t lm3639_bled_mode_store(struct device *dev,
struct device_attribute *devAttr,
const char *buf, size_t size)
{
ssize_t ret;
struct lm3639_chip_data *pchip = dev_get_drvdata(dev);
unsigned int state;
ret = kstrtouint(buf, 10, &state);
if (ret)
goto out_input;
if (!state)
ret =
regmap_update_bits(pchip->regmap, REG_BL_CONF_1, 0x10,
0x00);
else
ret =
regmap_update_bits(pchip->regmap, REG_BL_CONF_1, 0x10,
0x10);
if (ret < 0)
goto out;
return size;
out:
dev_err(pchip->dev, "%s:i2c access fail to register\n", __func__);
return ret;
out_input:
dev_err(pchip->dev, "%s:input conversion fail\n", __func__);
return ret;
}
static DEVICE_ATTR(bled_mode, S_IWUSR, NULL, lm3639_bled_mode_store);
/* torch */
static void lm3639_torch_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
int ret;
unsigned int reg_val;
struct lm3639_chip_data *pchip;
pchip = container_of(cdev, struct lm3639_chip_data, cdev_torch);
ret = regmap_read(pchip->regmap, REG_FLAG, &reg_val);
if (ret < 0)
goto out;
if (reg_val != 0)
dev_info(pchip->dev, "last flag is 0x%x\n", reg_val);
/* brightness 0 means off state */
if (!brightness) {
ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x00);
if (ret < 0)
goto out;
return;
}
ret = regmap_update_bits(pchip->regmap,
REG_FL_CONF_1, 0x70, (brightness - 1) << 4);
if (ret < 0)
goto out;
ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x02);
if (ret < 0)
goto out;
return;
out:
dev_err(pchip->dev, "i2c failed to access register\n");
}
/* flash */
static void lm3639_flash_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
int ret;
unsigned int reg_val;
struct lm3639_chip_data *pchip;
pchip = container_of(cdev, struct lm3639_chip_data, cdev_flash);
ret = regmap_read(pchip->regmap, REG_FLAG, &reg_val);
if (ret < 0)
goto out;
if (reg_val != 0)
dev_info(pchip->dev, "last flag is 0x%x\n", reg_val);
/* torch off before flash control */
ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x00);
if (ret < 0)
goto out;
/* brightness 0 means off state */
if (!brightness)
return;
ret = regmap_update_bits(pchip->regmap,
REG_FL_CONF_1, 0x0F, brightness - 1);
if (ret < 0)
goto out;
ret = regmap_update_bits(pchip->regmap, REG_ENABLE, 0x06, 0x06);
if (ret < 0)
goto out;
return;
out:
dev_err(pchip->dev, "i2c failed to access register\n");
}
static const struct regmap_config lm3639_regmap = {
.reg_bits = 8,
.val_bits = 8,
.max_register = REG_MAX,
};
static int lm3639_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret;
struct lm3639_chip_data *pchip;
struct lm3639_platform_data *pdata = dev_get_platdata(&client->dev);
struct backlight_properties props;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(&client->dev, "i2c functionality check fail.\n");
return -EOPNOTSUPP;
}
if (pdata == NULL) {
dev_err(&client->dev, "Needs Platform Data.\n");
return -ENODATA;
}
pchip = devm_kzalloc(&client->dev,
sizeof(struct lm3639_chip_data), GFP_KERNEL);
if (!pchip)
return -ENOMEM;
pchip->pdata = pdata;
pchip->dev = &client->dev;
pchip->regmap = devm_regmap_init_i2c(client, &lm3639_regmap);
if (IS_ERR(pchip->regmap)) {
ret = PTR_ERR(pchip->regmap);
dev_err(&client->dev, "fail : allocate register map: %d\n",
ret);
return ret;
}
i2c_set_clientdata(client, pchip);
/* chip initialize */
ret = lm3639_chip_init(pchip);
if (ret < 0) {
dev_err(&client->dev, "fail : chip init\n");
goto err_out;
}
/* backlight */
props.type = BACKLIGHT_RAW;
props.brightness = pdata->init_brt_led;
props.max_brightness = pdata->max_brt_led;
pchip->bled =
devm_backlight_device_register(pchip->dev, "lm3639_bled",
pchip->dev, pchip, &lm3639_bled_ops,
&props);
if (IS_ERR(pchip->bled)) {
dev_err(&client->dev, "fail : backlight register\n");
ret = PTR_ERR(pchip->bled);
goto err_out;
}
ret = device_create_file(&(pchip->bled->dev), &dev_attr_bled_mode);
if (ret < 0) {
dev_err(&client->dev, "failed : add sysfs entries\n");
goto err_out;
}
/* flash */
pchip->cdev_flash.name = "lm3639_flash";
pchip->cdev_flash.max_brightness = 16;
pchip->cdev_flash.brightness_set = lm3639_flash_brightness_set;
ret = led_classdev_register((struct device *)
&client->dev, &pchip->cdev_flash);
if (ret < 0) {
dev_err(&client->dev, "fail : flash register\n");
goto err_flash;
}
/* torch */
pchip->cdev_torch.name = "lm3639_torch";
pchip->cdev_torch.max_brightness = 8;
pchip->cdev_torch.brightness_set = lm3639_torch_brightness_set;
ret = led_classdev_register((struct device *)
&client->dev, &pchip->cdev_torch);
if (ret < 0) {
dev_err(&client->dev, "fail : torch register\n");
goto err_torch;
}
return 0;
err_torch:
led_classdev_unregister(&pchip->cdev_flash);
err_flash:
device_remove_file(&(pchip->bled->dev), &dev_attr_bled_mode);
err_out:
return ret;
}
static int lm3639_remove(struct i2c_client *client)
{
struct lm3639_chip_data *pchip = i2c_get_clientdata(client);
regmap_write(pchip->regmap, REG_ENABLE, 0x00);
if (&pchip->cdev_torch)
led_classdev_unregister(&pchip->cdev_torch);
if (&pchip->cdev_flash)
led_classdev_unregister(&pchip->cdev_flash);
if (pchip->bled)
device_remove_file(&(pchip->bled->dev), &dev_attr_bled_mode);
return 0;
}
static const struct i2c_device_id lm3639_id[] = {
{LM3639_NAME, 0},
{}
};
MODULE_DEVICE_TABLE(i2c, lm3639_id);
static struct i2c_driver lm3639_i2c_driver = {
.driver = {
.name = LM3639_NAME,
},
.probe = lm3639_probe,
.remove = lm3639_remove,
.id_table = lm3639_id,
};
module_i2c_driver(lm3639_i2c_driver);
MODULE_DESCRIPTION("Texas Instruments Backlight+Flash LED driver for LM3639");
MODULE_AUTHOR("Daniel Jeong <gshark.jeong@gmail.com>");
MODULE_AUTHOR("Ldd Mlp <ldd-mlp@list.ti.com>");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,204 @@
/*
* lms283gf05.c -- support for Samsung LMS283GF05 LCD
*
* Copyright (c) 2009 Marek Vasut <marek.vasut@gmail.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/device.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/lcd.h>
#include <linux/spi/spi.h>
#include <linux/spi/lms283gf05.h>
#include <linux/module.h>
struct lms283gf05_state {
struct spi_device *spi;
struct lcd_device *ld;
};
struct lms283gf05_seq {
unsigned char reg;
unsigned short value;
unsigned char delay;
};
/* Magic sequences supplied by manufacturer, for details refer to datasheet */
static const struct lms283gf05_seq disp_initseq[] = {
/* REG, VALUE, DELAY */
{ 0x07, 0x0000, 0 },
{ 0x13, 0x0000, 10 },
{ 0x11, 0x3004, 0 },
{ 0x14, 0x200F, 0 },
{ 0x10, 0x1a20, 0 },
{ 0x13, 0x0040, 50 },
{ 0x13, 0x0060, 0 },
{ 0x13, 0x0070, 200 },
{ 0x01, 0x0127, 0 },
{ 0x02, 0x0700, 0 },
{ 0x03, 0x1030, 0 },
{ 0x08, 0x0208, 0 },
{ 0x0B, 0x0620, 0 },
{ 0x0C, 0x0110, 0 },
{ 0x30, 0x0120, 0 },
{ 0x31, 0x0127, 0 },
{ 0x32, 0x0000, 0 },
{ 0x33, 0x0503, 0 },
{ 0x34, 0x0727, 0 },
{ 0x35, 0x0124, 0 },
{ 0x36, 0x0706, 0 },
{ 0x37, 0x0701, 0 },
{ 0x38, 0x0F00, 0 },
{ 0x39, 0x0F00, 0 },
{ 0x40, 0x0000, 0 },
{ 0x41, 0x0000, 0 },
{ 0x42, 0x013f, 0 },
{ 0x43, 0x0000, 0 },
{ 0x44, 0x013f, 0 },
{ 0x45, 0x0000, 0 },
{ 0x46, 0xef00, 0 },
{ 0x47, 0x013f, 0 },
{ 0x48, 0x0000, 0 },
{ 0x07, 0x0015, 30 },
{ 0x07, 0x0017, 0 },
{ 0x20, 0x0000, 0 },
{ 0x21, 0x0000, 0 },
{ 0x22, 0x0000, 0 }
};
static const struct lms283gf05_seq disp_pdwnseq[] = {
{ 0x07, 0x0016, 30 },
{ 0x07, 0x0004, 0 },
{ 0x10, 0x0220, 20 },
{ 0x13, 0x0060, 50 },
{ 0x13, 0x0040, 50 },
{ 0x13, 0x0000, 0 },
{ 0x10, 0x0000, 0 }
};
static void lms283gf05_reset(unsigned long gpio, bool inverted)
{
gpio_set_value(gpio, !inverted);
mdelay(100);
gpio_set_value(gpio, inverted);
mdelay(20);
gpio_set_value(gpio, !inverted);
mdelay(20);
}
static void lms283gf05_toggle(struct spi_device *spi,
const struct lms283gf05_seq *seq, int sz)
{
char buf[3];
int i;
for (i = 0; i < sz; i++) {
buf[0] = 0x74;
buf[1] = 0x00;
buf[2] = seq[i].reg;
spi_write(spi, buf, 3);
buf[0] = 0x76;
buf[1] = seq[i].value >> 8;
buf[2] = seq[i].value & 0xff;
spi_write(spi, buf, 3);
mdelay(seq[i].delay);
}
}
static int lms283gf05_power_set(struct lcd_device *ld, int power)
{
struct lms283gf05_state *st = lcd_get_data(ld);
struct spi_device *spi = st->spi;
struct lms283gf05_pdata *pdata = dev_get_platdata(&spi->dev);
if (power <= FB_BLANK_NORMAL) {
if (pdata)
lms283gf05_reset(pdata->reset_gpio,
pdata->reset_inverted);
lms283gf05_toggle(spi, disp_initseq, ARRAY_SIZE(disp_initseq));
} else {
lms283gf05_toggle(spi, disp_pdwnseq, ARRAY_SIZE(disp_pdwnseq));
if (pdata)
gpio_set_value(pdata->reset_gpio,
pdata->reset_inverted);
}
return 0;
}
static struct lcd_ops lms_ops = {
.set_power = lms283gf05_power_set,
.get_power = NULL,
};
static int lms283gf05_probe(struct spi_device *spi)
{
struct lms283gf05_state *st;
struct lms283gf05_pdata *pdata = dev_get_platdata(&spi->dev);
struct lcd_device *ld;
int ret = 0;
if (pdata != NULL) {
ret = devm_gpio_request_one(&spi->dev, pdata->reset_gpio,
GPIOF_DIR_OUT | (!pdata->reset_inverted ?
GPIOF_INIT_HIGH : GPIOF_INIT_LOW),
"LMS285GF05 RESET");
if (ret)
return ret;
}
st = devm_kzalloc(&spi->dev, sizeof(struct lms283gf05_state),
GFP_KERNEL);
if (st == NULL)
return -ENOMEM;
ld = devm_lcd_device_register(&spi->dev, "lms283gf05", &spi->dev, st,
&lms_ops);
if (IS_ERR(ld))
return PTR_ERR(ld);
st->spi = spi;
st->ld = ld;
spi_set_drvdata(spi, st);
/* kick in the LCD */
if (pdata)
lms283gf05_reset(pdata->reset_gpio, pdata->reset_inverted);
lms283gf05_toggle(spi, disp_initseq, ARRAY_SIZE(disp_initseq));
return 0;
}
static struct spi_driver lms283gf05_driver = {
.driver = {
.name = "lms283gf05",
.owner = THIS_MODULE,
},
.probe = lms283gf05_probe,
};
module_spi_driver(lms283gf05_driver);
MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
MODULE_DESCRIPTION("LCD283GF05 LCD");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,437 @@
/*
* lms501kf03 TFT LCD panel driver.
*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* Author: Jingoo Han <jg1.han@samsung.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/wait.h>
#define COMMAND_ONLY 0x00
#define DATA_ONLY 0x01
struct lms501kf03 {
struct device *dev;
struct spi_device *spi;
unsigned int power;
struct lcd_device *ld;
struct lcd_platform_data *lcd_pd;
};
static const unsigned char seq_password[] = {
0xb9, 0xff, 0x83, 0x69,
};
static const unsigned char seq_power[] = {
0xb1, 0x01, 0x00, 0x34, 0x06, 0x00, 0x14, 0x14, 0x20, 0x28,
0x12, 0x12, 0x17, 0x0a, 0x01, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6,
};
static const unsigned char seq_display[] = {
0xb2, 0x00, 0x2b, 0x03, 0x03, 0x70, 0x00, 0xff, 0x00, 0x00,
0x00, 0x00, 0x03, 0x03, 0x00, 0x01,
};
static const unsigned char seq_rgb_if[] = {
0xb3, 0x09,
};
static const unsigned char seq_display_inv[] = {
0xb4, 0x01, 0x08, 0x77, 0x0e, 0x06,
};
static const unsigned char seq_vcom[] = {
0xb6, 0x4c, 0x2e,
};
static const unsigned char seq_gate[] = {
0xd5, 0x00, 0x05, 0x03, 0x29, 0x01, 0x07, 0x17, 0x68, 0x13,
0x37, 0x20, 0x31, 0x8a, 0x46, 0x9b, 0x57, 0x13, 0x02, 0x75,
0xb9, 0x64, 0xa8, 0x07, 0x0f, 0x04, 0x07,
};
static const unsigned char seq_panel[] = {
0xcc, 0x02,
};
static const unsigned char seq_col_mod[] = {
0x3a, 0x77,
};
static const unsigned char seq_w_gamma[] = {
0xe0, 0x00, 0x04, 0x09, 0x0f, 0x1f, 0x3f, 0x1f, 0x2f, 0x0a,
0x0f, 0x10, 0x16, 0x18, 0x16, 0x17, 0x0d, 0x15, 0x00, 0x04,
0x09, 0x0f, 0x38, 0x3f, 0x20, 0x39, 0x0a, 0x0f, 0x10, 0x16,
0x18, 0x16, 0x17, 0x0d, 0x15,
};
static const unsigned char seq_rgb_gamma[] = {
0xc1, 0x01, 0x03, 0x07, 0x0f, 0x1a, 0x22, 0x2c, 0x33, 0x3c,
0x46, 0x4f, 0x58, 0x60, 0x69, 0x71, 0x79, 0x82, 0x89, 0x92,
0x9a, 0xa1, 0xa9, 0xb1, 0xb9, 0xc1, 0xc9, 0xcf, 0xd6, 0xde,
0xe5, 0xec, 0xf3, 0xf9, 0xff, 0xdd, 0x39, 0x07, 0x1c, 0xcb,
0xab, 0x5f, 0x49, 0x80, 0x03, 0x07, 0x0f, 0x19, 0x20, 0x2a,
0x31, 0x39, 0x42, 0x4b, 0x53, 0x5b, 0x63, 0x6b, 0x73, 0x7b,
0x83, 0x8a, 0x92, 0x9b, 0xa2, 0xaa, 0xb2, 0xba, 0xc2, 0xca,
0xd0, 0xd8, 0xe1, 0xe8, 0xf0, 0xf8, 0xff, 0xf7, 0xd8, 0xbe,
0xa7, 0x39, 0x40, 0x85, 0x8c, 0xc0, 0x04, 0x07, 0x0c, 0x17,
0x1c, 0x23, 0x2b, 0x34, 0x3b, 0x43, 0x4c, 0x54, 0x5b, 0x63,
0x6a, 0x73, 0x7a, 0x82, 0x8a, 0x91, 0x98, 0xa1, 0xa8, 0xb0,
0xb7, 0xc1, 0xc9, 0xcf, 0xd9, 0xe3, 0xea, 0xf4, 0xff, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const unsigned char seq_up_dn[] = {
0x36, 0x10,
};
static const unsigned char seq_sleep_in[] = {
0x10,
};
static const unsigned char seq_sleep_out[] = {
0x11,
};
static const unsigned char seq_display_on[] = {
0x29,
};
static const unsigned char seq_display_off[] = {
0x10,
};
static int lms501kf03_spi_write_byte(struct lms501kf03 *lcd, int addr, int data)
{
u16 buf[1];
struct spi_message msg;
struct spi_transfer xfer = {
.len = 2,
.tx_buf = buf,
};
buf[0] = (addr << 8) | data;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
return spi_sync(lcd->spi, &msg);
}
static int lms501kf03_spi_write(struct lms501kf03 *lcd, unsigned char address,
unsigned char command)
{
return lms501kf03_spi_write_byte(lcd, address, command);
}
static int lms501kf03_panel_send_sequence(struct lms501kf03 *lcd,
const unsigned char *wbuf,
unsigned int len)
{
int ret = 0, i = 0;
while (i < len) {
if (i == 0)
ret = lms501kf03_spi_write(lcd, COMMAND_ONLY, wbuf[i]);
else
ret = lms501kf03_spi_write(lcd, DATA_ONLY, wbuf[i]);
if (ret)
break;
i += 1;
}
return ret;
}
static int lms501kf03_ldi_init(struct lms501kf03 *lcd)
{
int ret, i;
static const unsigned char *init_seq[] = {
seq_password,
seq_power,
seq_display,
seq_rgb_if,
seq_display_inv,
seq_vcom,
seq_gate,
seq_panel,
seq_col_mod,
seq_w_gamma,
seq_rgb_gamma,
seq_sleep_out,
};
static const unsigned int size_seq[] = {
ARRAY_SIZE(seq_password),
ARRAY_SIZE(seq_power),
ARRAY_SIZE(seq_display),
ARRAY_SIZE(seq_rgb_if),
ARRAY_SIZE(seq_display_inv),
ARRAY_SIZE(seq_vcom),
ARRAY_SIZE(seq_gate),
ARRAY_SIZE(seq_panel),
ARRAY_SIZE(seq_col_mod),
ARRAY_SIZE(seq_w_gamma),
ARRAY_SIZE(seq_rgb_gamma),
ARRAY_SIZE(seq_sleep_out),
};
for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
ret = lms501kf03_panel_send_sequence(lcd, init_seq[i],
size_seq[i]);
if (ret)
break;
}
/*
* According to the datasheet, 120ms delay time is required.
* After sleep out sequence, command is blocked for 120ms.
* Thus, LDI should wait for 120ms.
*/
msleep(120);
return ret;
}
static int lms501kf03_ldi_enable(struct lms501kf03 *lcd)
{
return lms501kf03_panel_send_sequence(lcd, seq_display_on,
ARRAY_SIZE(seq_display_on));
}
static int lms501kf03_ldi_disable(struct lms501kf03 *lcd)
{
return lms501kf03_panel_send_sequence(lcd, seq_display_off,
ARRAY_SIZE(seq_display_off));
}
static int lms501kf03_power_is_on(int power)
{
return (power) <= FB_BLANK_NORMAL;
}
static int lms501kf03_power_on(struct lms501kf03 *lcd)
{
int ret = 0;
struct lcd_platform_data *pd;
pd = lcd->lcd_pd;
if (!pd->power_on) {
dev_err(lcd->dev, "power_on is NULL.\n");
return -EINVAL;
}
pd->power_on(lcd->ld, 1);
msleep(pd->power_on_delay);
if (!pd->reset) {
dev_err(lcd->dev, "reset is NULL.\n");
return -EINVAL;
}
pd->reset(lcd->ld);
msleep(pd->reset_delay);
ret = lms501kf03_ldi_init(lcd);
if (ret) {
dev_err(lcd->dev, "failed to initialize ldi.\n");
return ret;
}
ret = lms501kf03_ldi_enable(lcd);
if (ret) {
dev_err(lcd->dev, "failed to enable ldi.\n");
return ret;
}
return 0;
}
static int lms501kf03_power_off(struct lms501kf03 *lcd)
{
int ret = 0;
struct lcd_platform_data *pd;
pd = lcd->lcd_pd;
ret = lms501kf03_ldi_disable(lcd);
if (ret) {
dev_err(lcd->dev, "lcd setting failed.\n");
return -EIO;
}
msleep(pd->power_off_delay);
pd->power_on(lcd->ld, 0);
return 0;
}
static int lms501kf03_power(struct lms501kf03 *lcd, int power)
{
int ret = 0;
if (lms501kf03_power_is_on(power) &&
!lms501kf03_power_is_on(lcd->power))
ret = lms501kf03_power_on(lcd);
else if (!lms501kf03_power_is_on(power) &&
lms501kf03_power_is_on(lcd->power))
ret = lms501kf03_power_off(lcd);
if (!ret)
lcd->power = power;
return ret;
}
static int lms501kf03_get_power(struct lcd_device *ld)
{
struct lms501kf03 *lcd = lcd_get_data(ld);
return lcd->power;
}
static int lms501kf03_set_power(struct lcd_device *ld, int power)
{
struct lms501kf03 *lcd = lcd_get_data(ld);
if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
power != FB_BLANK_NORMAL) {
dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
return -EINVAL;
}
return lms501kf03_power(lcd, power);
}
static struct lcd_ops lms501kf03_lcd_ops = {
.get_power = lms501kf03_get_power,
.set_power = lms501kf03_set_power,
};
static int lms501kf03_probe(struct spi_device *spi)
{
struct lms501kf03 *lcd = NULL;
struct lcd_device *ld = NULL;
int ret = 0;
lcd = devm_kzalloc(&spi->dev, sizeof(struct lms501kf03), GFP_KERNEL);
if (!lcd)
return -ENOMEM;
/* lms501kf03 lcd panel uses 3-wire 9-bit SPI Mode. */
spi->bits_per_word = 9;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "spi setup failed.\n");
return ret;
}
lcd->spi = spi;
lcd->dev = &spi->dev;
lcd->lcd_pd = dev_get_platdata(&spi->dev);
if (!lcd->lcd_pd) {
dev_err(&spi->dev, "platform data is NULL\n");
return -EINVAL;
}
ld = devm_lcd_device_register(&spi->dev, "lms501kf03", &spi->dev, lcd,
&lms501kf03_lcd_ops);
if (IS_ERR(ld))
return PTR_ERR(ld);
lcd->ld = ld;
if (!lcd->lcd_pd->lcd_enabled) {
/*
* if lcd panel was off from bootloader then
* current lcd status is powerdown and then
* it enables lcd panel.
*/
lcd->power = FB_BLANK_POWERDOWN;
lms501kf03_power(lcd, FB_BLANK_UNBLANK);
} else {
lcd->power = FB_BLANK_UNBLANK;
}
spi_set_drvdata(spi, lcd);
dev_info(&spi->dev, "lms501kf03 panel driver has been probed.\n");
return 0;
}
static int lms501kf03_remove(struct spi_device *spi)
{
struct lms501kf03 *lcd = spi_get_drvdata(spi);
lms501kf03_power(lcd, FB_BLANK_POWERDOWN);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int lms501kf03_suspend(struct device *dev)
{
struct lms501kf03 *lcd = dev_get_drvdata(dev);
dev_dbg(dev, "lcd->power = %d\n", lcd->power);
/*
* when lcd panel is suspend, lcd panel becomes off
* regardless of status.
*/
return lms501kf03_power(lcd, FB_BLANK_POWERDOWN);
}
static int lms501kf03_resume(struct device *dev)
{
struct lms501kf03 *lcd = dev_get_drvdata(dev);
lcd->power = FB_BLANK_POWERDOWN;
return lms501kf03_power(lcd, FB_BLANK_UNBLANK);
}
#endif
static SIMPLE_DEV_PM_OPS(lms501kf03_pm_ops, lms501kf03_suspend,
lms501kf03_resume);
static void lms501kf03_shutdown(struct spi_device *spi)
{
struct lms501kf03 *lcd = spi_get_drvdata(spi);
lms501kf03_power(lcd, FB_BLANK_POWERDOWN);
}
static struct spi_driver lms501kf03_driver = {
.driver = {
.name = "lms501kf03",
.owner = THIS_MODULE,
.pm = &lms501kf03_pm_ops,
},
.probe = lms501kf03_probe,
.remove = lms501kf03_remove,
.shutdown = lms501kf03_shutdown,
};
module_spi_driver(lms501kf03_driver);
MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
MODULE_DESCRIPTION("lms501kf03 LCD Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,255 @@
/*
* Backlight control code for Sharp Zaurus SL-5500
*
* Copyright 2005 John Lenz <lenz@cs.wisc.edu>
* Maintainer: Pavel Machek <pavel@ucw.cz> (unless John wants to :-)
* GPL v2
*
* This driver assumes single CPU. That's okay, because collie is
* slightly old hardware, and no one is going to retrofit second CPU to
* old PDA.
*/
/* LCD power functions */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <asm/hardware/locomo.h>
#include <asm/irq.h>
#include <asm/mach/sharpsl_param.h>
#include <asm/mach-types.h>
#include "../../../arch/arm/mach-sa1100/generic.h"
static struct backlight_device *locomolcd_bl_device;
static struct locomo_dev *locomolcd_dev;
static unsigned long locomolcd_flags;
#define LOCOMOLCD_SUSPENDED 0x01
static void locomolcd_on(int comadj)
{
locomo_gpio_set_dir(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_VSHA_ON, 0);
locomo_gpio_write(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_VSHA_ON, 1);
mdelay(2);
locomo_gpio_set_dir(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_VSHD_ON, 0);
locomo_gpio_write(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_VSHD_ON, 1);
mdelay(2);
locomo_m62332_senddata(locomolcd_dev, comadj, 0);
mdelay(5);
locomo_gpio_set_dir(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_VEE_ON, 0);
locomo_gpio_write(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_VEE_ON, 1);
mdelay(10);
/* TFTCRST | CPSOUT=0 | CPSEN */
locomo_writel(0x01, locomolcd_dev->mapbase + LOCOMO_TC);
/* Set CPSD */
locomo_writel(6, locomolcd_dev->mapbase + LOCOMO_CPSD);
/* TFTCRST | CPSOUT=0 | CPSEN */
locomo_writel((0x04 | 0x01), locomolcd_dev->mapbase + LOCOMO_TC);
mdelay(10);
locomo_gpio_set_dir(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_MOD, 0);
locomo_gpio_write(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_MOD, 1);
}
static void locomolcd_off(int comadj)
{
/* TFTCRST=1 | CPSOUT=1 | CPSEN = 0 */
locomo_writel(0x06, locomolcd_dev->mapbase + LOCOMO_TC);
mdelay(1);
locomo_gpio_write(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_VSHA_ON, 0);
mdelay(110);
locomo_gpio_write(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_VEE_ON, 0);
mdelay(700);
/* TFTCRST=0 | CPSOUT=0 | CPSEN = 0 */
locomo_writel(0, locomolcd_dev->mapbase + LOCOMO_TC);
locomo_gpio_write(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_MOD, 0);
locomo_gpio_write(locomolcd_dev->dev.parent, LOCOMO_GPIO_LCD_VSHD_ON, 0);
}
void locomolcd_power(int on)
{
int comadj = sharpsl_param.comadj;
unsigned long flags;
local_irq_save(flags);
if (!locomolcd_dev) {
local_irq_restore(flags);
return;
}
/* read comadj */
if (comadj == -1 && machine_is_collie())
comadj = 128;
if (comadj == -1 && machine_is_poodle())
comadj = 118;
if (on)
locomolcd_on(comadj);
else
locomolcd_off(comadj);
local_irq_restore(flags);
}
EXPORT_SYMBOL(locomolcd_power);
static int current_intensity;
static int locomolcd_set_intensity(struct backlight_device *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 (locomolcd_flags & LOCOMOLCD_SUSPENDED)
intensity = 0;
switch (intensity) {
/*
* AC and non-AC are handled differently,
* but produce same results in sharp code?
*/
case 0:
locomo_frontlight_set(locomolcd_dev, 0, 0, 161);
break;
case 1:
locomo_frontlight_set(locomolcd_dev, 117, 0, 161);
break;
case 2:
locomo_frontlight_set(locomolcd_dev, 163, 0, 148);
break;
case 3:
locomo_frontlight_set(locomolcd_dev, 194, 0, 161);
break;
case 4:
locomo_frontlight_set(locomolcd_dev, 194, 1, 161);
break;
default:
return -ENODEV;
}
current_intensity = intensity;
return 0;
}
static int locomolcd_get_intensity(struct backlight_device *bd)
{
return current_intensity;
}
static const struct backlight_ops locomobl_data = {
.get_brightness = locomolcd_get_intensity,
.update_status = locomolcd_set_intensity,
};
#ifdef CONFIG_PM_SLEEP
static int locomolcd_suspend(struct device *dev)
{
locomolcd_flags |= LOCOMOLCD_SUSPENDED;
locomolcd_set_intensity(locomolcd_bl_device);
return 0;
}
static int locomolcd_resume(struct device *dev)
{
locomolcd_flags &= ~LOCOMOLCD_SUSPENDED;
locomolcd_set_intensity(locomolcd_bl_device);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(locomolcd_pm_ops, locomolcd_suspend, locomolcd_resume);
static int locomolcd_probe(struct locomo_dev *ldev)
{
struct backlight_properties props;
unsigned long flags;
local_irq_save(flags);
locomolcd_dev = ldev;
locomo_gpio_set_dir(ldev->dev.parent, LOCOMO_GPIO_FL_VR, 0);
/*
* the poodle_lcd_power function is called for the first time
* from fs_initcall, which is before locomo is activated.
* We need to recall poodle_lcd_power here
*/
if (machine_is_poodle())
locomolcd_power(1);
local_irq_restore(flags);
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = 4;
locomolcd_bl_device = backlight_device_register("locomo-bl",
&ldev->dev, NULL,
&locomobl_data, &props);
if (IS_ERR(locomolcd_bl_device))
return PTR_ERR(locomolcd_bl_device);
/* Set up frontlight so that screen is readable */
locomolcd_bl_device->props.brightness = 2;
locomolcd_set_intensity(locomolcd_bl_device);
return 0;
}
static int locomolcd_remove(struct locomo_dev *dev)
{
unsigned long flags;
locomolcd_bl_device->props.brightness = 0;
locomolcd_bl_device->props.power = 0;
locomolcd_set_intensity(locomolcd_bl_device);
backlight_device_unregister(locomolcd_bl_device);
local_irq_save(flags);
locomolcd_dev = NULL;
local_irq_restore(flags);
return 0;
}
static struct locomo_driver poodle_lcd_driver = {
.drv = {
.name = "locomo-backlight",
.pm = &locomolcd_pm_ops,
},
.devid = LOCOMO_DEVID_BACKLIGHT,
.probe = locomolcd_probe,
.remove = locomolcd_remove,
};
static int __init locomolcd_init(void)
{
return locomo_driver_register(&poodle_lcd_driver);
}
static void __exit locomolcd_exit(void)
{
locomo_driver_unregister(&poodle_lcd_driver);
}
module_init(locomolcd_init);
module_exit(locomolcd_exit);
MODULE_AUTHOR("John Lenz <lenz@cs.wisc.edu>, Pavel Machek <pavel@ucw.cz>");
MODULE_DESCRIPTION("Collie LCD driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,501 @@
/*
* TI LP855x Backlight Driver
*
* Copyright (C) 2011 Texas Instruments
*
* 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/slab.h>
#include <linux/i2c.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/platform_data/lp855x.h>
#include <linux/pwm.h>
/* LP8550/1/2/3/6 Registers */
#define LP855X_BRIGHTNESS_CTRL 0x00
#define LP855X_DEVICE_CTRL 0x01
#define LP855X_EEPROM_START 0xA0
#define LP855X_EEPROM_END 0xA7
#define LP8556_EPROM_START 0xA0
#define LP8556_EPROM_END 0xAF
/* LP8555/7 Registers */
#define LP8557_BL_CMD 0x00
#define LP8557_BL_MASK 0x01
#define LP8557_BL_ON 0x01
#define LP8557_BL_OFF 0x00
#define LP8557_BRIGHTNESS_CTRL 0x04
#define LP8557_CONFIG 0x10
#define LP8555_EPROM_START 0x10
#define LP8555_EPROM_END 0x7A
#define LP8557_EPROM_START 0x10
#define LP8557_EPROM_END 0x1E
#define DEFAULT_BL_NAME "lcd-backlight"
#define MAX_BRIGHTNESS 255
enum lp855x_brightness_ctrl_mode {
PWM_BASED = 1,
REGISTER_BASED,
};
struct lp855x;
/*
* struct lp855x_device_config
* @pre_init_device: init device function call before updating the brightness
* @reg_brightness: register address for brigthenss control
* @reg_devicectrl: register address for device control
* @post_init_device: late init device function call
*/
struct lp855x_device_config {
int (*pre_init_device)(struct lp855x *);
u8 reg_brightness;
u8 reg_devicectrl;
int (*post_init_device)(struct lp855x *);
};
struct lp855x {
const char *chipname;
enum lp855x_chip_id chip_id;
enum lp855x_brightness_ctrl_mode mode;
struct lp855x_device_config *cfg;
struct i2c_client *client;
struct backlight_device *bl;
struct device *dev;
struct lp855x_platform_data *pdata;
struct pwm_device *pwm;
};
static int lp855x_write_byte(struct lp855x *lp, u8 reg, u8 data)
{
return i2c_smbus_write_byte_data(lp->client, reg, data);
}
static int lp855x_update_bit(struct lp855x *lp, u8 reg, u8 mask, u8 data)
{
int ret;
u8 tmp;
ret = i2c_smbus_read_byte_data(lp->client, reg);
if (ret < 0) {
dev_err(lp->dev, "failed to read 0x%.2x\n", reg);
return ret;
}
tmp = (u8)ret;
tmp &= ~mask;
tmp |= data & mask;
return lp855x_write_byte(lp, reg, tmp);
}
static bool lp855x_is_valid_rom_area(struct lp855x *lp, u8 addr)
{
u8 start, end;
switch (lp->chip_id) {
case LP8550:
case LP8551:
case LP8552:
case LP8553:
start = LP855X_EEPROM_START;
end = LP855X_EEPROM_END;
break;
case LP8556:
start = LP8556_EPROM_START;
end = LP8556_EPROM_END;
break;
case LP8555:
start = LP8555_EPROM_START;
end = LP8555_EPROM_END;
break;
case LP8557:
start = LP8557_EPROM_START;
end = LP8557_EPROM_END;
break;
default:
return false;
}
return addr >= start && addr <= end;
}
static int lp8557_bl_off(struct lp855x *lp)
{
/* BL_ON = 0 before updating EPROM settings */
return lp855x_update_bit(lp, LP8557_BL_CMD, LP8557_BL_MASK,
LP8557_BL_OFF);
}
static int lp8557_bl_on(struct lp855x *lp)
{
/* BL_ON = 1 after updating EPROM settings */
return lp855x_update_bit(lp, LP8557_BL_CMD, LP8557_BL_MASK,
LP8557_BL_ON);
}
static struct lp855x_device_config lp855x_dev_cfg = {
.reg_brightness = LP855X_BRIGHTNESS_CTRL,
.reg_devicectrl = LP855X_DEVICE_CTRL,
};
static struct lp855x_device_config lp8557_dev_cfg = {
.reg_brightness = LP8557_BRIGHTNESS_CTRL,
.reg_devicectrl = LP8557_CONFIG,
.pre_init_device = lp8557_bl_off,
.post_init_device = lp8557_bl_on,
};
/*
* Device specific configuration flow
*
* a) pre_init_device(optional)
* b) update the brightness register
* c) update device control register
* d) update ROM area(optional)
* e) post_init_device(optional)
*
*/
static int lp855x_configure(struct lp855x *lp)
{
u8 val, addr;
int i, ret;
struct lp855x_platform_data *pd = lp->pdata;
switch (lp->chip_id) {
case LP8550:
case LP8551:
case LP8552:
case LP8553:
case LP8556:
lp->cfg = &lp855x_dev_cfg;
break;
case LP8555:
case LP8557:
lp->cfg = &lp8557_dev_cfg;
break;
default:
return -EINVAL;
}
if (lp->cfg->pre_init_device) {
ret = lp->cfg->pre_init_device(lp);
if (ret) {
dev_err(lp->dev, "pre init device err: %d\n", ret);
goto err;
}
}
val = pd->initial_brightness;
ret = lp855x_write_byte(lp, lp->cfg->reg_brightness, val);
if (ret)
goto err;
val = pd->device_control;
ret = lp855x_write_byte(lp, lp->cfg->reg_devicectrl, val);
if (ret)
goto err;
if (pd->size_program > 0) {
for (i = 0; i < pd->size_program; i++) {
addr = pd->rom_data[i].addr;
val = pd->rom_data[i].val;
if (!lp855x_is_valid_rom_area(lp, addr))
continue;
ret = lp855x_write_byte(lp, addr, val);
if (ret)
goto err;
}
}
if (lp->cfg->post_init_device) {
ret = lp->cfg->post_init_device(lp);
if (ret) {
dev_err(lp->dev, "post init device err: %d\n", ret);
goto err;
}
}
return 0;
err:
return ret;
}
static void lp855x_pwm_ctrl(struct lp855x *lp, int br, int max_br)
{
unsigned int period = lp->pdata->period_ns;
unsigned int duty = br * period / max_br;
struct pwm_device *pwm;
/* request pwm device with the consumer name */
if (!lp->pwm) {
pwm = devm_pwm_get(lp->dev, lp->chipname);
if (IS_ERR(pwm))
return;
lp->pwm = pwm;
}
pwm_config(lp->pwm, duty, period);
if (duty)
pwm_enable(lp->pwm);
else
pwm_disable(lp->pwm);
}
static int lp855x_bl_update_status(struct backlight_device *bl)
{
struct lp855x *lp = bl_get_data(bl);
if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
bl->props.brightness = 0;
if (lp->mode == PWM_BASED) {
int br = bl->props.brightness;
int max_br = bl->props.max_brightness;
lp855x_pwm_ctrl(lp, br, max_br);
} else if (lp->mode == REGISTER_BASED) {
u8 val = bl->props.brightness;
lp855x_write_byte(lp, lp->cfg->reg_brightness, val);
}
return 0;
}
static const struct backlight_ops lp855x_bl_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = lp855x_bl_update_status,
};
static int lp855x_backlight_register(struct lp855x *lp)
{
struct backlight_device *bl;
struct backlight_properties props;
struct lp855x_platform_data *pdata = lp->pdata;
const char *name = pdata->name ? : DEFAULT_BL_NAME;
props.type = BACKLIGHT_PLATFORM;
props.max_brightness = MAX_BRIGHTNESS;
if (pdata->initial_brightness > props.max_brightness)
pdata->initial_brightness = props.max_brightness;
props.brightness = pdata->initial_brightness;
bl = devm_backlight_device_register(lp->dev, name, lp->dev, lp,
&lp855x_bl_ops, &props);
if (IS_ERR(bl))
return PTR_ERR(bl);
lp->bl = bl;
return 0;
}
static ssize_t lp855x_get_chip_id(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lp855x *lp = dev_get_drvdata(dev);
return scnprintf(buf, PAGE_SIZE, "%s\n", lp->chipname);
}
static ssize_t lp855x_get_bl_ctl_mode(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lp855x *lp = dev_get_drvdata(dev);
char *strmode = NULL;
if (lp->mode == PWM_BASED)
strmode = "pwm based";
else if (lp->mode == REGISTER_BASED)
strmode = "register based";
return scnprintf(buf, PAGE_SIZE, "%s\n", strmode);
}
static DEVICE_ATTR(chip_id, S_IRUGO, lp855x_get_chip_id, NULL);
static DEVICE_ATTR(bl_ctl_mode, S_IRUGO, lp855x_get_bl_ctl_mode, NULL);
static struct attribute *lp855x_attributes[] = {
&dev_attr_chip_id.attr,
&dev_attr_bl_ctl_mode.attr,
NULL,
};
static const struct attribute_group lp855x_attr_group = {
.attrs = lp855x_attributes,
};
#ifdef CONFIG_OF
static int lp855x_parse_dt(struct device *dev, struct device_node *node)
{
struct lp855x_platform_data *pdata;
int rom_length;
if (!node) {
dev_err(dev, "no platform data\n");
return -EINVAL;
}
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
of_property_read_string(node, "bl-name", &pdata->name);
of_property_read_u8(node, "dev-ctrl", &pdata->device_control);
of_property_read_u8(node, "init-brt", &pdata->initial_brightness);
of_property_read_u32(node, "pwm-period", &pdata->period_ns);
/* Fill ROM platform data if defined */
rom_length = of_get_child_count(node);
if (rom_length > 0) {
struct lp855x_rom_data *rom;
struct device_node *child;
int i = 0;
rom = devm_kzalloc(dev, sizeof(*rom) * rom_length, GFP_KERNEL);
if (!rom)
return -ENOMEM;
for_each_child_of_node(node, child) {
of_property_read_u8(child, "rom-addr", &rom[i].addr);
of_property_read_u8(child, "rom-val", &rom[i].val);
i++;
}
pdata->size_program = rom_length;
pdata->rom_data = &rom[0];
}
dev->platform_data = pdata;
return 0;
}
#else
static int lp855x_parse_dt(struct device *dev, struct device_node *node)
{
return -EINVAL;
}
#endif
static int lp855x_probe(struct i2c_client *cl, const struct i2c_device_id *id)
{
struct lp855x *lp;
struct lp855x_platform_data *pdata = dev_get_platdata(&cl->dev);
struct device_node *node = cl->dev.of_node;
int ret;
if (!pdata) {
ret = lp855x_parse_dt(&cl->dev, node);
if (ret < 0)
return ret;
pdata = dev_get_platdata(&cl->dev);
}
if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
return -EIO;
lp = devm_kzalloc(&cl->dev, sizeof(struct lp855x), GFP_KERNEL);
if (!lp)
return -ENOMEM;
if (pdata->period_ns > 0)
lp->mode = PWM_BASED;
else
lp->mode = REGISTER_BASED;
lp->client = cl;
lp->dev = &cl->dev;
lp->pdata = pdata;
lp->chipname = id->name;
lp->chip_id = id->driver_data;
i2c_set_clientdata(cl, lp);
ret = lp855x_configure(lp);
if (ret) {
dev_err(lp->dev, "device config err: %d", ret);
return ret;
}
ret = lp855x_backlight_register(lp);
if (ret) {
dev_err(lp->dev,
"failed to register backlight. err: %d\n", ret);
return ret;
}
ret = sysfs_create_group(&lp->dev->kobj, &lp855x_attr_group);
if (ret) {
dev_err(lp->dev, "failed to register sysfs. err: %d\n", ret);
return ret;
}
backlight_update_status(lp->bl);
return 0;
}
static int lp855x_remove(struct i2c_client *cl)
{
struct lp855x *lp = i2c_get_clientdata(cl);
lp->bl->props.brightness = 0;
backlight_update_status(lp->bl);
sysfs_remove_group(&lp->dev->kobj, &lp855x_attr_group);
return 0;
}
static const struct of_device_id lp855x_dt_ids[] = {
{ .compatible = "ti,lp8550", },
{ .compatible = "ti,lp8551", },
{ .compatible = "ti,lp8552", },
{ .compatible = "ti,lp8553", },
{ .compatible = "ti,lp8555", },
{ .compatible = "ti,lp8556", },
{ .compatible = "ti,lp8557", },
{ }
};
MODULE_DEVICE_TABLE(of, lp855x_dt_ids);
static const struct i2c_device_id lp855x_ids[] = {
{"lp8550", LP8550},
{"lp8551", LP8551},
{"lp8552", LP8552},
{"lp8553", LP8553},
{"lp8555", LP8555},
{"lp8556", LP8556},
{"lp8557", LP8557},
{ }
};
MODULE_DEVICE_TABLE(i2c, lp855x_ids);
static struct i2c_driver lp855x_driver = {
.driver = {
.name = "lp855x",
.of_match_table = of_match_ptr(lp855x_dt_ids),
},
.probe = lp855x_probe,
.remove = lp855x_remove,
.id_table = lp855x_ids,
};
module_i2c_driver(lp855x_driver);
MODULE_DESCRIPTION("Texas Instruments LP855x Backlight driver");
MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,325 @@
/*
* TI LP8788 MFD - backlight driver
*
* Copyright 2012 Texas Instruments
*
* Author: Milo(Woogyom) Kim <milo.kim@ti.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/backlight.h>
#include <linux/err.h>
#include <linux/mfd/lp8788.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
/* Register address */
#define LP8788_BL_CONFIG 0x96
#define LP8788_BL_EN BIT(0)
#define LP8788_BL_PWM_INPUT_EN BIT(5)
#define LP8788_BL_FULLSCALE_SHIFT 2
#define LP8788_BL_DIM_MODE_SHIFT 1
#define LP8788_BL_PWM_POLARITY_SHIFT 6
#define LP8788_BL_BRIGHTNESS 0x97
#define LP8788_BL_RAMP 0x98
#define LP8788_BL_RAMP_RISE_SHIFT 4
#define MAX_BRIGHTNESS 127
#define DEFAULT_BL_NAME "lcd-backlight"
struct lp8788_bl_config {
enum lp8788_bl_ctrl_mode bl_mode;
enum lp8788_bl_dim_mode dim_mode;
enum lp8788_bl_full_scale_current full_scale;
enum lp8788_bl_ramp_step rise_time;
enum lp8788_bl_ramp_step fall_time;
enum pwm_polarity pwm_pol;
};
struct lp8788_bl {
struct lp8788 *lp;
struct backlight_device *bl_dev;
struct lp8788_backlight_platform_data *pdata;
enum lp8788_bl_ctrl_mode mode;
struct pwm_device *pwm;
};
static struct lp8788_bl_config default_bl_config = {
.bl_mode = LP8788_BL_REGISTER_ONLY,
.dim_mode = LP8788_DIM_EXPONENTIAL,
.full_scale = LP8788_FULLSCALE_1900uA,
.rise_time = LP8788_RAMP_8192us,
.fall_time = LP8788_RAMP_8192us,
.pwm_pol = PWM_POLARITY_NORMAL,
};
static inline bool is_brightness_ctrl_by_pwm(enum lp8788_bl_ctrl_mode mode)
{
return mode == LP8788_BL_COMB_PWM_BASED;
}
static inline bool is_brightness_ctrl_by_register(enum lp8788_bl_ctrl_mode mode)
{
return mode == LP8788_BL_REGISTER_ONLY ||
mode == LP8788_BL_COMB_REGISTER_BASED;
}
static int lp8788_backlight_configure(struct lp8788_bl *bl)
{
struct lp8788_backlight_platform_data *pdata = bl->pdata;
struct lp8788_bl_config *cfg = &default_bl_config;
int ret;
u8 val;
/*
* Update chip configuration if platform data exists,
* otherwise use the default settings.
*/
if (pdata) {
cfg->bl_mode = pdata->bl_mode;
cfg->dim_mode = pdata->dim_mode;
cfg->full_scale = pdata->full_scale;
cfg->rise_time = pdata->rise_time;
cfg->fall_time = pdata->fall_time;
cfg->pwm_pol = pdata->pwm_pol;
}
/* Brightness ramp up/down */
val = (cfg->rise_time << LP8788_BL_RAMP_RISE_SHIFT) | cfg->fall_time;
ret = lp8788_write_byte(bl->lp, LP8788_BL_RAMP, val);
if (ret)
return ret;
/* Fullscale current setting */
val = (cfg->full_scale << LP8788_BL_FULLSCALE_SHIFT) |
(cfg->dim_mode << LP8788_BL_DIM_MODE_SHIFT);
/* Brightness control mode */
switch (cfg->bl_mode) {
case LP8788_BL_REGISTER_ONLY:
val |= LP8788_BL_EN;
break;
case LP8788_BL_COMB_PWM_BASED:
case LP8788_BL_COMB_REGISTER_BASED:
val |= LP8788_BL_EN | LP8788_BL_PWM_INPUT_EN |
(cfg->pwm_pol << LP8788_BL_PWM_POLARITY_SHIFT);
break;
default:
dev_err(bl->lp->dev, "invalid mode: %d\n", cfg->bl_mode);
return -EINVAL;
}
bl->mode = cfg->bl_mode;
return lp8788_write_byte(bl->lp, LP8788_BL_CONFIG, val);
}
static void lp8788_pwm_ctrl(struct lp8788_bl *bl, int br, int max_br)
{
unsigned int period;
unsigned int duty;
struct device *dev;
struct pwm_device *pwm;
if (!bl->pdata)
return;
period = bl->pdata->period_ns;
duty = br * period / max_br;
dev = bl->lp->dev;
/* request PWM device with the consumer name */
if (!bl->pwm) {
pwm = devm_pwm_get(dev, LP8788_DEV_BACKLIGHT);
if (IS_ERR(pwm)) {
dev_err(dev, "can not get PWM device\n");
return;
}
bl->pwm = pwm;
}
pwm_config(bl->pwm, duty, period);
if (duty)
pwm_enable(bl->pwm);
else
pwm_disable(bl->pwm);
}
static int lp8788_bl_update_status(struct backlight_device *bl_dev)
{
struct lp8788_bl *bl = bl_get_data(bl_dev);
enum lp8788_bl_ctrl_mode mode = bl->mode;
if (bl_dev->props.state & BL_CORE_SUSPENDED)
bl_dev->props.brightness = 0;
if (is_brightness_ctrl_by_pwm(mode)) {
int brt = bl_dev->props.brightness;
int max = bl_dev->props.max_brightness;
lp8788_pwm_ctrl(bl, brt, max);
} else if (is_brightness_ctrl_by_register(mode)) {
u8 brt = bl_dev->props.brightness;
lp8788_write_byte(bl->lp, LP8788_BL_BRIGHTNESS, brt);
}
return 0;
}
static const struct backlight_ops lp8788_bl_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = lp8788_bl_update_status,
};
static int lp8788_backlight_register(struct lp8788_bl *bl)
{
struct backlight_device *bl_dev;
struct backlight_properties props;
struct lp8788_backlight_platform_data *pdata = bl->pdata;
int init_brt;
char *name;
props.type = BACKLIGHT_PLATFORM;
props.max_brightness = MAX_BRIGHTNESS;
/* Initial brightness */
if (pdata)
init_brt = min_t(int, pdata->initial_brightness,
props.max_brightness);
else
init_brt = 0;
props.brightness = init_brt;
/* Backlight device name */
if (!pdata || !pdata->name)
name = DEFAULT_BL_NAME;
else
name = pdata->name;
bl_dev = backlight_device_register(name, bl->lp->dev, bl,
&lp8788_bl_ops, &props);
if (IS_ERR(bl_dev))
return PTR_ERR(bl_dev);
bl->bl_dev = bl_dev;
return 0;
}
static void lp8788_backlight_unregister(struct lp8788_bl *bl)
{
struct backlight_device *bl_dev = bl->bl_dev;
if (bl_dev)
backlight_device_unregister(bl_dev);
}
static ssize_t lp8788_get_bl_ctl_mode(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct lp8788_bl *bl = dev_get_drvdata(dev);
enum lp8788_bl_ctrl_mode mode = bl->mode;
char *strmode;
if (is_brightness_ctrl_by_pwm(mode))
strmode = "PWM based";
else if (is_brightness_ctrl_by_register(mode))
strmode = "Register based";
else
strmode = "Invalid mode";
return scnprintf(buf, PAGE_SIZE, "%s\n", strmode);
}
static DEVICE_ATTR(bl_ctl_mode, S_IRUGO, lp8788_get_bl_ctl_mode, NULL);
static struct attribute *lp8788_attributes[] = {
&dev_attr_bl_ctl_mode.attr,
NULL,
};
static const struct attribute_group lp8788_attr_group = {
.attrs = lp8788_attributes,
};
static int lp8788_backlight_probe(struct platform_device *pdev)
{
struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent);
struct lp8788_bl *bl;
int ret;
bl = devm_kzalloc(lp->dev, sizeof(struct lp8788_bl), GFP_KERNEL);
if (!bl)
return -ENOMEM;
bl->lp = lp;
if (lp->pdata)
bl->pdata = lp->pdata->bl_pdata;
platform_set_drvdata(pdev, bl);
ret = lp8788_backlight_configure(bl);
if (ret) {
dev_err(lp->dev, "backlight config err: %d\n", ret);
goto err_dev;
}
ret = lp8788_backlight_register(bl);
if (ret) {
dev_err(lp->dev, "register backlight err: %d\n", ret);
goto err_dev;
}
ret = sysfs_create_group(&pdev->dev.kobj, &lp8788_attr_group);
if (ret) {
dev_err(lp->dev, "register sysfs err: %d\n", ret);
goto err_sysfs;
}
backlight_update_status(bl->bl_dev);
return 0;
err_sysfs:
lp8788_backlight_unregister(bl);
err_dev:
return ret;
}
static int lp8788_backlight_remove(struct platform_device *pdev)
{
struct lp8788_bl *bl = platform_get_drvdata(pdev);
struct backlight_device *bl_dev = bl->bl_dev;
bl_dev->props.brightness = 0;
backlight_update_status(bl_dev);
sysfs_remove_group(&pdev->dev.kobj, &lp8788_attr_group);
lp8788_backlight_unregister(bl);
return 0;
}
static struct platform_driver lp8788_bl_driver = {
.probe = lp8788_backlight_probe,
.remove = lp8788_backlight_remove,
.driver = {
.name = LP8788_DEV_BACKLIGHT,
},
};
module_platform_driver(lp8788_bl_driver);
MODULE_DESCRIPTION("Texas Instruments LP8788 Backlight Driver");
MODULE_AUTHOR("Milo Kim");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:lp8788-backlight");

View file

@ -0,0 +1,312 @@
/*
* Power control for Samsung LTV350QV Quarter VGA LCD Panel
*
* Copyright (C) 2006, 2007 Atmel Corporation
*
* 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/delay.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include "ltv350qv.h"
#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
struct ltv350qv {
struct spi_device *spi;
u8 *buffer;
int power;
struct lcd_device *ld;
};
/*
* The power-on and power-off sequences are taken from the
* LTV350QV-F04 data sheet from Samsung. The register definitions are
* taken from the S6F2002 command list also from Samsung. Both
* documents are distributed with the AVR32 Linux BSP CD from Atmel.
*
* There's still some voodoo going on here, but it's a lot better than
* in the first incarnation of the driver where all we had was the raw
* numbers from the initialization sequence.
*/
static int ltv350qv_write_reg(struct ltv350qv *lcd, u8 reg, u16 val)
{
struct spi_message msg;
struct spi_transfer index_xfer = {
.len = 3,
.cs_change = 1,
};
struct spi_transfer value_xfer = {
.len = 3,
};
spi_message_init(&msg);
/* register index */
lcd->buffer[0] = LTV_OPC_INDEX;
lcd->buffer[1] = 0x00;
lcd->buffer[2] = reg & 0x7f;
index_xfer.tx_buf = lcd->buffer;
spi_message_add_tail(&index_xfer, &msg);
/* register value */
lcd->buffer[4] = LTV_OPC_DATA;
lcd->buffer[5] = val >> 8;
lcd->buffer[6] = val;
value_xfer.tx_buf = lcd->buffer + 4;
spi_message_add_tail(&value_xfer, &msg);
return spi_sync(lcd->spi, &msg);
}
/* The comments are taken straight from the data sheet */
static int ltv350qv_power_on(struct ltv350qv *lcd)
{
int ret;
/* Power On Reset Display off State */
if (ltv350qv_write_reg(lcd, LTV_PWRCTL1, 0x0000))
goto err;
usleep_range(15000, 16000);
/* Power Setting Function 1 */
if (ltv350qv_write_reg(lcd, LTV_PWRCTL1, LTV_VCOM_DISABLE))
goto err;
if (ltv350qv_write_reg(lcd, LTV_PWRCTL2, LTV_VCOML_ENABLE))
goto err_power1;
/* Power Setting Function 2 */
if (ltv350qv_write_reg(lcd, LTV_PWRCTL1,
LTV_VCOM_DISABLE | LTV_DRIVE_CURRENT(5)
| LTV_SUPPLY_CURRENT(5)))
goto err_power2;
msleep(55);
/* Instruction Setting */
ret = ltv350qv_write_reg(lcd, LTV_IFCTL,
LTV_NMD | LTV_REV | LTV_NL(0x1d));
ret |= ltv350qv_write_reg(lcd, LTV_DATACTL,
LTV_DS_SAME | LTV_CHS_480
| LTV_DF_RGB | LTV_RGB_BGR);
ret |= ltv350qv_write_reg(lcd, LTV_ENTRY_MODE,
LTV_VSPL_ACTIVE_LOW
| LTV_HSPL_ACTIVE_LOW
| LTV_DPL_SAMPLE_RISING
| LTV_EPL_ACTIVE_LOW
| LTV_SS_RIGHT_TO_LEFT);
ret |= ltv350qv_write_reg(lcd, LTV_GATECTL1, LTV_CLW(3));
ret |= ltv350qv_write_reg(lcd, LTV_GATECTL2,
LTV_NW_INV_1LINE | LTV_FWI(3));
ret |= ltv350qv_write_reg(lcd, LTV_VBP, 0x000a);
ret |= ltv350qv_write_reg(lcd, LTV_HBP, 0x0021);
ret |= ltv350qv_write_reg(lcd, LTV_SOTCTL, LTV_SDT(3) | LTV_EQ(0));
ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(0), 0x0103);
ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(1), 0x0301);
ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(2), 0x1f0f);
ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(3), 0x1f0f);
ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(4), 0x0707);
ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(5), 0x0307);
ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(6), 0x0707);
ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(7), 0x0000);
ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(8), 0x0004);
ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(9), 0x0000);
if (ret)
goto err_settings;
/* Wait more than 2 frames */
msleep(20);
/* Display On Sequence */
ret = ltv350qv_write_reg(lcd, LTV_PWRCTL1,
LTV_VCOM_DISABLE | LTV_VCOMOUT_ENABLE
| LTV_POWER_ON | LTV_DRIVE_CURRENT(5)
| LTV_SUPPLY_CURRENT(5));
ret |= ltv350qv_write_reg(lcd, LTV_GATECTL2,
LTV_NW_INV_1LINE | LTV_DSC | LTV_FWI(3));
if (ret)
goto err_disp_on;
/* Display should now be ON. Phew. */
return 0;
err_disp_on:
/*
* Try to recover. Error handling probably isn't very useful
* at this point, just make a best effort to switch the panel
* off.
*/
ltv350qv_write_reg(lcd, LTV_PWRCTL1,
LTV_VCOM_DISABLE | LTV_DRIVE_CURRENT(5)
| LTV_SUPPLY_CURRENT(5));
ltv350qv_write_reg(lcd, LTV_GATECTL2,
LTV_NW_INV_1LINE | LTV_FWI(3));
err_settings:
err_power2:
err_power1:
ltv350qv_write_reg(lcd, LTV_PWRCTL2, 0x0000);
usleep_range(1000, 1100);
err:
ltv350qv_write_reg(lcd, LTV_PWRCTL1, LTV_VCOM_DISABLE);
return -EIO;
}
static int ltv350qv_power_off(struct ltv350qv *lcd)
{
int ret;
/* Display Off Sequence */
ret = ltv350qv_write_reg(lcd, LTV_PWRCTL1,
LTV_VCOM_DISABLE
| LTV_DRIVE_CURRENT(5)
| LTV_SUPPLY_CURRENT(5));
ret |= ltv350qv_write_reg(lcd, LTV_GATECTL2,
LTV_NW_INV_1LINE | LTV_FWI(3));
/* Power down setting 1 */
ret |= ltv350qv_write_reg(lcd, LTV_PWRCTL2, 0x0000);
/* Wait at least 1 ms */
usleep_range(1000, 1100);
/* Power down setting 2 */
ret |= ltv350qv_write_reg(lcd, LTV_PWRCTL1, LTV_VCOM_DISABLE);
/*
* No point in trying to recover here. If we can't switch the
* panel off, what are we supposed to do other than inform the
* user about the failure?
*/
if (ret)
return -EIO;
/* Display power should now be OFF */
return 0;
}
static int ltv350qv_power(struct ltv350qv *lcd, int power)
{
int ret = 0;
if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power))
ret = ltv350qv_power_on(lcd);
else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power))
ret = ltv350qv_power_off(lcd);
if (!ret)
lcd->power = power;
return ret;
}
static int ltv350qv_set_power(struct lcd_device *ld, int power)
{
struct ltv350qv *lcd = lcd_get_data(ld);
return ltv350qv_power(lcd, power);
}
static int ltv350qv_get_power(struct lcd_device *ld)
{
struct ltv350qv *lcd = lcd_get_data(ld);
return lcd->power;
}
static struct lcd_ops ltv_ops = {
.get_power = ltv350qv_get_power,
.set_power = ltv350qv_set_power,
};
static int ltv350qv_probe(struct spi_device *spi)
{
struct ltv350qv *lcd;
struct lcd_device *ld;
int ret;
lcd = devm_kzalloc(&spi->dev, sizeof(struct ltv350qv), GFP_KERNEL);
if (!lcd)
return -ENOMEM;
lcd->spi = spi;
lcd->power = FB_BLANK_POWERDOWN;
lcd->buffer = devm_kzalloc(&spi->dev, 8, GFP_KERNEL);
if (!lcd->buffer)
return -ENOMEM;
ld = devm_lcd_device_register(&spi->dev, "ltv350qv", &spi->dev, lcd,
&ltv_ops);
if (IS_ERR(ld))
return PTR_ERR(ld);
lcd->ld = ld;
ret = ltv350qv_power(lcd, FB_BLANK_UNBLANK);
if (ret)
return ret;
spi_set_drvdata(spi, lcd);
return 0;
}
static int ltv350qv_remove(struct spi_device *spi)
{
struct ltv350qv *lcd = spi_get_drvdata(spi);
ltv350qv_power(lcd, FB_BLANK_POWERDOWN);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int ltv350qv_suspend(struct device *dev)
{
struct ltv350qv *lcd = dev_get_drvdata(dev);
return ltv350qv_power(lcd, FB_BLANK_POWERDOWN);
}
static int ltv350qv_resume(struct device *dev)
{
struct ltv350qv *lcd = dev_get_drvdata(dev);
return ltv350qv_power(lcd, FB_BLANK_UNBLANK);
}
#endif
static SIMPLE_DEV_PM_OPS(ltv350qv_pm_ops, ltv350qv_suspend, ltv350qv_resume);
/* Power down all displays on reboot, poweroff or halt */
static void ltv350qv_shutdown(struct spi_device *spi)
{
struct ltv350qv *lcd = spi_get_drvdata(spi);
ltv350qv_power(lcd, FB_BLANK_POWERDOWN);
}
static struct spi_driver ltv350qv_driver = {
.driver = {
.name = "ltv350qv",
.owner = THIS_MODULE,
.pm = &ltv350qv_pm_ops,
},
.probe = ltv350qv_probe,
.remove = ltv350qv_remove,
.shutdown = ltv350qv_shutdown,
};
module_spi_driver(ltv350qv_driver);
MODULE_AUTHOR("Haavard Skinnemoen (Atmel)");
MODULE_DESCRIPTION("Samsung LTV350QV LCD Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:ltv350qv");

View file

@ -0,0 +1,95 @@
/*
* Register definitions for Samsung LTV350QV Quarter VGA LCD Panel
*
* Copyright (C) 2006, 2007 Atmel Corporation
*
* 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.
*/
#ifndef __LTV350QV_H
#define __LTV350QV_H
#define LTV_OPC_INDEX 0x74
#define LTV_OPC_DATA 0x76
#define LTV_ID 0x00 /* ID Read */
#define LTV_IFCTL 0x01 /* Display Interface Control */
#define LTV_DATACTL 0x02 /* Display Data Control */
#define LTV_ENTRY_MODE 0x03 /* Entry Mode */
#define LTV_GATECTL1 0x04 /* Gate Control 1 */
#define LTV_GATECTL2 0x05 /* Gate Control 2 */
#define LTV_VBP 0x06 /* Vertical Back Porch */
#define LTV_HBP 0x07 /* Horizontal Back Porch */
#define LTV_SOTCTL 0x08 /* Source Output Timing Control */
#define LTV_PWRCTL1 0x09 /* Power Control 1 */
#define LTV_PWRCTL2 0x0a /* Power Control 2 */
#define LTV_GAMMA(x) (0x10 + (x)) /* Gamma control */
/* Bit definitions for LTV_IFCTL */
#define LTV_IM (1 << 15)
#define LTV_NMD (1 << 14)
#define LTV_SSMD (1 << 13)
#define LTV_REV (1 << 7)
#define LTV_NL(x) (((x) & 0x001f) << 0)
/* Bit definitions for LTV_DATACTL */
#define LTV_DS_SAME (0 << 12)
#define LTV_DS_D_TO_S (1 << 12)
#define LTV_DS_S_TO_D (2 << 12)
#define LTV_CHS_384 (0 << 9)
#define LTV_CHS_480 (1 << 9)
#define LTV_CHS_492 (2 << 9)
#define LTV_DF_RGB (0 << 6)
#define LTV_DF_RGBX (1 << 6)
#define LTV_DF_XRGB (2 << 6)
#define LTV_RGB_RGB (0 << 2)
#define LTV_RGB_BGR (1 << 2)
#define LTV_RGB_GRB (2 << 2)
#define LTV_RGB_RBG (3 << 2)
/* Bit definitions for LTV_ENTRY_MODE */
#define LTV_VSPL_ACTIVE_LOW (0 << 15)
#define LTV_VSPL_ACTIVE_HIGH (1 << 15)
#define LTV_HSPL_ACTIVE_LOW (0 << 14)
#define LTV_HSPL_ACTIVE_HIGH (1 << 14)
#define LTV_DPL_SAMPLE_RISING (0 << 13)
#define LTV_DPL_SAMPLE_FALLING (1 << 13)
#define LTV_EPL_ACTIVE_LOW (0 << 12)
#define LTV_EPL_ACTIVE_HIGH (1 << 12)
#define LTV_SS_LEFT_TO_RIGHT (0 << 8)
#define LTV_SS_RIGHT_TO_LEFT (1 << 8)
#define LTV_STB (1 << 1)
/* Bit definitions for LTV_GATECTL1 */
#define LTV_CLW(x) (((x) & 0x0007) << 12)
#define LTV_GAON (1 << 5)
#define LTV_SDR (1 << 3)
/* Bit definitions for LTV_GATECTL2 */
#define LTV_NW_INV_FRAME (0 << 14)
#define LTV_NW_INV_1LINE (1 << 14)
#define LTV_NW_INV_2LINE (2 << 14)
#define LTV_DSC (1 << 12)
#define LTV_GIF (1 << 8)
#define LTV_FHN (1 << 7)
#define LTV_FTI(x) (((x) & 0x0003) << 4)
#define LTV_FWI(x) (((x) & 0x0003) << 0)
/* Bit definitions for LTV_SOTCTL */
#define LTV_SDT(x) (((x) & 0x0007) << 10)
#define LTV_EQ(x) (((x) & 0x0007) << 2)
/* Bit definitions for LTV_PWRCTL1 */
#define LTV_VCOM_DISABLE (1 << 14)
#define LTV_VCOMOUT_ENABLE (1 << 11)
#define LTV_POWER_ON (1 << 9)
#define LTV_DRIVE_CURRENT(x) (((x) & 0x0007) << 4) /* 0=off, 5=max */
#define LTV_SUPPLY_CURRENT(x) (((x) & 0x0007) << 0) /* 0=off, 5=max */
/* Bit definitions for LTV_PWRCTL2 */
#define LTV_VCOML_ENABLE (1 << 13)
#define LTV_VCOML_VOLTAGE(x) (((x) & 0x001f) << 8) /* 0=1V, 31=-1V */
#define LTV_VCOMH_VOLTAGE(x) (((x) & 0x001f) << 0) /* 0=3V, 31=4.5V */
#endif /* __LTV350QV_H */

View file

@ -0,0 +1,164 @@
/*
* Sanyo LV5207LP LED Driver
*
* Copyright (C) 2013 Ideas on board SPRL
*
* Contact: Laurent Pinchart <laurent.pinchart@ideasonboard.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/backlight.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/platform_data/lv5207lp.h>
#include <linux/slab.h>
#define LV5207LP_CTRL1 0x00
#define LV5207LP_CPSW (1 << 7)
#define LV5207LP_SCTEN (1 << 6)
#define LV5207LP_C10 (1 << 5)
#define LV5207LP_CKSW (1 << 4)
#define LV5207LP_RSW (1 << 3)
#define LV5207LP_GSW (1 << 2)
#define LV5207LP_BSW (1 << 1)
#define LV5207LP_CTRL2 0x01
#define LV5207LP_MSW (1 << 7)
#define LV5207LP_MLED4 (1 << 6)
#define LV5207LP_RED 0x02
#define LV5207LP_GREEN 0x03
#define LV5207LP_BLUE 0x04
#define LV5207LP_MAX_BRIGHTNESS 32
struct lv5207lp {
struct i2c_client *client;
struct backlight_device *backlight;
struct lv5207lp_platform_data *pdata;
};
static int lv5207lp_write(struct lv5207lp *lv, u8 reg, u8 data)
{
return i2c_smbus_write_byte_data(lv->client, reg, data);
}
static int lv5207lp_backlight_update_status(struct backlight_device *backlight)
{
struct lv5207lp *lv = bl_get_data(backlight);
int brightness = backlight->props.brightness;
if (backlight->props.power != FB_BLANK_UNBLANK ||
backlight->props.fb_blank != FB_BLANK_UNBLANK ||
backlight->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
brightness = 0;
if (brightness) {
lv5207lp_write(lv, LV5207LP_CTRL1,
LV5207LP_CPSW | LV5207LP_C10 | LV5207LP_CKSW);
lv5207lp_write(lv, LV5207LP_CTRL2,
LV5207LP_MSW | LV5207LP_MLED4 |
(brightness - 1));
} else {
lv5207lp_write(lv, LV5207LP_CTRL1, 0);
lv5207lp_write(lv, LV5207LP_CTRL2, 0);
}
return 0;
}
static int lv5207lp_backlight_check_fb(struct backlight_device *backlight,
struct fb_info *info)
{
struct lv5207lp *lv = bl_get_data(backlight);
return lv->pdata->fbdev == NULL || lv->pdata->fbdev == info->dev;
}
static const struct backlight_ops lv5207lp_backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = lv5207lp_backlight_update_status,
.check_fb = lv5207lp_backlight_check_fb,
};
static int lv5207lp_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct lv5207lp_platform_data *pdata = dev_get_platdata(&client->dev);
struct backlight_device *backlight;
struct backlight_properties props;
struct lv5207lp *lv;
if (pdata == NULL) {
dev_err(&client->dev, "No platform data supplied\n");
return -EINVAL;
}
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_warn(&client->dev,
"I2C adapter doesn't support I2C_FUNC_SMBUS_BYTE\n");
return -EIO;
}
lv = devm_kzalloc(&client->dev, sizeof(*lv), GFP_KERNEL);
if (!lv)
return -ENOMEM;
lv->client = client;
lv->pdata = pdata;
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW;
props.max_brightness = min_t(unsigned int, pdata->max_value,
LV5207LP_MAX_BRIGHTNESS);
props.brightness = clamp_t(unsigned int, pdata->def_value, 0,
props.max_brightness);
backlight = devm_backlight_device_register(&client->dev,
dev_name(&client->dev), &lv->client->dev,
lv, &lv5207lp_backlight_ops, &props);
if (IS_ERR(backlight)) {
dev_err(&client->dev, "failed to register backlight\n");
return PTR_ERR(backlight);
}
backlight_update_status(backlight);
i2c_set_clientdata(client, backlight);
return 0;
}
static int lv5207lp_remove(struct i2c_client *client)
{
struct backlight_device *backlight = i2c_get_clientdata(client);
backlight->props.brightness = 0;
backlight_update_status(backlight);
return 0;
}
static const struct i2c_device_id lv5207lp_ids[] = {
{ "lv5207lp", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, lv5207lp_ids);
static struct i2c_driver lv5207lp_driver = {
.driver = {
.name = "lv5207lp",
},
.probe = lv5207lp_probe,
.remove = lv5207lp_remove,
.id_table = lv5207lp_ids,
};
module_i2c_driver(lv5207lp_driver);
MODULE_DESCRIPTION("Sanyo LV5207LP Backlight Driver");
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,209 @@
/*
* Backlight driver for Maxim MAX8925
*
* Copyright (C) 2009 Marvell International Ltd.
* Haojian Zhuang <haojian.zhuang@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/init.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/i2c.h>
#include <linux/backlight.h>
#include <linux/mfd/max8925.h>
#include <linux/slab.h>
#include <linux/module.h>
#define MAX_BRIGHTNESS (0xff)
#define MIN_BRIGHTNESS (0)
#define LWX_FREQ(x) (((x - 601) / 100) & 0x7)
struct max8925_backlight_data {
struct max8925_chip *chip;
int current_brightness;
int reg_mode_cntl;
int reg_cntl;
};
static int max8925_backlight_set(struct backlight_device *bl, int brightness)
{
struct max8925_backlight_data *data = bl_get_data(bl);
struct max8925_chip *chip = data->chip;
unsigned char value;
int ret;
if (brightness > MAX_BRIGHTNESS)
value = MAX_BRIGHTNESS;
else
value = brightness;
ret = max8925_reg_write(chip->i2c, data->reg_cntl, value);
if (ret < 0)
goto out;
if (!data->current_brightness && brightness)
/* enable WLED output */
ret = max8925_set_bits(chip->i2c, data->reg_mode_cntl, 1, 1);
else if (!brightness)
/* disable WLED output */
ret = max8925_set_bits(chip->i2c, data->reg_mode_cntl, 1, 0);
if (ret < 0)
goto out;
dev_dbg(chip->dev, "set brightness %d\n", value);
data->current_brightness = value;
return 0;
out:
dev_dbg(chip->dev, "set brightness %d failure with return value:%d\n",
value, ret);
return ret;
}
static int max8925_backlight_update_status(struct backlight_device *bl)
{
int brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.state & BL_CORE_SUSPENDED)
brightness = 0;
return max8925_backlight_set(bl, brightness);
}
static int max8925_backlight_get_brightness(struct backlight_device *bl)
{
struct max8925_backlight_data *data = bl_get_data(bl);
struct max8925_chip *chip = data->chip;
int ret;
ret = max8925_reg_read(chip->i2c, data->reg_cntl);
if (ret < 0)
return -EINVAL;
data->current_brightness = ret;
dev_dbg(chip->dev, "get brightness %d\n", data->current_brightness);
return ret;
}
static const struct backlight_ops max8925_backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = max8925_backlight_update_status,
.get_brightness = max8925_backlight_get_brightness,
};
static void max8925_backlight_dt_init(struct platform_device *pdev)
{
struct device_node *nproot = pdev->dev.parent->of_node, *np;
struct max8925_backlight_pdata *pdata;
u32 val;
if (!nproot || !IS_ENABLED(CONFIG_OF))
return;
pdata = devm_kzalloc(&pdev->dev,
sizeof(struct max8925_backlight_pdata),
GFP_KERNEL);
if (!pdata)
return;
np = of_find_node_by_name(nproot, "backlight");
if (!np) {
dev_err(&pdev->dev, "failed to find backlight node\n");
return;
}
if (!of_property_read_u32(np, "maxim,max8925-dual-string", &val))
pdata->dual_string = val;
pdev->dev.platform_data = pdata;
}
static int max8925_backlight_probe(struct platform_device *pdev)
{
struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
struct max8925_backlight_pdata *pdata;
struct max8925_backlight_data *data;
struct backlight_device *bl;
struct backlight_properties props;
struct resource *res;
unsigned char value;
int ret = 0;
data = devm_kzalloc(&pdev->dev, sizeof(struct max8925_backlight_data),
GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_REG, 0);
if (!res) {
dev_err(&pdev->dev, "No REG resource for mode control!\n");
return -ENXIO;
}
data->reg_mode_cntl = res->start;
res = platform_get_resource(pdev, IORESOURCE_REG, 1);
if (!res) {
dev_err(&pdev->dev, "No REG resource for control!\n");
return -ENXIO;
}
data->reg_cntl = res->start;
data->chip = chip;
data->current_brightness = 0;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = MAX_BRIGHTNESS;
bl = devm_backlight_device_register(&pdev->dev, "max8925-backlight",
&pdev->dev, data,
&max8925_backlight_ops, &props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
bl->props.brightness = MAX_BRIGHTNESS;
platform_set_drvdata(pdev, bl);
value = 0;
if (!pdev->dev.platform_data)
max8925_backlight_dt_init(pdev);
pdata = pdev->dev.platform_data;
if (pdata) {
if (pdata->lxw_scl)
value |= (1 << 7);
if (pdata->lxw_freq)
value |= (LWX_FREQ(pdata->lxw_freq) << 4);
if (pdata->dual_string)
value |= (1 << 1);
}
ret = max8925_set_bits(chip->i2c, data->reg_mode_cntl, 0xfe, value);
if (ret < 0)
return ret;
backlight_update_status(bl);
return 0;
}
static struct platform_driver max8925_backlight_driver = {
.driver = {
.name = "max8925-backlight",
},
.probe = max8925_backlight_probe,
};
module_platform_driver(max8925_backlight_driver);
MODULE_DESCRIPTION("Backlight Driver for Maxim MAX8925");
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:max8925-backlight");

View file

@ -0,0 +1,188 @@
/*
* Backlight driver for OMAP based boards.
*
* Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org>
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this package; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/slab.h>
#include <linux/platform_data/omap1_bl.h>
#include <mach/hardware.h>
#include <mach/mux.h>
#define OMAPBL_MAX_INTENSITY 0xff
struct omap_backlight {
int powermode;
int current_intensity;
struct device *dev;
struct omap_backlight_config *pdata;
};
static inline void omapbl_send_intensity(int intensity)
{
omap_writeb(intensity, OMAP_PWL_ENABLE);
}
static inline void omapbl_send_enable(int enable)
{
omap_writeb(enable, OMAP_PWL_CLK_ENABLE);
}
static void omapbl_blank(struct omap_backlight *bl, int mode)
{
if (bl->pdata->set_power)
bl->pdata->set_power(bl->dev, mode);
switch (mode) {
case FB_BLANK_NORMAL:
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_POWERDOWN:
omapbl_send_intensity(0);
omapbl_send_enable(0);
break;
case FB_BLANK_UNBLANK:
omapbl_send_intensity(bl->current_intensity);
omapbl_send_enable(1);
break;
}
}
#ifdef CONFIG_PM_SLEEP
static int omapbl_suspend(struct device *dev)
{
struct backlight_device *bl_dev = dev_get_drvdata(dev);
struct omap_backlight *bl = bl_get_data(bl_dev);
omapbl_blank(bl, FB_BLANK_POWERDOWN);
return 0;
}
static int omapbl_resume(struct device *dev)
{
struct backlight_device *bl_dev = dev_get_drvdata(dev);
struct omap_backlight *bl = bl_get_data(bl_dev);
omapbl_blank(bl, bl->powermode);
return 0;
}
#endif
static int omapbl_set_power(struct backlight_device *dev, int state)
{
struct omap_backlight *bl = bl_get_data(dev);
omapbl_blank(bl, state);
bl->powermode = state;
return 0;
}
static int omapbl_update_status(struct backlight_device *dev)
{
struct omap_backlight *bl = bl_get_data(dev);
if (bl->current_intensity != dev->props.brightness) {
if (bl->powermode == FB_BLANK_UNBLANK)
omapbl_send_intensity(dev->props.brightness);
bl->current_intensity = dev->props.brightness;
}
if (dev->props.fb_blank != bl->powermode)
omapbl_set_power(dev, dev->props.fb_blank);
return 0;
}
static int omapbl_get_intensity(struct backlight_device *dev)
{
struct omap_backlight *bl = bl_get_data(dev);
return bl->current_intensity;
}
static const struct backlight_ops omapbl_ops = {
.get_brightness = omapbl_get_intensity,
.update_status = omapbl_update_status,
};
static int omapbl_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct backlight_device *dev;
struct omap_backlight *bl;
struct omap_backlight_config *pdata = dev_get_platdata(&pdev->dev);
if (!pdata)
return -ENXIO;
bl = devm_kzalloc(&pdev->dev, sizeof(struct omap_backlight),
GFP_KERNEL);
if (unlikely(!bl))
return -ENOMEM;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = OMAPBL_MAX_INTENSITY;
dev = devm_backlight_device_register(&pdev->dev, "omap-bl", &pdev->dev,
bl, &omapbl_ops, &props);
if (IS_ERR(dev))
return PTR_ERR(dev);
bl->powermode = FB_BLANK_POWERDOWN;
bl->current_intensity = 0;
bl->pdata = pdata;
bl->dev = &pdev->dev;
platform_set_drvdata(pdev, dev);
omap_cfg_reg(PWL); /* Conflicts with UART3 */
dev->props.fb_blank = FB_BLANK_UNBLANK;
dev->props.brightness = pdata->default_intensity;
omapbl_update_status(dev);
dev_info(&pdev->dev, "OMAP LCD backlight initialised\n");
return 0;
}
static SIMPLE_DEV_PM_OPS(omapbl_pm_ops, omapbl_suspend, omapbl_resume);
static struct platform_driver omapbl_driver = {
.probe = omapbl_probe,
.driver = {
.name = "omap-bl",
.pm = &omapbl_pm_ops,
},
};
module_platform_driver(omapbl_driver);
MODULE_AUTHOR("Andrzej Zaborowski <balrog@zabor.org>");
MODULE_DESCRIPTION("OMAP LCD Backlight driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,165 @@
/*
* Copyright (C) 2012 Bachmann electronic GmbH
* Christian Gmeiner <christian.gmeiner@gmail.com>
*
* Backlight driver for ot200 visualisation device from
* Bachmann electronic GmbH.
*
* 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/fb.h>
#include <linux/backlight.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/cs5535.h>
static struct cs5535_mfgpt_timer *pwm_timer;
/* this array defines the mapping of brightness in % to pwm frequency */
static const u8 dim_table[101] = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,
4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9,
10, 10, 11, 11, 12, 12, 13, 14, 15, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28,
30, 31, 33, 35, 37, 39, 41, 43, 45, 47, 50,
53, 55, 58, 61, 65, 68, 72, 75, 79, 84, 88,
93, 97, 103, 108, 114, 120, 126, 133, 140,
147, 155, 163};
struct ot200_backlight_data {
int current_brightness;
};
#define GPIO_DIMM 27
#define SCALE 1
#define CMP1MODE 0x2 /* compare on GE; output high on compare
* greater than or equal */
#define PWM_SETUP (SCALE | CMP1MODE << 6 | MFGPT_SETUP_CNTEN)
#define MAX_COMP2 163
static int ot200_backlight_update_status(struct backlight_device *bl)
{
struct ot200_backlight_data *data = bl_get_data(bl);
int brightness = bl->props.brightness;
if (bl->props.state & BL_CORE_FBBLANK)
brightness = 0;
/* enable or disable PWM timer */
if (brightness == 0)
cs5535_mfgpt_write(pwm_timer, MFGPT_REG_SETUP, 0);
else if (data->current_brightness == 0) {
cs5535_mfgpt_write(pwm_timer, MFGPT_REG_COUNTER, 0);
cs5535_mfgpt_write(pwm_timer, MFGPT_REG_SETUP,
MFGPT_SETUP_CNTEN);
}
/* apply new brightness value */
cs5535_mfgpt_write(pwm_timer, MFGPT_REG_CMP1,
MAX_COMP2 - dim_table[brightness]);
data->current_brightness = brightness;
return 0;
}
static int ot200_backlight_get_brightness(struct backlight_device *bl)
{
struct ot200_backlight_data *data = bl_get_data(bl);
return data->current_brightness;
}
static const struct backlight_ops ot200_backlight_ops = {
.update_status = ot200_backlight_update_status,
.get_brightness = ot200_backlight_get_brightness,
};
static int ot200_backlight_probe(struct platform_device *pdev)
{
struct backlight_device *bl;
struct ot200_backlight_data *data;
struct backlight_properties props;
int retval = 0;
/* request gpio */
if (devm_gpio_request(&pdev->dev, GPIO_DIMM,
"ot200 backlight dimmer") < 0) {
dev_err(&pdev->dev, "failed to request GPIO %d\n", GPIO_DIMM);
return -ENODEV;
}
/* request timer */
pwm_timer = cs5535_mfgpt_alloc_timer(7, MFGPT_DOMAIN_ANY);
if (!pwm_timer) {
dev_err(&pdev->dev, "MFGPT 7 not available\n");
return -ENODEV;
}
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
retval = -ENOMEM;
goto error_devm_kzalloc;
}
/* setup gpio */
cs5535_gpio_set(GPIO_DIMM, GPIO_OUTPUT_ENABLE);
cs5535_gpio_set(GPIO_DIMM, GPIO_OUTPUT_AUX1);
/* setup timer */
cs5535_mfgpt_write(pwm_timer, MFGPT_REG_CMP1, 0);
cs5535_mfgpt_write(pwm_timer, MFGPT_REG_CMP2, MAX_COMP2);
cs5535_mfgpt_write(pwm_timer, MFGPT_REG_SETUP, PWM_SETUP);
data->current_brightness = 100;
props.max_brightness = 100;
props.brightness = 100;
props.type = BACKLIGHT_RAW;
bl = devm_backlight_device_register(&pdev->dev, dev_name(&pdev->dev),
&pdev->dev, data, &ot200_backlight_ops,
&props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
retval = PTR_ERR(bl);
goto error_devm_kzalloc;
}
platform_set_drvdata(pdev, bl);
return 0;
error_devm_kzalloc:
cs5535_mfgpt_free_timer(pwm_timer);
return retval;
}
static int ot200_backlight_remove(struct platform_device *pdev)
{
/* on module unload set brightness to 100% */
cs5535_mfgpt_write(pwm_timer, MFGPT_REG_COUNTER, 0);
cs5535_mfgpt_write(pwm_timer, MFGPT_REG_SETUP, MFGPT_SETUP_CNTEN);
cs5535_mfgpt_write(pwm_timer, MFGPT_REG_CMP1,
MAX_COMP2 - dim_table[100]);
cs5535_mfgpt_free_timer(pwm_timer);
return 0;
}
static struct platform_driver ot200_backlight_driver = {
.driver = {
.name = "ot200-backlight",
},
.probe = ot200_backlight_probe,
.remove = ot200_backlight_remove,
};
module_platform_driver(ot200_backlight_driver);
MODULE_DESCRIPTION("backlight driver for ot200 visualisation device");
MODULE_AUTHOR("Christian Gmeiner <christian.gmeiner@gmail.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:ot200-backlight");

View file

@ -0,0 +1,154 @@
/*
* Backlight driver for Pandora handheld.
* Pandora uses TWL4030 PWM0 -> TPS61161 combo for control backlight.
* Based on pwm_bl.c
*
* Copyright 2009,2012 Gražvydas Ignotas <notasas@gmail.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/platform_device.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/i2c/twl.h>
#include <linux/err.h>
#define TWL_PWM0_ON 0x00
#define TWL_PWM0_OFF 0x01
#define TWL_INTBR_GPBR1 0x0c
#define TWL_INTBR_PMBR1 0x0d
#define TWL_PMBR1_PWM0_MUXMASK 0x0c
#define TWL_PMBR1_PWM0 0x04
#define PWM0_CLK_ENABLE BIT(0)
#define PWM0_ENABLE BIT(2)
/* range accepted by hardware */
#define MIN_VALUE 9
#define MAX_VALUE 63
#define MAX_USER_VALUE (MAX_VALUE - MIN_VALUE)
#define PANDORABL_WAS_OFF BL_CORE_DRIVER1
static int pandora_backlight_update_status(struct backlight_device *bl)
{
int brightness = bl->props.brightness;
u8 r;
if (bl->props.power != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.state & BL_CORE_FBBLANK)
brightness = 0;
if (bl->props.state & BL_CORE_SUSPENDED)
brightness = 0;
if ((unsigned int)brightness > MAX_USER_VALUE)
brightness = MAX_USER_VALUE;
if (brightness == 0) {
if (bl->props.state & PANDORABL_WAS_OFF)
goto done;
/* first disable PWM0 output, then clock */
twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_GPBR1);
r &= ~PWM0_ENABLE;
twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
r &= ~PWM0_CLK_ENABLE;
twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
goto done;
}
if (bl->props.state & PANDORABL_WAS_OFF) {
/*
* set PWM duty cycle to max. TPS61161 seems to use this
* to calibrate it's PWM sensitivity when it starts.
*/
twl_i2c_write_u8(TWL_MODULE_PWM, MAX_VALUE, TWL_PWM0_OFF);
/* first enable clock, then PWM0 out */
twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_GPBR1);
r &= ~PWM0_ENABLE;
r |= PWM0_CLK_ENABLE;
twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
r |= PWM0_ENABLE;
twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_GPBR1);
/*
* TI made it very easy to enable digital control, so easy that
* it often triggers unintentionally and disabes PWM control,
* so wait until 1 wire mode detection window ends.
*/
usleep_range(2000, 10000);
}
twl_i2c_write_u8(TWL_MODULE_PWM, MIN_VALUE + brightness, TWL_PWM0_OFF);
done:
if (brightness != 0)
bl->props.state &= ~PANDORABL_WAS_OFF;
else
bl->props.state |= PANDORABL_WAS_OFF;
return 0;
}
static const struct backlight_ops pandora_backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = pandora_backlight_update_status,
};
static int pandora_backlight_probe(struct platform_device *pdev)
{
struct backlight_properties props;
struct backlight_device *bl;
u8 r;
memset(&props, 0, sizeof(props));
props.max_brightness = MAX_USER_VALUE;
props.type = BACKLIGHT_RAW;
bl = devm_backlight_device_register(&pdev->dev, pdev->name, &pdev->dev,
NULL, &pandora_backlight_ops, &props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
platform_set_drvdata(pdev, bl);
/* 64 cycle period, ON position 0 */
twl_i2c_write_u8(TWL_MODULE_PWM, 0x80, TWL_PWM0_ON);
bl->props.state |= PANDORABL_WAS_OFF;
bl->props.brightness = MAX_USER_VALUE;
backlight_update_status(bl);
/* enable PWM function in pin mux */
twl_i2c_read_u8(TWL4030_MODULE_INTBR, &r, TWL_INTBR_PMBR1);
r &= ~TWL_PMBR1_PWM0_MUXMASK;
r |= TWL_PMBR1_PWM0;
twl_i2c_write_u8(TWL4030_MODULE_INTBR, r, TWL_INTBR_PMBR1);
return 0;
}
static struct platform_driver pandora_backlight_driver = {
.driver = {
.name = "pandora-backlight",
},
.probe = pandora_backlight_probe,
};
module_platform_driver(pandora_backlight_driver);
MODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>");
MODULE_DESCRIPTION("Pandora Backlight Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pandora-backlight");

View file

@ -0,0 +1,164 @@
/*
* Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
* PCF50633 backlight device driver
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/backlight.h>
#include <linux/fb.h>
#include <linux/mfd/pcf50633/core.h>
#include <linux/mfd/pcf50633/backlight.h>
struct pcf50633_bl {
struct pcf50633 *pcf;
struct backlight_device *bl;
unsigned int brightness;
unsigned int brightness_limit;
};
/*
* pcf50633_bl_set_brightness_limit
*
* Update the brightness limit for the pc50633 backlight. The actual brightness
* will not go above the limit. This is useful to limit power drain for example
* on low battery.
*
* @dev: Pointer to a pcf50633 device
* @limit: The brightness limit. Valid values are 0-63
*/
int pcf50633_bl_set_brightness_limit(struct pcf50633 *pcf, unsigned int limit)
{
struct pcf50633_bl *pcf_bl = platform_get_drvdata(pcf->bl_pdev);
if (!pcf_bl)
return -ENODEV;
pcf_bl->brightness_limit = limit & 0x3f;
backlight_update_status(pcf_bl->bl);
return 0;
}
static int pcf50633_bl_update_status(struct backlight_device *bl)
{
struct pcf50633_bl *pcf_bl = bl_get_data(bl);
unsigned int new_brightness;
if (bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK) ||
bl->props.power != FB_BLANK_UNBLANK)
new_brightness = 0;
else if (bl->props.brightness < pcf_bl->brightness_limit)
new_brightness = bl->props.brightness;
else
new_brightness = pcf_bl->brightness_limit;
if (pcf_bl->brightness == new_brightness)
return 0;
if (new_brightness) {
pcf50633_reg_write(pcf_bl->pcf, PCF50633_REG_LEDOUT,
new_brightness);
if (!pcf_bl->brightness)
pcf50633_reg_write(pcf_bl->pcf, PCF50633_REG_LEDENA, 1);
} else {
pcf50633_reg_write(pcf_bl->pcf, PCF50633_REG_LEDENA, 0);
}
pcf_bl->brightness = new_brightness;
return 0;
}
static int pcf50633_bl_get_brightness(struct backlight_device *bl)
{
struct pcf50633_bl *pcf_bl = bl_get_data(bl);
return pcf_bl->brightness;
}
static const struct backlight_ops pcf50633_bl_ops = {
.get_brightness = pcf50633_bl_get_brightness,
.update_status = pcf50633_bl_update_status,
.options = BL_CORE_SUSPENDRESUME,
};
static int pcf50633_bl_probe(struct platform_device *pdev)
{
struct pcf50633_bl *pcf_bl;
struct device *parent = pdev->dev.parent;
struct pcf50633_platform_data *pcf50633_data = dev_get_platdata(parent);
struct pcf50633_bl_platform_data *pdata = pcf50633_data->backlight_data;
struct backlight_properties bl_props;
pcf_bl = devm_kzalloc(&pdev->dev, sizeof(*pcf_bl), GFP_KERNEL);
if (!pcf_bl)
return -ENOMEM;
memset(&bl_props, 0, sizeof(bl_props));
bl_props.type = BACKLIGHT_RAW;
bl_props.max_brightness = 0x3f;
bl_props.power = FB_BLANK_UNBLANK;
if (pdata) {
bl_props.brightness = pdata->default_brightness;
pcf_bl->brightness_limit = pdata->default_brightness_limit;
} else {
bl_props.brightness = 0x3f;
pcf_bl->brightness_limit = 0x3f;
}
pcf_bl->pcf = dev_to_pcf50633(pdev->dev.parent);
pcf_bl->bl = devm_backlight_device_register(&pdev->dev, pdev->name,
&pdev->dev, pcf_bl,
&pcf50633_bl_ops, &bl_props);
if (IS_ERR(pcf_bl->bl))
return PTR_ERR(pcf_bl->bl);
platform_set_drvdata(pdev, pcf_bl);
pcf50633_reg_write(pcf_bl->pcf, PCF50633_REG_LEDDIM, pdata->ramp_time);
/*
* Should be different from bl_props.brightness, so we do not exit
* update_status early the first time it's called
*/
pcf_bl->brightness = pcf_bl->bl->props.brightness + 1;
backlight_update_status(pcf_bl->bl);
return 0;
}
static struct platform_driver pcf50633_bl_driver = {
.probe = pcf50633_bl_probe,
.driver = {
.name = "pcf50633-backlight",
},
};
module_platform_driver(pcf50633_bl_driver);
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
MODULE_DESCRIPTION("PCF50633 backlight driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pcf50633-backlight");

View file

@ -0,0 +1,161 @@
/* drivers/video/backlight/platform_lcd.c
*
* Copyright 2008 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>
*
* Generic platform-device LCD power control interface.
*
* 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/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/lcd.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <video/platform_lcd.h>
struct platform_lcd {
struct device *us;
struct lcd_device *lcd;
struct plat_lcd_data *pdata;
unsigned int power;
unsigned int suspended:1;
};
static inline struct platform_lcd *to_our_lcd(struct lcd_device *lcd)
{
return lcd_get_data(lcd);
}
static int platform_lcd_get_power(struct lcd_device *lcd)
{
struct platform_lcd *plcd = to_our_lcd(lcd);
return plcd->power;
}
static int platform_lcd_set_power(struct lcd_device *lcd, int power)
{
struct platform_lcd *plcd = to_our_lcd(lcd);
int lcd_power = 1;
if (power == FB_BLANK_POWERDOWN || plcd->suspended)
lcd_power = 0;
plcd->pdata->set_power(plcd->pdata, lcd_power);
plcd->power = power;
return 0;
}
static int platform_lcd_match(struct lcd_device *lcd, struct fb_info *info)
{
struct platform_lcd *plcd = to_our_lcd(lcd);
struct plat_lcd_data *pdata = plcd->pdata;
if (pdata->match_fb)
return pdata->match_fb(pdata, info);
return plcd->us->parent == info->device;
}
static struct lcd_ops platform_lcd_ops = {
.get_power = platform_lcd_get_power,
.set_power = platform_lcd_set_power,
.check_fb = platform_lcd_match,
};
static int platform_lcd_probe(struct platform_device *pdev)
{
struct plat_lcd_data *pdata;
struct platform_lcd *plcd;
struct device *dev = &pdev->dev;
int err;
pdata = dev_get_platdata(&pdev->dev);
if (!pdata) {
dev_err(dev, "no platform data supplied\n");
return -EINVAL;
}
if (pdata->probe) {
err = pdata->probe(pdata);
if (err)
return err;
}
plcd = devm_kzalloc(&pdev->dev, sizeof(struct platform_lcd),
GFP_KERNEL);
if (!plcd)
return -ENOMEM;
plcd->us = dev;
plcd->pdata = pdata;
plcd->lcd = devm_lcd_device_register(&pdev->dev, dev_name(dev), dev,
plcd, &platform_lcd_ops);
if (IS_ERR(plcd->lcd)) {
dev_err(dev, "cannot register lcd device\n");
return PTR_ERR(plcd->lcd);
}
platform_set_drvdata(pdev, plcd);
platform_lcd_set_power(plcd->lcd, FB_BLANK_NORMAL);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int platform_lcd_suspend(struct device *dev)
{
struct platform_lcd *plcd = dev_get_drvdata(dev);
plcd->suspended = 1;
platform_lcd_set_power(plcd->lcd, plcd->power);
return 0;
}
static int platform_lcd_resume(struct device *dev)
{
struct platform_lcd *plcd = dev_get_drvdata(dev);
plcd->suspended = 0;
platform_lcd_set_power(plcd->lcd, plcd->power);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(platform_lcd_pm_ops, platform_lcd_suspend,
platform_lcd_resume);
#ifdef CONFIG_OF
static const struct of_device_id platform_lcd_of_match[] = {
{ .compatible = "platform-lcd" },
{},
};
MODULE_DEVICE_TABLE(of, platform_lcd_of_match);
#endif
static struct platform_driver platform_lcd_driver = {
.driver = {
.name = "platform-lcd",
.pm = &platform_lcd_pm_ops,
.of_match_table = of_match_ptr(platform_lcd_of_match),
},
.probe = platform_lcd_probe,
};
module_platform_driver(platform_lcd_driver);
MODULE_AUTHOR("Ben Dooks <ben-linux@fluff.org>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:platform-lcd");

View file

@ -0,0 +1,405 @@
/*
* linux/drivers/video/backlight/pwm_bl.c
*
* simple PWM based backlight control, board code has to setup
* 1) pin configuration so PWM waveforms can output
* 2) platform_data being correctly configured
*
* 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/gpio/consumer.h>
#include <linux/gpio.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/pwm_backlight.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
struct pwm_bl_data {
struct pwm_device *pwm;
struct device *dev;
unsigned int period;
unsigned int lth_brightness;
unsigned int *levels;
bool enabled;
struct regulator *power_supply;
struct gpio_desc *enable_gpio;
unsigned int scale;
int (*notify)(struct device *,
int brightness);
void (*notify_after)(struct device *,
int brightness);
int (*check_fb)(struct device *, struct fb_info *);
void (*exit)(struct device *);
};
static void pwm_backlight_power_on(struct pwm_bl_data *pb, int brightness)
{
int err;
if (pb->enabled)
return;
err = regulator_enable(pb->power_supply);
if (err < 0)
dev_err(pb->dev, "failed to enable power supply\n");
if (pb->enable_gpio)
gpiod_set_value(pb->enable_gpio, 1);
pwm_enable(pb->pwm);
pb->enabled = true;
}
static void pwm_backlight_power_off(struct pwm_bl_data *pb)
{
if (!pb->enabled)
return;
pwm_config(pb->pwm, 0, pb->period);
pwm_disable(pb->pwm);
if (pb->enable_gpio)
gpiod_set_value(pb->enable_gpio, 0);
regulator_disable(pb->power_supply);
pb->enabled = false;
}
static int compute_duty_cycle(struct pwm_bl_data *pb, int brightness)
{
unsigned int lth = pb->lth_brightness;
int duty_cycle;
if (pb->levels)
duty_cycle = pb->levels[brightness];
else
duty_cycle = brightness;
return (duty_cycle * (pb->period - lth) / pb->scale) + lth;
}
static int pwm_backlight_update_status(struct backlight_device *bl)
{
struct pwm_bl_data *pb = bl_get_data(bl);
int brightness = bl->props.brightness;
int duty_cycle;
if (bl->props.power != FB_BLANK_UNBLANK ||
bl->props.fb_blank != FB_BLANK_UNBLANK ||
bl->props.state & BL_CORE_FBBLANK)
brightness = 0;
if (pb->notify)
brightness = pb->notify(pb->dev, brightness);
if (brightness > 0) {
duty_cycle = compute_duty_cycle(pb, brightness);
pwm_config(pb->pwm, duty_cycle, pb->period);
pwm_backlight_power_on(pb, brightness);
} else
pwm_backlight_power_off(pb);
if (pb->notify_after)
pb->notify_after(pb->dev, brightness);
return 0;
}
static int pwm_backlight_check_fb(struct backlight_device *bl,
struct fb_info *info)
{
struct pwm_bl_data *pb = bl_get_data(bl);
return !pb->check_fb || pb->check_fb(pb->dev, info);
}
static const struct backlight_ops pwm_backlight_ops = {
.update_status = pwm_backlight_update_status,
.check_fb = pwm_backlight_check_fb,
};
#ifdef CONFIG_OF
static int pwm_backlight_parse_dt(struct device *dev,
struct platform_pwm_backlight_data *data)
{
struct device_node *node = dev->of_node;
struct property *prop;
int length;
u32 value;
int ret;
if (!node)
return -ENODEV;
memset(data, 0, sizeof(*data));
/* determine the number of brightness levels */
prop = of_find_property(node, "brightness-levels", &length);
if (!prop)
return -EINVAL;
data->max_brightness = length / sizeof(u32);
/* read brightness levels from DT property */
if (data->max_brightness > 0) {
size_t size = sizeof(*data->levels) * data->max_brightness;
data->levels = devm_kzalloc(dev, size, GFP_KERNEL);
if (!data->levels)
return -ENOMEM;
ret = of_property_read_u32_array(node, "brightness-levels",
data->levels,
data->max_brightness);
if (ret < 0)
return ret;
ret = of_property_read_u32(node, "default-brightness-level",
&value);
if (ret < 0)
return ret;
data->dft_brightness = value;
data->max_brightness--;
}
data->enable_gpio = -EINVAL;
return 0;
}
static struct of_device_id pwm_backlight_of_match[] = {
{ .compatible = "pwm-backlight" },
{ }
};
MODULE_DEVICE_TABLE(of, pwm_backlight_of_match);
#else
static int pwm_backlight_parse_dt(struct device *dev,
struct platform_pwm_backlight_data *data)
{
return -ENODEV;
}
#endif
static int pwm_backlight_probe(struct platform_device *pdev)
{
struct platform_pwm_backlight_data *data = dev_get_platdata(&pdev->dev);
struct platform_pwm_backlight_data defdata;
struct backlight_properties props;
struct backlight_device *bl;
struct pwm_bl_data *pb;
int ret;
if (!data) {
ret = pwm_backlight_parse_dt(&pdev->dev, &defdata);
if (ret < 0) {
dev_err(&pdev->dev, "failed to find platform data\n");
return ret;
}
data = &defdata;
}
if (data->init) {
ret = data->init(&pdev->dev);
if (ret < 0)
return ret;
}
pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL);
if (!pb) {
ret = -ENOMEM;
goto err_alloc;
}
if (data->levels) {
unsigned int i;
for (i = 0; i <= data->max_brightness; i++)
if (data->levels[i] > pb->scale)
pb->scale = data->levels[i];
pb->levels = data->levels;
} else
pb->scale = data->max_brightness;
pb->notify = data->notify;
pb->notify_after = data->notify_after;
pb->check_fb = data->check_fb;
pb->exit = data->exit;
pb->dev = &pdev->dev;
pb->enabled = false;
pb->enable_gpio = devm_gpiod_get_optional(&pdev->dev, "enable");
if (IS_ERR(pb->enable_gpio)) {
ret = PTR_ERR(pb->enable_gpio);
goto err_alloc;
}
/*
* Compatibility fallback for drivers still using the integer GPIO
* platform data. Must go away soon.
*/
if (!pb->enable_gpio && gpio_is_valid(data->enable_gpio)) {
ret = devm_gpio_request_one(&pdev->dev, data->enable_gpio,
GPIOF_OUT_INIT_HIGH, "enable");
if (ret < 0) {
dev_err(&pdev->dev, "failed to request GPIO#%d: %d\n",
data->enable_gpio, ret);
goto err_alloc;
}
pb->enable_gpio = gpio_to_desc(data->enable_gpio);
}
if (pb->enable_gpio)
gpiod_direction_output(pb->enable_gpio, 1);
pb->power_supply = devm_regulator_get(&pdev->dev, "power");
if (IS_ERR(pb->power_supply)) {
ret = PTR_ERR(pb->power_supply);
goto err_alloc;
}
pb->pwm = devm_pwm_get(&pdev->dev, NULL);
if (IS_ERR(pb->pwm)) {
dev_err(&pdev->dev, "unable to request PWM, trying legacy API\n");
pb->pwm = pwm_request(data->pwm_id, "pwm-backlight");
if (IS_ERR(pb->pwm)) {
dev_err(&pdev->dev, "unable to request legacy PWM\n");
ret = PTR_ERR(pb->pwm);
goto err_alloc;
}
}
dev_dbg(&pdev->dev, "got pwm for backlight\n");
/*
* The DT case will set the pwm_period_ns field to 0 and store the
* period, parsed from the DT, in the PWM device. For the non-DT case,
* set the period from platform data if it has not already been set
* via the PWM lookup table.
*/
pb->period = pwm_get_period(pb->pwm);
if (!pb->period && (data->pwm_period_ns > 0)) {
pb->period = data->pwm_period_ns;
pwm_set_period(pb->pwm, data->pwm_period_ns);
}
pb->lth_brightness = data->lth_brightness * (pb->period / pb->scale);
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = data->max_brightness;
bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,
&pwm_backlight_ops, &props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
ret = PTR_ERR(bl);
goto err_alloc;
}
if (data->dft_brightness > data->max_brightness) {
dev_warn(&pdev->dev,
"invalid default brightness level: %u, using %u\n",
data->dft_brightness, data->max_brightness);
data->dft_brightness = data->max_brightness;
}
bl->props.brightness = data->dft_brightness;
backlight_update_status(bl);
platform_set_drvdata(pdev, bl);
return 0;
err_alloc:
if (data->exit)
data->exit(&pdev->dev);
return ret;
}
static int pwm_backlight_remove(struct platform_device *pdev)
{
struct backlight_device *bl = platform_get_drvdata(pdev);
struct pwm_bl_data *pb = bl_get_data(bl);
backlight_device_unregister(bl);
pwm_backlight_power_off(pb);
if (pb->exit)
pb->exit(&pdev->dev);
return 0;
}
static void pwm_backlight_shutdown(struct platform_device *pdev)
{
struct backlight_device *bl = platform_get_drvdata(pdev);
struct pwm_bl_data *pb = bl_get_data(bl);
pwm_backlight_power_off(pb);
}
#ifdef CONFIG_PM_SLEEP
static int pwm_backlight_suspend(struct device *dev)
{
struct backlight_device *bl = dev_get_drvdata(dev);
struct pwm_bl_data *pb = bl_get_data(bl);
if (pb->notify)
pb->notify(pb->dev, 0);
pwm_backlight_power_off(pb);
if (pb->notify_after)
pb->notify_after(pb->dev, 0);
return 0;
}
static int pwm_backlight_resume(struct device *dev)
{
struct backlight_device *bl = dev_get_drvdata(dev);
backlight_update_status(bl);
return 0;
}
#endif
static const struct dev_pm_ops pwm_backlight_pm_ops = {
#ifdef CONFIG_PM_SLEEP
.suspend = pwm_backlight_suspend,
.resume = pwm_backlight_resume,
.poweroff = pwm_backlight_suspend,
.restore = pwm_backlight_resume,
#endif
};
static struct platform_driver pwm_backlight_driver = {
.driver = {
.name = "pwm-backlight",
.pm = &pwm_backlight_pm_ops,
.of_match_table = of_match_ptr(pwm_backlight_of_match),
},
.probe = pwm_backlight_probe,
.remove = pwm_backlight_remove,
.shutdown = pwm_backlight_shutdown,
};
module_platform_driver(pwm_backlight_driver);
MODULE_DESCRIPTION("PWM based Backlight Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pwm-backlight");

View file

@ -0,0 +1,858 @@
/*
* S6E63M0 AMOLED LCD panel driver.
*
* Author: InKi Dae <inki.dae@samsung.com>
*
* Derived from drivers/video/omap/lcd-apollon.c
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/backlight.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/wait.h>
#include "s6e63m0_gamma.h"
#define SLEEPMSEC 0x1000
#define ENDDEF 0x2000
#define DEFMASK 0xFF00
#define COMMAND_ONLY 0xFE
#define DATA_ONLY 0xFF
#define MIN_BRIGHTNESS 0
#define MAX_BRIGHTNESS 10
struct s6e63m0 {
struct device *dev;
struct spi_device *spi;
unsigned int power;
unsigned int current_brightness;
unsigned int gamma_mode;
unsigned int gamma_table_count;
struct lcd_device *ld;
struct backlight_device *bd;
struct lcd_platform_data *lcd_pd;
};
static const unsigned short seq_panel_condition_set[] = {
0xF8, 0x01,
DATA_ONLY, 0x27,
DATA_ONLY, 0x27,
DATA_ONLY, 0x07,
DATA_ONLY, 0x07,
DATA_ONLY, 0x54,
DATA_ONLY, 0x9f,
DATA_ONLY, 0x63,
DATA_ONLY, 0x86,
DATA_ONLY, 0x1a,
DATA_ONLY, 0x33,
DATA_ONLY, 0x0d,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
ENDDEF, 0x0000
};
static const unsigned short seq_display_condition_set[] = {
0xf2, 0x02,
DATA_ONLY, 0x03,
DATA_ONLY, 0x1c,
DATA_ONLY, 0x10,
DATA_ONLY, 0x10,
0xf7, 0x03,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
ENDDEF, 0x0000
};
static const unsigned short seq_gamma_setting[] = {
0xfa, 0x00,
DATA_ONLY, 0x18,
DATA_ONLY, 0x08,
DATA_ONLY, 0x24,
DATA_ONLY, 0x64,
DATA_ONLY, 0x56,
DATA_ONLY, 0x33,
DATA_ONLY, 0xb6,
DATA_ONLY, 0xba,
DATA_ONLY, 0xa8,
DATA_ONLY, 0xac,
DATA_ONLY, 0xb1,
DATA_ONLY, 0x9d,
DATA_ONLY, 0xc1,
DATA_ONLY, 0xc1,
DATA_ONLY, 0xb7,
DATA_ONLY, 0x00,
DATA_ONLY, 0x9c,
DATA_ONLY, 0x00,
DATA_ONLY, 0x9f,
DATA_ONLY, 0x00,
DATA_ONLY, 0xd6,
0xfa, 0x01,
ENDDEF, 0x0000
};
static const unsigned short seq_etc_condition_set[] = {
0xf6, 0x00,
DATA_ONLY, 0x8c,
DATA_ONLY, 0x07,
0xb3, 0xc,
0xb5, 0x2c,
DATA_ONLY, 0x12,
DATA_ONLY, 0x0c,
DATA_ONLY, 0x0a,
DATA_ONLY, 0x10,
DATA_ONLY, 0x0e,
DATA_ONLY, 0x17,
DATA_ONLY, 0x13,
DATA_ONLY, 0x1f,
DATA_ONLY, 0x1a,
DATA_ONLY, 0x2a,
DATA_ONLY, 0x24,
DATA_ONLY, 0x1f,
DATA_ONLY, 0x1b,
DATA_ONLY, 0x1a,
DATA_ONLY, 0x17,
DATA_ONLY, 0x2b,
DATA_ONLY, 0x26,
DATA_ONLY, 0x22,
DATA_ONLY, 0x20,
DATA_ONLY, 0x3a,
DATA_ONLY, 0x34,
DATA_ONLY, 0x30,
DATA_ONLY, 0x2c,
DATA_ONLY, 0x29,
DATA_ONLY, 0x26,
DATA_ONLY, 0x25,
DATA_ONLY, 0x23,
DATA_ONLY, 0x21,
DATA_ONLY, 0x20,
DATA_ONLY, 0x1e,
DATA_ONLY, 0x1e,
0xb6, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x11,
DATA_ONLY, 0x22,
DATA_ONLY, 0x33,
DATA_ONLY, 0x44,
DATA_ONLY, 0x44,
DATA_ONLY, 0x44,
DATA_ONLY, 0x55,
DATA_ONLY, 0x55,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
0xb7, 0x2c,
DATA_ONLY, 0x12,
DATA_ONLY, 0x0c,
DATA_ONLY, 0x0a,
DATA_ONLY, 0x10,
DATA_ONLY, 0x0e,
DATA_ONLY, 0x17,
DATA_ONLY, 0x13,
DATA_ONLY, 0x1f,
DATA_ONLY, 0x1a,
DATA_ONLY, 0x2a,
DATA_ONLY, 0x24,
DATA_ONLY, 0x1f,
DATA_ONLY, 0x1b,
DATA_ONLY, 0x1a,
DATA_ONLY, 0x17,
DATA_ONLY, 0x2b,
DATA_ONLY, 0x26,
DATA_ONLY, 0x22,
DATA_ONLY, 0x20,
DATA_ONLY, 0x3a,
DATA_ONLY, 0x34,
DATA_ONLY, 0x30,
DATA_ONLY, 0x2c,
DATA_ONLY, 0x29,
DATA_ONLY, 0x26,
DATA_ONLY, 0x25,
DATA_ONLY, 0x23,
DATA_ONLY, 0x21,
DATA_ONLY, 0x20,
DATA_ONLY, 0x1e,
DATA_ONLY, 0x1e,
0xb8, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x11,
DATA_ONLY, 0x22,
DATA_ONLY, 0x33,
DATA_ONLY, 0x44,
DATA_ONLY, 0x44,
DATA_ONLY, 0x44,
DATA_ONLY, 0x55,
DATA_ONLY, 0x55,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
0xb9, 0x2c,
DATA_ONLY, 0x12,
DATA_ONLY, 0x0c,
DATA_ONLY, 0x0a,
DATA_ONLY, 0x10,
DATA_ONLY, 0x0e,
DATA_ONLY, 0x17,
DATA_ONLY, 0x13,
DATA_ONLY, 0x1f,
DATA_ONLY, 0x1a,
DATA_ONLY, 0x2a,
DATA_ONLY, 0x24,
DATA_ONLY, 0x1f,
DATA_ONLY, 0x1b,
DATA_ONLY, 0x1a,
DATA_ONLY, 0x17,
DATA_ONLY, 0x2b,
DATA_ONLY, 0x26,
DATA_ONLY, 0x22,
DATA_ONLY, 0x20,
DATA_ONLY, 0x3a,
DATA_ONLY, 0x34,
DATA_ONLY, 0x30,
DATA_ONLY, 0x2c,
DATA_ONLY, 0x29,
DATA_ONLY, 0x26,
DATA_ONLY, 0x25,
DATA_ONLY, 0x23,
DATA_ONLY, 0x21,
DATA_ONLY, 0x20,
DATA_ONLY, 0x1e,
DATA_ONLY, 0x1e,
0xba, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x11,
DATA_ONLY, 0x22,
DATA_ONLY, 0x33,
DATA_ONLY, 0x44,
DATA_ONLY, 0x44,
DATA_ONLY, 0x44,
DATA_ONLY, 0x55,
DATA_ONLY, 0x55,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
DATA_ONLY, 0x66,
0xc1, 0x4d,
DATA_ONLY, 0x96,
DATA_ONLY, 0x1d,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x01,
DATA_ONLY, 0xdf,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x03,
DATA_ONLY, 0x1f,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x00,
DATA_ONLY, 0x03,
DATA_ONLY, 0x06,
DATA_ONLY, 0x09,
DATA_ONLY, 0x0d,
DATA_ONLY, 0x0f,
DATA_ONLY, 0x12,
DATA_ONLY, 0x15,
DATA_ONLY, 0x18,
0xb2, 0x10,
DATA_ONLY, 0x10,
DATA_ONLY, 0x0b,
DATA_ONLY, 0x05,
ENDDEF, 0x0000
};
static const unsigned short seq_acl_on[] = {
/* ACL on */
0xc0, 0x01,
ENDDEF, 0x0000
};
static const unsigned short seq_acl_off[] = {
/* ACL off */
0xc0, 0x00,
ENDDEF, 0x0000
};
static const unsigned short seq_elvss_on[] = {
/* ELVSS on */
0xb1, 0x0b,
ENDDEF, 0x0000
};
static const unsigned short seq_elvss_off[] = {
/* ELVSS off */
0xb1, 0x0a,
ENDDEF, 0x0000
};
static const unsigned short seq_stand_by_off[] = {
0x11, COMMAND_ONLY,
ENDDEF, 0x0000
};
static const unsigned short seq_stand_by_on[] = {
0x10, COMMAND_ONLY,
ENDDEF, 0x0000
};
static const unsigned short seq_display_on[] = {
0x29, COMMAND_ONLY,
ENDDEF, 0x0000
};
static int s6e63m0_spi_write_byte(struct s6e63m0 *lcd, int addr, int data)
{
u16 buf[1];
struct spi_message msg;
struct spi_transfer xfer = {
.len = 2,
.tx_buf = buf,
};
buf[0] = (addr << 8) | data;
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
return spi_sync(lcd->spi, &msg);
}
static int s6e63m0_spi_write(struct s6e63m0 *lcd, unsigned char address,
unsigned char command)
{
int ret = 0;
if (address != DATA_ONLY)
ret = s6e63m0_spi_write_byte(lcd, 0x0, address);
if (command != COMMAND_ONLY)
ret = s6e63m0_spi_write_byte(lcd, 0x1, command);
return ret;
}
static int s6e63m0_panel_send_sequence(struct s6e63m0 *lcd,
const unsigned short *wbuf)
{
int ret = 0, i = 0;
while ((wbuf[i] & DEFMASK) != ENDDEF) {
if ((wbuf[i] & DEFMASK) != SLEEPMSEC) {
ret = s6e63m0_spi_write(lcd, wbuf[i], wbuf[i+1]);
if (ret)
break;
} else {
msleep(wbuf[i+1]);
}
i += 2;
}
return ret;
}
static int _s6e63m0_gamma_ctl(struct s6e63m0 *lcd, const unsigned int *gamma)
{
unsigned int i = 0;
int ret = 0;
/* disable gamma table updating. */
ret = s6e63m0_spi_write(lcd, 0xfa, 0x00);
if (ret) {
dev_err(lcd->dev, "failed to disable gamma table updating.\n");
goto gamma_err;
}
for (i = 0 ; i < GAMMA_TABLE_COUNT; i++) {
ret = s6e63m0_spi_write(lcd, DATA_ONLY, gamma[i]);
if (ret) {
dev_err(lcd->dev, "failed to set gamma table.\n");
goto gamma_err;
}
}
/* update gamma table. */
ret = s6e63m0_spi_write(lcd, 0xfa, 0x01);
if (ret)
dev_err(lcd->dev, "failed to update gamma table.\n");
gamma_err:
return ret;
}
static int s6e63m0_gamma_ctl(struct s6e63m0 *lcd, int gamma)
{
int ret = 0;
ret = _s6e63m0_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
return ret;
}
static int s6e63m0_ldi_init(struct s6e63m0 *lcd)
{
int ret, i;
const unsigned short *init_seq[] = {
seq_panel_condition_set,
seq_display_condition_set,
seq_gamma_setting,
seq_etc_condition_set,
seq_acl_on,
seq_elvss_on,
};
for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
ret = s6e63m0_panel_send_sequence(lcd, init_seq[i]);
if (ret)
break;
}
return ret;
}
static int s6e63m0_ldi_enable(struct s6e63m0 *lcd)
{
int ret = 0, i;
const unsigned short *enable_seq[] = {
seq_stand_by_off,
seq_display_on,
};
for (i = 0; i < ARRAY_SIZE(enable_seq); i++) {
ret = s6e63m0_panel_send_sequence(lcd, enable_seq[i]);
if (ret)
break;
}
return ret;
}
static int s6e63m0_ldi_disable(struct s6e63m0 *lcd)
{
int ret;
ret = s6e63m0_panel_send_sequence(lcd, seq_stand_by_on);
return ret;
}
static int s6e63m0_power_is_on(int power)
{
return power <= FB_BLANK_NORMAL;
}
static int s6e63m0_power_on(struct s6e63m0 *lcd)
{
int ret = 0;
struct lcd_platform_data *pd;
struct backlight_device *bd;
pd = lcd->lcd_pd;
bd = lcd->bd;
if (!pd->power_on) {
dev_err(lcd->dev, "power_on is NULL.\n");
return -EINVAL;
}
pd->power_on(lcd->ld, 1);
msleep(pd->power_on_delay);
if (!pd->reset) {
dev_err(lcd->dev, "reset is NULL.\n");
return -EINVAL;
}
pd->reset(lcd->ld);
msleep(pd->reset_delay);
ret = s6e63m0_ldi_init(lcd);
if (ret) {
dev_err(lcd->dev, "failed to initialize ldi.\n");
return ret;
}
ret = s6e63m0_ldi_enable(lcd);
if (ret) {
dev_err(lcd->dev, "failed to enable ldi.\n");
return ret;
}
/* set brightness to current value after power on or resume. */
ret = s6e63m0_gamma_ctl(lcd, bd->props.brightness);
if (ret) {
dev_err(lcd->dev, "lcd gamma setting failed.\n");
return ret;
}
return 0;
}
static int s6e63m0_power_off(struct s6e63m0 *lcd)
{
int ret;
struct lcd_platform_data *pd;
pd = lcd->lcd_pd;
ret = s6e63m0_ldi_disable(lcd);
if (ret) {
dev_err(lcd->dev, "lcd setting failed.\n");
return -EIO;
}
msleep(pd->power_off_delay);
pd->power_on(lcd->ld, 0);
return 0;
}
static int s6e63m0_power(struct s6e63m0 *lcd, int power)
{
int ret = 0;
if (s6e63m0_power_is_on(power) && !s6e63m0_power_is_on(lcd->power))
ret = s6e63m0_power_on(lcd);
else if (!s6e63m0_power_is_on(power) && s6e63m0_power_is_on(lcd->power))
ret = s6e63m0_power_off(lcd);
if (!ret)
lcd->power = power;
return ret;
}
static int s6e63m0_set_power(struct lcd_device *ld, int power)
{
struct s6e63m0 *lcd = lcd_get_data(ld);
if (power != FB_BLANK_UNBLANK && power != FB_BLANK_POWERDOWN &&
power != FB_BLANK_NORMAL) {
dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
return -EINVAL;
}
return s6e63m0_power(lcd, power);
}
static int s6e63m0_get_power(struct lcd_device *ld)
{
struct s6e63m0 *lcd = lcd_get_data(ld);
return lcd->power;
}
static int s6e63m0_set_brightness(struct backlight_device *bd)
{
int ret = 0, brightness = bd->props.brightness;
struct s6e63m0 *lcd = bl_get_data(bd);
if (brightness < MIN_BRIGHTNESS ||
brightness > bd->props.max_brightness) {
dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
MIN_BRIGHTNESS, MAX_BRIGHTNESS);
return -EINVAL;
}
ret = s6e63m0_gamma_ctl(lcd, bd->props.brightness);
if (ret) {
dev_err(&bd->dev, "lcd brightness setting failed.\n");
return -EIO;
}
return ret;
}
static struct lcd_ops s6e63m0_lcd_ops = {
.set_power = s6e63m0_set_power,
.get_power = s6e63m0_get_power,
};
static const struct backlight_ops s6e63m0_backlight_ops = {
.update_status = s6e63m0_set_brightness,
};
static ssize_t s6e63m0_sysfs_show_gamma_mode(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s6e63m0 *lcd = dev_get_drvdata(dev);
char temp[10];
switch (lcd->gamma_mode) {
case 0:
sprintf(temp, "2.2 mode\n");
strcat(buf, temp);
break;
case 1:
sprintf(temp, "1.9 mode\n");
strcat(buf, temp);
break;
case 2:
sprintf(temp, "1.7 mode\n");
strcat(buf, temp);
break;
default:
dev_info(dev, "gamma mode could be 0:2.2, 1:1.9 or 2:1.7)n");
break;
}
return strlen(buf);
}
static ssize_t s6e63m0_sysfs_store_gamma_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct s6e63m0 *lcd = dev_get_drvdata(dev);
struct backlight_device *bd = NULL;
int brightness, rc;
rc = kstrtouint(buf, 0, &lcd->gamma_mode);
if (rc < 0)
return rc;
bd = lcd->bd;
brightness = bd->props.brightness;
switch (lcd->gamma_mode) {
case 0:
_s6e63m0_gamma_ctl(lcd, gamma_table.gamma_22_table[brightness]);
break;
case 1:
_s6e63m0_gamma_ctl(lcd, gamma_table.gamma_19_table[brightness]);
break;
case 2:
_s6e63m0_gamma_ctl(lcd, gamma_table.gamma_17_table[brightness]);
break;
default:
dev_info(dev, "gamma mode could be 0:2.2, 1:1.9 or 2:1.7\n");
_s6e63m0_gamma_ctl(lcd, gamma_table.gamma_22_table[brightness]);
break;
}
return len;
}
static DEVICE_ATTR(gamma_mode, 0644,
s6e63m0_sysfs_show_gamma_mode, s6e63m0_sysfs_store_gamma_mode);
static ssize_t s6e63m0_sysfs_show_gamma_table(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct s6e63m0 *lcd = dev_get_drvdata(dev);
char temp[3];
sprintf(temp, "%u\n", lcd->gamma_table_count);
strcpy(buf, temp);
return strlen(buf);
}
static DEVICE_ATTR(gamma_table, 0444,
s6e63m0_sysfs_show_gamma_table, NULL);
static int s6e63m0_probe(struct spi_device *spi)
{
int ret = 0;
struct s6e63m0 *lcd = NULL;
struct lcd_device *ld = NULL;
struct backlight_device *bd = NULL;
struct backlight_properties props;
lcd = devm_kzalloc(&spi->dev, sizeof(struct s6e63m0), GFP_KERNEL);
if (!lcd)
return -ENOMEM;
/* s6e63m0 lcd panel uses 3-wire 9bits SPI Mode. */
spi->bits_per_word = 9;
ret = spi_setup(spi);
if (ret < 0) {
dev_err(&spi->dev, "spi setup failed.\n");
return ret;
}
lcd->spi = spi;
lcd->dev = &spi->dev;
lcd->lcd_pd = dev_get_platdata(&spi->dev);
if (!lcd->lcd_pd) {
dev_err(&spi->dev, "platform data is NULL.\n");
return -EINVAL;
}
ld = devm_lcd_device_register(&spi->dev, "s6e63m0", &spi->dev, lcd,
&s6e63m0_lcd_ops);
if (IS_ERR(ld))
return PTR_ERR(ld);
lcd->ld = ld;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = MAX_BRIGHTNESS;
bd = devm_backlight_device_register(&spi->dev, "s6e63m0bl-bl",
&spi->dev, lcd, &s6e63m0_backlight_ops,
&props);
if (IS_ERR(bd))
return PTR_ERR(bd);
bd->props.brightness = MAX_BRIGHTNESS;
lcd->bd = bd;
/*
* it gets gamma table count available so it gets user
* know that.
*/
lcd->gamma_table_count =
sizeof(gamma_table) / (MAX_GAMMA_LEVEL * sizeof(int *));
ret = device_create_file(&(spi->dev), &dev_attr_gamma_mode);
if (ret < 0)
dev_err(&(spi->dev), "failed to add sysfs entries\n");
ret = device_create_file(&(spi->dev), &dev_attr_gamma_table);
if (ret < 0)
dev_err(&(spi->dev), "failed to add sysfs entries\n");
/*
* if lcd panel was on from bootloader like u-boot then
* do not lcd on.
*/
if (!lcd->lcd_pd->lcd_enabled) {
/*
* if lcd panel was off from bootloader then
* current lcd status is powerdown and then
* it enables lcd panel.
*/
lcd->power = FB_BLANK_POWERDOWN;
s6e63m0_power(lcd, FB_BLANK_UNBLANK);
} else {
lcd->power = FB_BLANK_UNBLANK;
}
spi_set_drvdata(spi, lcd);
dev_info(&spi->dev, "s6e63m0 panel driver has been probed.\n");
return 0;
}
static int s6e63m0_remove(struct spi_device *spi)
{
struct s6e63m0 *lcd = spi_get_drvdata(spi);
s6e63m0_power(lcd, FB_BLANK_POWERDOWN);
device_remove_file(&spi->dev, &dev_attr_gamma_table);
device_remove_file(&spi->dev, &dev_attr_gamma_mode);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int s6e63m0_suspend(struct device *dev)
{
struct s6e63m0 *lcd = dev_get_drvdata(dev);
dev_dbg(dev, "lcd->power = %d\n", lcd->power);
/*
* when lcd panel is suspend, lcd panel becomes off
* regardless of status.
*/
return s6e63m0_power(lcd, FB_BLANK_POWERDOWN);
}
static int s6e63m0_resume(struct device *dev)
{
struct s6e63m0 *lcd = dev_get_drvdata(dev);
lcd->power = FB_BLANK_POWERDOWN;
return s6e63m0_power(lcd, FB_BLANK_UNBLANK);
}
#endif
static SIMPLE_DEV_PM_OPS(s6e63m0_pm_ops, s6e63m0_suspend, s6e63m0_resume);
/* Power down all displays on reboot, poweroff or halt. */
static void s6e63m0_shutdown(struct spi_device *spi)
{
struct s6e63m0 *lcd = spi_get_drvdata(spi);
s6e63m0_power(lcd, FB_BLANK_POWERDOWN);
}
static struct spi_driver s6e63m0_driver = {
.driver = {
.name = "s6e63m0",
.owner = THIS_MODULE,
.pm = &s6e63m0_pm_ops,
},
.probe = s6e63m0_probe,
.remove = s6e63m0_remove,
.shutdown = s6e63m0_shutdown,
};
module_spi_driver(s6e63m0_driver);
MODULE_AUTHOR("InKi Dae <inki.dae@samsung.com>");
MODULE_DESCRIPTION("S6E63M0 LCD Driver");
MODULE_LICENSE("GPL");

View file

@ -0,0 +1,266 @@
/* linux/drivers/video/samsung/s6e63m0_brightness.h
*
* Gamma level definitions.
*
* Copyright (c) 2009 Samsung Electronics
* InKi Dae <inki.dae@samsung.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.
*/
#ifndef _S6E63M0_BRIGHTNESS_H
#define _S6E63M0_BRIGHTNESS_H
#define MAX_GAMMA_LEVEL 11
#define GAMMA_TABLE_COUNT 21
/* gamma value: 2.2 */
static const unsigned int s6e63m0_22_300[] = {
0x18, 0x08, 0x24, 0x5f, 0x50, 0x2d, 0xB6,
0xB9, 0xA7, 0xAd, 0xB1, 0x9f, 0xbe, 0xC0,
0xB5, 0x00, 0xa0, 0x00, 0xa4, 0x00, 0xdb
};
static const unsigned int s6e63m0_22_280[] = {
0x18, 0x08, 0x24, 0x64, 0x56, 0x33, 0xB6,
0xBA, 0xA8, 0xAC, 0xB1, 0x9D, 0xC1, 0xC1,
0xB7, 0x00, 0x9C, 0x00, 0x9F, 0x00, 0xD6
};
static const unsigned int s6e63m0_22_260[] = {
0x18, 0x08, 0x24, 0x66, 0x58, 0x34, 0xB6,
0xBA, 0xA7, 0xAF, 0xB3, 0xA0, 0xC1, 0xC2,
0xB7, 0x00, 0x97, 0x00, 0x9A, 0x00, 0xD1
};
static const unsigned int s6e63m0_22_240[] = {
0x18, 0x08, 0x24, 0x62, 0x54, 0x30, 0xB9,
0xBB, 0xA9, 0xB0, 0xB3, 0xA1, 0xC1, 0xC3,
0xB7, 0x00, 0x91, 0x00, 0x95, 0x00, 0xDA
};
static const unsigned int s6e63m0_22_220[] = {
0x18, 0x08, 0x24, 0x63, 0x53, 0x31, 0xB8,
0xBC, 0xA9, 0xB0, 0xB5, 0xA2, 0xC4, 0xC4,
0xB8, 0x00, 0x8B, 0x00, 0x8E, 0x00, 0xC2
};
static const unsigned int s6e63m0_22_200[] = {
0x18, 0x08, 0x24, 0x66, 0x55, 0x34, 0xBA,
0xBD, 0xAB, 0xB1, 0xB5, 0xA3, 0xC5, 0xC6,
0xB9, 0x00, 0x85, 0x00, 0x88, 0x00, 0xBA
};
static const unsigned int s6e63m0_22_170[] = {
0x18, 0x08, 0x24, 0x69, 0x54, 0x37, 0xBB,
0xBE, 0xAC, 0xB4, 0xB7, 0xA6, 0xC7, 0xC8,
0xBC, 0x00, 0x7B, 0x00, 0x7E, 0x00, 0xAB
};
static const unsigned int s6e63m0_22_140[] = {
0x18, 0x08, 0x24, 0x6C, 0x54, 0x3A, 0xBC,
0xBF, 0xAC, 0xB7, 0xBB, 0xA9, 0xC9, 0xC9,
0xBE, 0x00, 0x71, 0x00, 0x73, 0x00, 0x9E
};
static const unsigned int s6e63m0_22_110[] = {
0x18, 0x08, 0x24, 0x70, 0x51, 0x3E, 0xBF,
0xC1, 0xAF, 0xB9, 0xBC, 0xAB, 0xCC, 0xCC,
0xC2, 0x00, 0x65, 0x00, 0x67, 0x00, 0x8D
};
static const unsigned int s6e63m0_22_90[] = {
0x18, 0x08, 0x24, 0x73, 0x4A, 0x3D, 0xC0,
0xC2, 0xB1, 0xBB, 0xBE, 0xAC, 0xCE, 0xCF,
0xC5, 0x00, 0x5D, 0x00, 0x5E, 0x00, 0x82
};
static const unsigned int s6e63m0_22_30[] = {
0x18, 0x08, 0x24, 0x78, 0xEC, 0x3D, 0xC8,
0xC2, 0xB6, 0xC4, 0xC7, 0xB6, 0xD5, 0xD7,
0xCC, 0x00, 0x39, 0x00, 0x36, 0x00, 0x51
};
/* gamma value: 1.9 */
static const unsigned int s6e63m0_19_300[] = {
0x18, 0x08, 0x24, 0x61, 0x5F, 0x39, 0xBA,
0xBD, 0xAD, 0xB1, 0xB6, 0xA5, 0xC4, 0xC5,
0xBC, 0x00, 0xA0, 0x00, 0xA4, 0x00, 0xDB
};
static const unsigned int s6e63m0_19_280[] = {
0x18, 0x08, 0x24, 0x61, 0x60, 0x39, 0xBB,
0xBE, 0xAD, 0xB2, 0xB6, 0xA6, 0xC5, 0xC7,
0xBD, 0x00, 0x9B, 0x00, 0x9E, 0x00, 0xD5
};
static const unsigned int s6e63m0_19_260[] = {
0x18, 0x08, 0x24, 0x63, 0x61, 0x3B, 0xBA,
0xBE, 0xAC, 0xB3, 0xB8, 0xA7, 0xC6, 0xC8,
0xBD, 0x00, 0x96, 0x00, 0x98, 0x00, 0xCF
};
static const unsigned int s6e63m0_19_240[] = {
0x18, 0x08, 0x24, 0x67, 0x64, 0x3F, 0xBB,
0xBE, 0xAD, 0xB3, 0xB9, 0xA7, 0xC8, 0xC9,
0xBE, 0x00, 0x90, 0x00, 0x92, 0x00, 0xC8
};
static const unsigned int s6e63m0_19_220[] = {
0x18, 0x08, 0x24, 0x68, 0x64, 0x40, 0xBC,
0xBF, 0xAF, 0xB4, 0xBA, 0xA9, 0xC8, 0xCA,
0xBE, 0x00, 0x8B, 0x00, 0x8C, 0x00, 0xC0
};
static const unsigned int s6e63m0_19_200[] = {
0x18, 0x08, 0x24, 0x68, 0x64, 0x3F, 0xBE,
0xC0, 0xB0, 0xB6, 0xBB, 0xAB, 0xC8, 0xCB,
0xBF, 0x00, 0x85, 0x00, 0x86, 0x00, 0xB8
};
static const unsigned int s6e63m0_19_170[] = {
0x18, 0x08, 0x24, 0x69, 0x64, 0x40, 0xBF,
0xC1, 0xB0, 0xB9, 0xBE, 0xAD, 0xCB, 0xCD,
0xC2, 0x00, 0x7A, 0x00, 0x7B, 0x00, 0xAA
};
static const unsigned int s6e63m0_19_140[] = {
0x18, 0x08, 0x24, 0x6E, 0x65, 0x45, 0xC0,
0xC3, 0xB2, 0xBA, 0xBE, 0xAE, 0xCD, 0xD0,
0xC4, 0x00, 0x70, 0x00, 0x70, 0x00, 0x9C
};
static const unsigned int s6e63m0_19_110[] = {
0x18, 0x08, 0x24, 0x6F, 0x65, 0x46, 0xC2,
0xC4, 0xB3, 0xBF, 0xC2, 0xB2, 0xCF, 0xD1,
0xC6, 0x00, 0x64, 0x00, 0x64, 0x00, 0x8D
};
static const unsigned int s6e63m0_19_90[] = {
0x18, 0x08, 0x24, 0x74, 0x60, 0x4A, 0xC3,
0xC6, 0xB5, 0xBF, 0xC3, 0xB2, 0xD2, 0xD3,
0xC8, 0x00, 0x5B, 0x00, 0x5B, 0x00, 0x81
};
static const unsigned int s6e63m0_19_30[] = {
0x18, 0x08, 0x24, 0x84, 0x45, 0x4F, 0xCA,
0xCB, 0xBC, 0xC9, 0xCB, 0xBC, 0xDA, 0xDA,
0xD0, 0x00, 0x35, 0x00, 0x34, 0x00, 0x4E
};
/* gamma value: 1.7 */
static const unsigned int s6e63m0_17_300[] = {
0x18, 0x08, 0x24, 0x70, 0x70, 0x4F, 0xBF,
0xC2, 0xB2, 0xB8, 0xBC, 0xAC, 0xCB, 0xCD,
0xC3, 0x00, 0xA0, 0x00, 0xA4, 0x00, 0xDB
};
static const unsigned int s6e63m0_17_280[] = {
0x18, 0x08, 0x24, 0x71, 0x71, 0x50, 0xBF,
0xC2, 0xB2, 0xBA, 0xBE, 0xAE, 0xCB, 0xCD,
0xC3, 0x00, 0x9C, 0x00, 0x9F, 0x00, 0xD6
};
static const unsigned int s6e63m0_17_260[] = {
0x18, 0x08, 0x24, 0x72, 0x72, 0x50, 0xC0,
0xC3, 0xB4, 0xB9, 0xBE, 0xAE, 0xCC, 0xCF,
0xC4, 0x00, 0x97, 0x00, 0x9A, 0x00, 0xD1
};
static const unsigned int s6e63m0_17_240[] = {
0x18, 0x08, 0x24, 0x71, 0x72, 0x4F, 0xC2,
0xC4, 0xB5, 0xBB, 0xBF, 0xB0, 0xCC, 0xCF,
0xC3, 0x00, 0x91, 0x00, 0x95, 0x00, 0xCA
};
static const unsigned int s6e63m0_17_220[] = {
0x18, 0x08, 0x24, 0x71, 0x73, 0x4F, 0xC2,
0xC5, 0xB5, 0xBD, 0xC0, 0xB2, 0xCD, 0xD1,
0xC5, 0x00, 0x8B, 0x00, 0x8E, 0x00, 0xC2
};
static const unsigned int s6e63m0_17_200[] = {
0x18, 0x08, 0x24, 0x72, 0x75, 0x51, 0xC2,
0xC6, 0xB5, 0xBF, 0xC1, 0xB3, 0xCE, 0xD1,
0xC6, 0x00, 0x85, 0x00, 0x88, 0x00, 0xBA
};
static const unsigned int s6e63m0_17_170[] = {
0x18, 0x08, 0x24, 0x75, 0x77, 0x54, 0xC3,
0xC7, 0xB7, 0xC0, 0xC3, 0xB4, 0xD1, 0xD3,
0xC9, 0x00, 0x7B, 0x00, 0x7E, 0x00, 0xAB
};
static const unsigned int s6e63m0_17_140[] = {
0x18, 0x08, 0x24, 0x7B, 0x77, 0x58, 0xC3,
0xC8, 0xB8, 0xC2, 0xC6, 0xB6, 0xD3, 0xD4,
0xCA, 0x00, 0x71, 0x00, 0x73, 0x00, 0x9E
};
static const unsigned int s6e63m0_17_110[] = {
0x18, 0x08, 0x24, 0x81, 0x7B, 0x5D, 0xC6,
0xCA, 0xBB, 0xC3, 0xC7, 0xB8, 0xD6, 0xD8,
0xCD, 0x00, 0x65, 0x00, 0x67, 0x00, 0x8D
};
static const unsigned int s6e63m0_17_90[] = {
0x18, 0x08, 0x24, 0x82, 0x7A, 0x5B, 0xC8,
0xCB, 0xBD, 0xC5, 0xCA, 0xBA, 0xD6, 0xD8,
0xCE, 0x00, 0x5D, 0x00, 0x5E, 0x00, 0x82
};
static const unsigned int s6e63m0_17_30[] = {
0x18, 0x08, 0x24, 0x8F, 0x73, 0x63, 0xD1,
0xD0, 0xC5, 0xCC, 0xD1, 0xC2, 0xDE, 0xE0,
0xD6, 0x00, 0x39, 0x00, 0x36, 0x00, 0x51
};
struct s6e63m0_gamma {
unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
unsigned int *gamma_19_table[MAX_GAMMA_LEVEL];
unsigned int *gamma_17_table[MAX_GAMMA_LEVEL];
};
static struct s6e63m0_gamma gamma_table = {
.gamma_22_table[0] = (unsigned int *)&s6e63m0_22_30,
.gamma_22_table[1] = (unsigned int *)&s6e63m0_22_90,
.gamma_22_table[2] = (unsigned int *)&s6e63m0_22_110,
.gamma_22_table[3] = (unsigned int *)&s6e63m0_22_140,
.gamma_22_table[4] = (unsigned int *)&s6e63m0_22_170,
.gamma_22_table[5] = (unsigned int *)&s6e63m0_22_200,
.gamma_22_table[6] = (unsigned int *)&s6e63m0_22_220,
.gamma_22_table[7] = (unsigned int *)&s6e63m0_22_240,
.gamma_22_table[8] = (unsigned int *)&s6e63m0_22_260,
.gamma_22_table[9] = (unsigned int *)&s6e63m0_22_280,
.gamma_22_table[10] = (unsigned int *)&s6e63m0_22_300,
.gamma_19_table[0] = (unsigned int *)&s6e63m0_19_30,
.gamma_19_table[1] = (unsigned int *)&s6e63m0_19_90,
.gamma_19_table[2] = (unsigned int *)&s6e63m0_19_110,
.gamma_19_table[3] = (unsigned int *)&s6e63m0_19_140,
.gamma_19_table[4] = (unsigned int *)&s6e63m0_19_170,
.gamma_19_table[5] = (unsigned int *)&s6e63m0_19_200,
.gamma_19_table[6] = (unsigned int *)&s6e63m0_19_220,
.gamma_19_table[7] = (unsigned int *)&s6e63m0_19_240,
.gamma_19_table[8] = (unsigned int *)&s6e63m0_19_260,
.gamma_19_table[9] = (unsigned int *)&s6e63m0_19_280,
.gamma_19_table[10] = (unsigned int *)&s6e63m0_19_300,
.gamma_17_table[0] = (unsigned int *)&s6e63m0_17_30,
.gamma_17_table[1] = (unsigned int *)&s6e63m0_17_90,
.gamma_17_table[2] = (unsigned int *)&s6e63m0_17_110,
.gamma_17_table[3] = (unsigned int *)&s6e63m0_17_140,
.gamma_17_table[4] = (unsigned int *)&s6e63m0_17_170,
.gamma_17_table[5] = (unsigned int *)&s6e63m0_17_200,
.gamma_17_table[6] = (unsigned int *)&s6e63m0_17_220,
.gamma_17_table[7] = (unsigned int *)&s6e63m0_17_240,
.gamma_17_table[8] = (unsigned int *)&s6e63m0_17_260,
.gamma_17_table[9] = (unsigned int *)&s6e63m0_17_280,
.gamma_17_table[10] = (unsigned int *)&s6e63m0_17_300,
};
#endif

View file

@ -0,0 +1,453 @@
/*
* tdo24m - SPI-based drivers for Toppoly TDO24M series LCD panels
*
* Copyright (C) 2008 Marvell International Ltd.
* 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
* publishhed by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/spi/spi.h>
#include <linux/spi/tdo24m.h>
#include <linux/fb.h>
#include <linux/lcd.h>
#include <linux/slab.h>
#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
#define TDO24M_SPI_BUFF_SIZE (4)
#define MODE_QVGA 0
#define MODE_VGA 1
struct tdo24m {
struct spi_device *spi_dev;
struct lcd_device *lcd_dev;
struct spi_message msg;
struct spi_transfer xfer;
uint8_t *buf;
int (*adj_mode)(struct tdo24m *lcd, int mode);
int color_invert;
int power;
int mode;
};
/* use bit 30, 31 as the indicator of command parameter number */
#define CMD0(x) ((0 << 30) | (x))
#define CMD1(x, x1) ((1 << 30) | ((x) << 9) | 0x100 | (x1))
#define CMD2(x, x1, x2) ((2 << 30) | ((x) << 18) | 0x20000 |\
((x1) << 9) | 0x100 | (x2))
#define CMD_NULL (-1)
static const uint32_t lcd_panel_reset[] = {
CMD0(0x1), /* reset */
CMD0(0x0), /* nop */
CMD0(0x0), /* nop */
CMD0(0x0), /* nop */
CMD_NULL,
};
static const uint32_t lcd_panel_on[] = {
CMD0(0x29), /* Display ON */
CMD2(0xB8, 0xFF, 0xF9), /* Output Control */
CMD0(0x11), /* Sleep out */
CMD1(0xB0, 0x16), /* Wake */
CMD_NULL,
};
static const uint32_t lcd_panel_off[] = {
CMD0(0x28), /* Display OFF */
CMD2(0xB8, 0x80, 0x02), /* Output Control */
CMD0(0x10), /* Sleep in */
CMD1(0xB0, 0x00), /* Deep stand by in */
CMD_NULL,
};
static const uint32_t lcd_vga_pass_through_tdo24m[] = {
CMD1(0xB0, 0x16),
CMD1(0xBC, 0x80),
CMD1(0xE1, 0x00),
CMD1(0x36, 0x50),
CMD1(0x3B, 0x00),
CMD_NULL,
};
static const uint32_t lcd_qvga_pass_through_tdo24m[] = {
CMD1(0xB0, 0x16),
CMD1(0xBC, 0x81),
CMD1(0xE1, 0x00),
CMD1(0x36, 0x50),
CMD1(0x3B, 0x22),
CMD_NULL,
};
static const uint32_t lcd_vga_transfer_tdo24m[] = {
CMD1(0xcf, 0x02), /* Blanking period control (1) */
CMD2(0xd0, 0x08, 0x04), /* Blanking period control (2) */
CMD1(0xd1, 0x01), /* CKV timing control on/off */
CMD2(0xd2, 0x14, 0x00), /* CKV 1,2 timing control */
CMD2(0xd3, 0x1a, 0x0f), /* OEV timing control */
CMD2(0xd4, 0x1f, 0xaf), /* ASW timing control (1) */
CMD1(0xd5, 0x14), /* ASW timing control (2) */
CMD0(0x21), /* Invert for normally black display */
CMD0(0x29), /* Display on */
CMD_NULL,
};
static const uint32_t lcd_qvga_transfer[] = {
CMD1(0xd6, 0x02), /* Blanking period control (1) */
CMD2(0xd7, 0x08, 0x04), /* Blanking period control (2) */
CMD1(0xd8, 0x01), /* CKV timing control on/off */
CMD2(0xd9, 0x00, 0x08), /* CKV 1,2 timing control */
CMD2(0xde, 0x05, 0x0a), /* OEV timing control */
CMD2(0xdf, 0x0a, 0x19), /* ASW timing control (1) */
CMD1(0xe0, 0x0a), /* ASW timing control (2) */
CMD0(0x21), /* Invert for normally black display */
CMD0(0x29), /* Display on */
CMD_NULL,
};
static const uint32_t lcd_vga_pass_through_tdo35s[] = {
CMD1(0xB0, 0x16),
CMD1(0xBC, 0x80),
CMD1(0xE1, 0x00),
CMD1(0x3B, 0x00),
CMD_NULL,
};
static const uint32_t lcd_qvga_pass_through_tdo35s[] = {
CMD1(0xB0, 0x16),
CMD1(0xBC, 0x81),
CMD1(0xE1, 0x00),
CMD1(0x3B, 0x22),
CMD_NULL,
};
static const uint32_t lcd_vga_transfer_tdo35s[] = {
CMD1(0xcf, 0x02), /* Blanking period control (1) */
CMD2(0xd0, 0x08, 0x04), /* Blanking period control (2) */
CMD1(0xd1, 0x01), /* CKV timing control on/off */
CMD2(0xd2, 0x00, 0x1e), /* CKV 1,2 timing control */
CMD2(0xd3, 0x14, 0x28), /* OEV timing control */
CMD2(0xd4, 0x28, 0x64), /* ASW timing control (1) */
CMD1(0xd5, 0x28), /* ASW timing control (2) */
CMD0(0x21), /* Invert for normally black display */
CMD0(0x29), /* Display on */
CMD_NULL,
};
static const uint32_t lcd_panel_config[] = {
CMD2(0xb8, 0xff, 0xf9), /* Output control */
CMD0(0x11), /* sleep out */
CMD1(0xba, 0x01), /* Display mode (1) */
CMD1(0xbb, 0x00), /* Display mode (2) */
CMD1(0x3a, 0x60), /* Display mode 18-bit RGB */
CMD1(0xbf, 0x10), /* Drive system change control */
CMD1(0xb1, 0x56), /* Booster operation setup */
CMD1(0xb2, 0x33), /* Booster mode setup */
CMD1(0xb3, 0x11), /* Booster frequency setup */
CMD1(0xb4, 0x02), /* Op amp/system clock */
CMD1(0xb5, 0x35), /* VCS voltage */
CMD1(0xb6, 0x40), /* VCOM voltage */
CMD1(0xb7, 0x03), /* External display signal */
CMD1(0xbd, 0x00), /* ASW slew rate */
CMD1(0xbe, 0x00), /* Dummy data for QuadData operation */
CMD1(0xc0, 0x11), /* Sleep out FR count (A) */
CMD1(0xc1, 0x11), /* Sleep out FR count (B) */
CMD1(0xc2, 0x11), /* Sleep out FR count (C) */
CMD2(0xc3, 0x20, 0x40), /* Sleep out FR count (D) */
CMD2(0xc4, 0x60, 0xc0), /* Sleep out FR count (E) */
CMD2(0xc5, 0x10, 0x20), /* Sleep out FR count (F) */
CMD1(0xc6, 0xc0), /* Sleep out FR count (G) */
CMD2(0xc7, 0x33, 0x43), /* Gamma 1 fine tuning (1) */
CMD1(0xc8, 0x44), /* Gamma 1 fine tuning (2) */
CMD1(0xc9, 0x33), /* Gamma 1 inclination adjustment */
CMD1(0xca, 0x00), /* Gamma 1 blue offset adjustment */
CMD2(0xec, 0x01, 0xf0), /* Horizontal clock cycles */
CMD_NULL,
};
static int tdo24m_writes(struct tdo24m *lcd, const uint32_t *array)
{
struct spi_transfer *x = &lcd->xfer;
const uint32_t *p = array;
uint32_t data;
int nparams, err = 0;
for (; *p != CMD_NULL; p++) {
if (!lcd->color_invert && *p == CMD0(0x21))
continue;
nparams = (*p >> 30) & 0x3;
data = *p << (7 - nparams);
switch (nparams) {
case 0:
lcd->buf[0] = (data >> 8) & 0xff;
lcd->buf[1] = data & 0xff;
break;
case 1:
lcd->buf[0] = (data >> 16) & 0xff;
lcd->buf[1] = (data >> 8) & 0xff;
lcd->buf[2] = data & 0xff;
break;
case 2:
lcd->buf[0] = (data >> 24) & 0xff;
lcd->buf[1] = (data >> 16) & 0xff;
lcd->buf[2] = (data >> 8) & 0xff;
lcd->buf[3] = data & 0xff;
break;
default:
continue;
}
x->len = nparams + 2;
err = spi_sync(lcd->spi_dev, &lcd->msg);
if (err)
break;
}
return err;
}
static int tdo24m_adj_mode(struct tdo24m *lcd, int mode)
{
switch (mode) {
case MODE_VGA:
tdo24m_writes(lcd, lcd_vga_pass_through_tdo24m);
tdo24m_writes(lcd, lcd_panel_config);
tdo24m_writes(lcd, lcd_vga_transfer_tdo24m);
break;
case MODE_QVGA:
tdo24m_writes(lcd, lcd_qvga_pass_through_tdo24m);
tdo24m_writes(lcd, lcd_panel_config);
tdo24m_writes(lcd, lcd_qvga_transfer);
break;
default:
return -EINVAL;
}
lcd->mode = mode;
return 0;
}
static int tdo35s_adj_mode(struct tdo24m *lcd, int mode)
{
switch (mode) {
case MODE_VGA:
tdo24m_writes(lcd, lcd_vga_pass_through_tdo35s);
tdo24m_writes(lcd, lcd_panel_config);
tdo24m_writes(lcd, lcd_vga_transfer_tdo35s);
break;
case MODE_QVGA:
tdo24m_writes(lcd, lcd_qvga_pass_through_tdo35s);
tdo24m_writes(lcd, lcd_panel_config);
tdo24m_writes(lcd, lcd_qvga_transfer);
break;
default:
return -EINVAL;
}
lcd->mode = mode;
return 0;
}
static int tdo24m_power_on(struct tdo24m *lcd)
{
int err;
err = tdo24m_writes(lcd, lcd_panel_on);
if (err)
goto out;
err = tdo24m_writes(lcd, lcd_panel_reset);
if (err)
goto out;
err = lcd->adj_mode(lcd, lcd->mode);
out:
return err;
}
static int tdo24m_power_off(struct tdo24m *lcd)
{
return tdo24m_writes(lcd, lcd_panel_off);
}
static int tdo24m_power(struct tdo24m *lcd, int power)
{
int ret = 0;
if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power))
ret = tdo24m_power_on(lcd);
else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power))
ret = tdo24m_power_off(lcd);
if (!ret)
lcd->power = power;
return ret;
}
static int tdo24m_set_power(struct lcd_device *ld, int power)
{
struct tdo24m *lcd = lcd_get_data(ld);
return tdo24m_power(lcd, power);
}
static int tdo24m_get_power(struct lcd_device *ld)
{
struct tdo24m *lcd = lcd_get_data(ld);
return lcd->power;
}
static int tdo24m_set_mode(struct lcd_device *ld, struct fb_videomode *m)
{
struct tdo24m *lcd = lcd_get_data(ld);
int mode = MODE_QVGA;
if (m->xres == 640 || m->xres == 480)
mode = MODE_VGA;
if (lcd->mode == mode)
return 0;
return lcd->adj_mode(lcd, mode);
}
static struct lcd_ops tdo24m_ops = {
.get_power = tdo24m_get_power,
.set_power = tdo24m_set_power,
.set_mode = tdo24m_set_mode,
};
static int tdo24m_probe(struct spi_device *spi)
{
struct tdo24m *lcd;
struct spi_message *m;
struct spi_transfer *x;
struct tdo24m_platform_data *pdata;
enum tdo24m_model model;
int err;
pdata = dev_get_platdata(&spi->dev);
if (pdata)
model = pdata->model;
else
model = TDO24M;
spi->bits_per_word = 8;
spi->mode = SPI_MODE_3;
err = spi_setup(spi);
if (err)
return err;
lcd = devm_kzalloc(&spi->dev, sizeof(struct tdo24m), GFP_KERNEL);
if (!lcd)
return -ENOMEM;
lcd->spi_dev = spi;
lcd->power = FB_BLANK_POWERDOWN;
lcd->mode = MODE_VGA; /* default to VGA */
lcd->buf = devm_kzalloc(&spi->dev, TDO24M_SPI_BUFF_SIZE, GFP_KERNEL);
if (lcd->buf == NULL)
return -ENOMEM;
m = &lcd->msg;
x = &lcd->xfer;
spi_message_init(m);
x->cs_change = 1;
x->tx_buf = &lcd->buf[0];
spi_message_add_tail(x, m);
switch (model) {
case TDO24M:
lcd->color_invert = 1;
lcd->adj_mode = tdo24m_adj_mode;
break;
case TDO35S:
lcd->adj_mode = tdo35s_adj_mode;
lcd->color_invert = 0;
break;
default:
dev_err(&spi->dev, "Unsupported model");
return -EINVAL;
}
lcd->lcd_dev = devm_lcd_device_register(&spi->dev, "tdo24m", &spi->dev,
lcd, &tdo24m_ops);
if (IS_ERR(lcd->lcd_dev))
return PTR_ERR(lcd->lcd_dev);
spi_set_drvdata(spi, lcd);
err = tdo24m_power(lcd, FB_BLANK_UNBLANK);
if (err)
return err;
return 0;
}
static int tdo24m_remove(struct spi_device *spi)
{
struct tdo24m *lcd = spi_get_drvdata(spi);
tdo24m_power(lcd, FB_BLANK_POWERDOWN);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int tdo24m_suspend(struct device *dev)
{
struct tdo24m *lcd = dev_get_drvdata(dev);
return tdo24m_power(lcd, FB_BLANK_POWERDOWN);
}
static int tdo24m_resume(struct device *dev)
{
struct tdo24m *lcd = dev_get_drvdata(dev);
return tdo24m_power(lcd, FB_BLANK_UNBLANK);
}
#endif
static SIMPLE_DEV_PM_OPS(tdo24m_pm_ops, tdo24m_suspend, tdo24m_resume);
/* Power down all displays on reboot, poweroff or halt */
static void tdo24m_shutdown(struct spi_device *spi)
{
struct tdo24m *lcd = spi_get_drvdata(spi);
tdo24m_power(lcd, FB_BLANK_POWERDOWN);
}
static struct spi_driver tdo24m_driver = {
.driver = {
.name = "tdo24m",
.owner = THIS_MODULE,
.pm = &tdo24m_pm_ops,
},
.probe = tdo24m_probe,
.remove = tdo24m_remove,
.shutdown = tdo24m_shutdown,
};
module_spi_driver(tdo24m_driver);
MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
MODULE_DESCRIPTION("Driver for Toppoly TDO24M LCD Panel");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:tdo24m");

View file

@ -0,0 +1,178 @@
/*
* LCD / Backlight control code for Sharp SL-6000x (tosa)
*
* Copyright (c) 2005 Dirk Opfer
* Copyright (c) 2007,2008 Dmitry Baryshkov
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/spi/spi.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/slab.h>
#include <asm/mach/sharpsl_param.h>
#include <mach/tosa.h>
#define COMADJ_DEFAULT 97
#define DAC_CH1 0
#define DAC_CH2 1
struct tosa_bl_data {
struct i2c_client *i2c;
struct backlight_device *bl;
int comadj;
};
static void tosa_bl_set_backlight(struct tosa_bl_data *data, int brightness)
{
struct spi_device *spi = dev_get_platdata(&data->i2c->dev);
i2c_smbus_write_byte_data(data->i2c, DAC_CH1, data->comadj);
/* SetBacklightDuty */
i2c_smbus_write_byte_data(data->i2c, DAC_CH2, (u8)(brightness & 0xff));
/* SetBacklightVR */
gpio_set_value(TOSA_GPIO_BL_C20MA, brightness & 0x100);
tosa_bl_enable(spi, brightness);
}
static int tosa_bl_update_status(struct backlight_device *dev)
{
struct backlight_properties *props = &dev->props;
struct tosa_bl_data *data = bl_get_data(dev);
int power = max(props->power, props->fb_blank);
int brightness = props->brightness;
if (power)
brightness = 0;
tosa_bl_set_backlight(data, brightness);
return 0;
}
static int tosa_bl_get_brightness(struct backlight_device *dev)
{
struct backlight_properties *props = &dev->props;
return props->brightness;
}
static const struct backlight_ops bl_ops = {
.get_brightness = tosa_bl_get_brightness,
.update_status = tosa_bl_update_status,
};
static int tosa_bl_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct backlight_properties props;
struct tosa_bl_data *data;
int ret = 0;
data = devm_kzalloc(&client->dev, sizeof(struct tosa_bl_data),
GFP_KERNEL);
if (!data)
return -ENOMEM;
data->comadj = sharpsl_param.comadj == -1 ? COMADJ_DEFAULT : sharpsl_param.comadj;
ret = devm_gpio_request_one(&client->dev, TOSA_GPIO_BL_C20MA,
GPIOF_OUT_INIT_LOW, "backlight");
if (ret) {
dev_dbg(&data->bl->dev, "Unable to request gpio!\n");
return ret;
}
i2c_set_clientdata(client, data);
data->i2c = client;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = 512 - 1;
data->bl = devm_backlight_device_register(&client->dev, "tosa-bl",
&client->dev, data, &bl_ops,
&props);
if (IS_ERR(data->bl)) {
ret = PTR_ERR(data->bl);
goto err_reg;
}
data->bl->props.brightness = 69;
data->bl->props.power = FB_BLANK_UNBLANK;
backlight_update_status(data->bl);
return 0;
err_reg:
data->bl = NULL;
return ret;
}
static int tosa_bl_remove(struct i2c_client *client)
{
struct tosa_bl_data *data = i2c_get_clientdata(client);
data->bl = NULL;
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int tosa_bl_suspend(struct device *dev)
{
struct tosa_bl_data *data = dev_get_drvdata(dev);
tosa_bl_set_backlight(data, 0);
return 0;
}
static int tosa_bl_resume(struct device *dev)
{
struct tosa_bl_data *data = dev_get_drvdata(dev);
backlight_update_status(data->bl);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(tosa_bl_pm_ops, tosa_bl_suspend, tosa_bl_resume);
static const struct i2c_device_id tosa_bl_id[] = {
{ "tosa-bl", 0 },
{ },
};
static struct i2c_driver tosa_bl_driver = {
.driver = {
.name = "tosa-bl",
.owner = THIS_MODULE,
.pm = &tosa_bl_pm_ops,
},
.probe = tosa_bl_probe,
.remove = tosa_bl_remove,
.id_table = tosa_bl_id,
};
module_i2c_driver(tosa_bl_driver);
MODULE_AUTHOR("Dmitry Baryshkov");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("LCD/Backlight control for Sharp SL-6000 PDA");

View file

@ -0,0 +1,278 @@
/*
* LCD / Backlight control code for Sharp SL-6000x (tosa)
*
* Copyright (c) 2005 Dirk Opfer
* Copyright (c) 2007,2008 Dmitry Baryshkov
*
* 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/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/spi/spi.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/lcd.h>
#include <linux/fb.h>
#include <asm/mach/sharpsl_param.h>
#include <mach/tosa.h>
#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
#define TG_REG0_VQV 0x0001
#define TG_REG0_COLOR 0x0002
#define TG_REG0_UD 0x0004
#define TG_REG0_LR 0x0008
#define DAC_BASE 0x4e
struct tosa_lcd_data {
struct spi_device *spi;
struct lcd_device *lcd;
struct i2c_client *i2c;
int lcd_power;
bool is_vga;
};
static int tosa_tg_send(struct spi_device *spi, int adrs, uint8_t data)
{
u8 buf[1];
struct spi_message msg;
struct spi_transfer xfer = {
.len = 1,
.cs_change = 1,
.tx_buf = buf,
};
buf[0] = ((adrs & 0x07) << 5) | (data & 0x1f);
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
return spi_sync(spi, &msg);
}
int tosa_bl_enable(struct spi_device *spi, int enable)
{
/* bl_enable GP04=1 otherwise GP04=0*/
return tosa_tg_send(spi, TG_GPODR2, enable ? 0x01 : 0x00);
}
EXPORT_SYMBOL(tosa_bl_enable);
static void tosa_lcd_tg_init(struct tosa_lcd_data *data)
{
/* TG on */
gpio_set_value(TOSA_GPIO_TG_ON, 0);
mdelay(60);
/* delayed 0clk TCTL signal for VGA */
tosa_tg_send(data->spi, TG_TPOSCTL, 0x00);
/* GPOS0=powercontrol, GPOS1=GPIO, GPOS2=TCTL */
tosa_tg_send(data->spi, TG_GPOSR, 0x02);
}
static void tosa_lcd_tg_on(struct tosa_lcd_data *data)
{
struct spi_device *spi = data->spi;
int value = TG_REG0_COLOR | TG_REG0_UD | TG_REG0_LR;
if (data->is_vga)
value |= TG_REG0_VQV;
tosa_tg_send(spi, TG_PNLCTL, value);
/* TG LCD pannel power up */
tosa_tg_send(spi, TG_PINICTL, 0x4);
mdelay(50);
/* TG LCD GVSS */
tosa_tg_send(spi, TG_PINICTL, 0x0);
if (!data->i2c) {
/*
* after the pannel is powered up the first time,
* we can access the i2c bus so probe for the DAC
*/
struct i2c_adapter *adap = i2c_get_adapter(0);
struct i2c_board_info info = {
.type = "tosa-bl",
.addr = DAC_BASE,
.platform_data = data->spi,
};
data->i2c = i2c_new_device(adap, &info);
}
}
static void tosa_lcd_tg_off(struct tosa_lcd_data *data)
{
struct spi_device *spi = data->spi;
/* TG LCD VHSA off */
tosa_tg_send(spi, TG_PINICTL, 0x4);
mdelay(50);
/* TG LCD signal off */
tosa_tg_send(spi, TG_PINICTL, 0x6);
mdelay(50);
/* TG Off */
gpio_set_value(TOSA_GPIO_TG_ON, 1);
mdelay(100);
}
int tosa_lcd_set_power(struct lcd_device *lcd, int power)
{
struct tosa_lcd_data *data = lcd_get_data(lcd);
if (POWER_IS_ON(power) && !POWER_IS_ON(data->lcd_power))
tosa_lcd_tg_on(data);
if (!POWER_IS_ON(power) && POWER_IS_ON(data->lcd_power))
tosa_lcd_tg_off(data);
data->lcd_power = power;
return 0;
}
static int tosa_lcd_get_power(struct lcd_device *lcd)
{
struct tosa_lcd_data *data = lcd_get_data(lcd);
return data->lcd_power;
}
static int tosa_lcd_set_mode(struct lcd_device *lcd, struct fb_videomode *mode)
{
struct tosa_lcd_data *data = lcd_get_data(lcd);
if (mode->xres == 320 || mode->yres == 320)
data->is_vga = false;
else
data->is_vga = true;
if (POWER_IS_ON(data->lcd_power))
tosa_lcd_tg_on(data);
return 0;
}
static struct lcd_ops tosa_lcd_ops = {
.set_power = tosa_lcd_set_power,
.get_power = tosa_lcd_get_power,
.set_mode = tosa_lcd_set_mode,
};
static int tosa_lcd_probe(struct spi_device *spi)
{
int ret;
struct tosa_lcd_data *data;
data = devm_kzalloc(&spi->dev, sizeof(struct tosa_lcd_data),
GFP_KERNEL);
if (!data)
return -ENOMEM;
data->is_vga = true; /* default to VGA mode */
/*
* bits_per_word cannot be configured in platform data
*/
spi->bits_per_word = 8;
ret = spi_setup(spi);
if (ret < 0)
return ret;
data->spi = spi;
spi_set_drvdata(spi, data);
ret = devm_gpio_request_one(&spi->dev, TOSA_GPIO_TG_ON,
GPIOF_OUT_INIT_LOW, "tg #pwr");
if (ret < 0)
return ret;
mdelay(60);
tosa_lcd_tg_init(data);
tosa_lcd_tg_on(data);
data->lcd = devm_lcd_device_register(&spi->dev, "tosa-lcd", &spi->dev,
data, &tosa_lcd_ops);
if (IS_ERR(data->lcd)) {
ret = PTR_ERR(data->lcd);
data->lcd = NULL;
goto err_register;
}
return 0;
err_register:
tosa_lcd_tg_off(data);
return ret;
}
static int tosa_lcd_remove(struct spi_device *spi)
{
struct tosa_lcd_data *data = spi_get_drvdata(spi);
if (data->i2c)
i2c_unregister_device(data->i2c);
tosa_lcd_tg_off(data);
return 0;
}
#ifdef CONFIG_PM_SLEEP
static int tosa_lcd_suspend(struct device *dev)
{
struct tosa_lcd_data *data = dev_get_drvdata(dev);
tosa_lcd_tg_off(data);
return 0;
}
static int tosa_lcd_resume(struct device *dev)
{
struct tosa_lcd_data *data = dev_get_drvdata(dev);
tosa_lcd_tg_init(data);
if (POWER_IS_ON(data->lcd_power))
tosa_lcd_tg_on(data);
else
tosa_lcd_tg_off(data);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(tosa_lcd_pm_ops, tosa_lcd_suspend, tosa_lcd_resume);
static struct spi_driver tosa_lcd_driver = {
.driver = {
.name = "tosa-lcd",
.owner = THIS_MODULE,
.pm = &tosa_lcd_pm_ops,
},
.probe = tosa_lcd_probe,
.remove = tosa_lcd_remove,
};
module_spi_driver(tosa_lcd_driver);
MODULE_AUTHOR("Dmitry Baryshkov");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("LCD/Backlight control for Sharp SL-6000 PDA");
MODULE_ALIAS("spi:tosa-lcd");

View file

@ -0,0 +1,334 @@
/*
* tps65217_bl.c
*
* TPS65217 backlight driver
*
* Copyright (C) 2012 Matthias Kaehlcke
* Author: Matthias Kaehlcke <matthias@kaehlcke.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/backlight.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/mfd/tps65217.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
struct tps65217_bl {
struct tps65217 *tps;
struct device *dev;
struct backlight_device *bl;
bool is_enabled;
};
static int tps65217_bl_enable(struct tps65217_bl *tps65217_bl)
{
int rc;
rc = tps65217_set_bits(tps65217_bl->tps, TPS65217_REG_WLEDCTRL1,
TPS65217_WLEDCTRL1_ISINK_ENABLE,
TPS65217_WLEDCTRL1_ISINK_ENABLE, TPS65217_PROTECT_NONE);
if (rc) {
dev_err(tps65217_bl->dev,
"failed to enable backlight: %d\n", rc);
return rc;
}
tps65217_bl->is_enabled = true;
dev_dbg(tps65217_bl->dev, "backlight enabled\n");
return 0;
}
static int tps65217_bl_disable(struct tps65217_bl *tps65217_bl)
{
int rc;
rc = tps65217_clear_bits(tps65217_bl->tps,
TPS65217_REG_WLEDCTRL1,
TPS65217_WLEDCTRL1_ISINK_ENABLE,
TPS65217_PROTECT_NONE);
if (rc) {
dev_err(tps65217_bl->dev,
"failed to disable backlight: %d\n", rc);
return rc;
}
tps65217_bl->is_enabled = false;
dev_dbg(tps65217_bl->dev, "backlight disabled\n");
return 0;
}
static int tps65217_bl_update_status(struct backlight_device *bl)
{
struct tps65217_bl *tps65217_bl = bl_get_data(bl);
int rc;
int brightness = bl->props.brightness;
if (bl->props.state & BL_CORE_SUSPENDED)
brightness = 0;
if ((bl->props.power != FB_BLANK_UNBLANK) ||
(bl->props.fb_blank != FB_BLANK_UNBLANK))
/* framebuffer in low power mode or blanking active */
brightness = 0;
if (brightness > 0) {
rc = tps65217_reg_write(tps65217_bl->tps,
TPS65217_REG_WLEDCTRL2,
brightness - 1,
TPS65217_PROTECT_NONE);
if (rc) {
dev_err(tps65217_bl->dev,
"failed to set brightness level: %d\n", rc);
return rc;
}
dev_dbg(tps65217_bl->dev, "brightness set to %d\n", brightness);
if (!tps65217_bl->is_enabled)
rc = tps65217_bl_enable(tps65217_bl);
} else {
rc = tps65217_bl_disable(tps65217_bl);
}
return rc;
}
static const struct backlight_ops tps65217_bl_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = tps65217_bl_update_status,
};
static int tps65217_bl_hw_init(struct tps65217_bl *tps65217_bl,
struct tps65217_bl_pdata *pdata)
{
int rc;
rc = tps65217_bl_disable(tps65217_bl);
if (rc)
return rc;
switch (pdata->isel) {
case TPS65217_BL_ISET1:
/* select ISET_1 current level */
rc = tps65217_clear_bits(tps65217_bl->tps,
TPS65217_REG_WLEDCTRL1,
TPS65217_WLEDCTRL1_ISEL,
TPS65217_PROTECT_NONE);
if (rc) {
dev_err(tps65217_bl->dev,
"failed to select ISET1 current level: %d)\n",
rc);
return rc;
}
dev_dbg(tps65217_bl->dev, "selected ISET1 current level\n");
break;
case TPS65217_BL_ISET2:
/* select ISET2 current level */
rc = tps65217_set_bits(tps65217_bl->tps, TPS65217_REG_WLEDCTRL1,
TPS65217_WLEDCTRL1_ISEL,
TPS65217_WLEDCTRL1_ISEL, TPS65217_PROTECT_NONE);
if (rc) {
dev_err(tps65217_bl->dev,
"failed to select ISET2 current level: %d\n",
rc);
return rc;
}
dev_dbg(tps65217_bl->dev, "selected ISET2 current level\n");
break;
default:
dev_err(tps65217_bl->dev,
"invalid value for current level: %d\n", pdata->isel);
return -EINVAL;
}
/* set PWM frequency */
rc = tps65217_set_bits(tps65217_bl->tps,
TPS65217_REG_WLEDCTRL1,
TPS65217_WLEDCTRL1_FDIM_MASK,
pdata->fdim,
TPS65217_PROTECT_NONE);
if (rc) {
dev_err(tps65217_bl->dev,
"failed to select PWM dimming frequency: %d\n",
rc);
return rc;
}
return 0;
}
#ifdef CONFIG_OF
static struct tps65217_bl_pdata *
tps65217_bl_parse_dt(struct platform_device *pdev)
{
struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
struct device_node *node = of_node_get(tps->dev->of_node);
struct tps65217_bl_pdata *pdata, *err;
u32 val;
node = of_find_node_by_name(node, "backlight");
if (!node)
return ERR_PTR(-ENODEV);
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
err = ERR_PTR(-ENOMEM);
goto err;
}
pdata->isel = TPS65217_BL_ISET1;
if (!of_property_read_u32(node, "isel", &val)) {
if (val < TPS65217_BL_ISET1 ||
val > TPS65217_BL_ISET2) {
dev_err(&pdev->dev,
"invalid 'isel' value in the device tree\n");
err = ERR_PTR(-EINVAL);
goto err;
}
pdata->isel = val;
}
pdata->fdim = TPS65217_BL_FDIM_200HZ;
if (!of_property_read_u32(node, "fdim", &val)) {
switch (val) {
case 100:
pdata->fdim = TPS65217_BL_FDIM_100HZ;
break;
case 200:
pdata->fdim = TPS65217_BL_FDIM_200HZ;
break;
case 500:
pdata->fdim = TPS65217_BL_FDIM_500HZ;
break;
case 1000:
pdata->fdim = TPS65217_BL_FDIM_1000HZ;
break;
default:
dev_err(&pdev->dev,
"invalid 'fdim' value in the device tree\n");
err = ERR_PTR(-EINVAL);
goto err;
}
}
if (!of_property_read_u32(node, "default-brightness", &val)) {
if (val < 0 ||
val > 100) {
dev_err(&pdev->dev,
"invalid 'default-brightness' value in the device tree\n");
err = ERR_PTR(-EINVAL);
goto err;
}
pdata->dft_brightness = val;
}
of_node_put(node);
return pdata;
err:
of_node_put(node);
return err;
}
#else
static struct tps65217_bl_pdata *
tps65217_bl_parse_dt(struct platform_device *pdev)
{
return NULL;
}
#endif
static int tps65217_bl_probe(struct platform_device *pdev)
{
int rc;
struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
struct tps65217_bl *tps65217_bl;
struct tps65217_bl_pdata *pdata;
struct backlight_properties bl_props;
if (tps->dev->of_node) {
pdata = tps65217_bl_parse_dt(pdev);
if (IS_ERR(pdata))
return PTR_ERR(pdata);
} else {
pdata = dev_get_platdata(&pdev->dev);
if (!pdata) {
dev_err(&pdev->dev, "no platform data provided\n");
return -EINVAL;
}
}
tps65217_bl = devm_kzalloc(&pdev->dev, sizeof(*tps65217_bl),
GFP_KERNEL);
if (tps65217_bl == NULL)
return -ENOMEM;
tps65217_bl->tps = tps;
tps65217_bl->dev = &pdev->dev;
tps65217_bl->is_enabled = false;
rc = tps65217_bl_hw_init(tps65217_bl, pdata);
if (rc)
return rc;
memset(&bl_props, 0, sizeof(struct backlight_properties));
bl_props.type = BACKLIGHT_RAW;
bl_props.max_brightness = 100;
tps65217_bl->bl = devm_backlight_device_register(&pdev->dev, pdev->name,
tps65217_bl->dev, tps65217_bl,
&tps65217_bl_ops, &bl_props);
if (IS_ERR(tps65217_bl->bl)) {
dev_err(tps65217_bl->dev,
"registration of backlight device failed: %d\n", rc);
return PTR_ERR(tps65217_bl->bl);
}
tps65217_bl->bl->props.brightness = pdata->dft_brightness;
backlight_update_status(tps65217_bl->bl);
platform_set_drvdata(pdev, tps65217_bl);
return 0;
}
static struct platform_driver tps65217_bl_driver = {
.probe = tps65217_bl_probe,
.driver = {
.name = "tps65217-bl",
},
};
module_platform_driver(tps65217_bl_driver);
MODULE_DESCRIPTION("TPS65217 Backlight driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Matthias Kaehlcke <matthias@kaehlcke.net>");

View file

@ -0,0 +1,267 @@
/* drivers/video/backlight/vgg2432a4.c
*
* VGG2432A4 (ILI9320) LCD controller driver.
*
* Copyright 2007 Simtec Electronics
* http://armlinux.simtec.co.uk/
* Ben Dooks <ben@simtec.co.uk>
*
* 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/delay.h>
#include <linux/err.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/lcd.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <video/ili9320.h>
#include "ili9320.h"
/* Device initialisation sequences */
static const struct ili9320_reg vgg_init1[] = {
{
.address = ILI9320_POWER1,
.value = ILI9320_POWER1_AP(0) | ILI9320_POWER1_BT(0),
}, {
.address = ILI9320_POWER2,
.value = (ILI9320_POWER2_VC(7) |
ILI9320_POWER2_DC0(0) | ILI9320_POWER2_DC1(0)),
}, {
.address = ILI9320_POWER3,
.value = ILI9320_POWER3_VRH(0),
}, {
.address = ILI9320_POWER4,
.value = ILI9320_POWER4_VREOUT(0),
},
};
static const struct ili9320_reg vgg_init2[] = {
{
.address = ILI9320_POWER1,
.value = (ILI9320_POWER1_AP(3) | ILI9320_POWER1_APE |
ILI9320_POWER1_BT(7) | ILI9320_POWER1_SAP),
}, {
.address = ILI9320_POWER2,
.value = ILI9320_POWER2_VC(7) | ILI9320_POWER2_DC0(3),
}
};
static const struct ili9320_reg vgg_gamma[] = {
{
.address = ILI9320_GAMMA1,
.value = 0x0000,
}, {
.address = ILI9320_GAMMA2,
.value = 0x0505,
}, {
.address = ILI9320_GAMMA3,
.value = 0x0004,
}, {
.address = ILI9320_GAMMA4,
.value = 0x0006,
}, {
.address = ILI9320_GAMMA5,
.value = 0x0707,
}, {
.address = ILI9320_GAMMA6,
.value = 0x0105,
}, {
.address = ILI9320_GAMMA7,
.value = 0x0002,
}, {
.address = ILI9320_GAMMA8,
.value = 0x0707,
}, {
.address = ILI9320_GAMMA9,
.value = 0x0704,
}, {
.address = ILI9320_GAMMA10,
.value = 0x807,
}
};
static const struct ili9320_reg vgg_init0[] = {
[0] = {
/* set direction and scan mode gate */
.address = ILI9320_DRIVER,
.value = ILI9320_DRIVER_SS,
}, {
.address = ILI9320_DRIVEWAVE,
.value = (ILI9320_DRIVEWAVE_MUSTSET |
ILI9320_DRIVEWAVE_EOR | ILI9320_DRIVEWAVE_BC),
}, {
.address = ILI9320_ENTRYMODE,
.value = ILI9320_ENTRYMODE_ID(3) | ILI9320_ENTRYMODE_BGR,
}, {
.address = ILI9320_RESIZING,
.value = 0x0,
},
};
static int vgg2432a4_lcd_init(struct ili9320 *lcd,
struct ili9320_platdata *cfg)
{
unsigned int addr;
int ret;
/* Set VCore before anything else (VGG243237-6UFLWA) */
ret = ili9320_write(lcd, 0x00e5, 0x8000);
if (ret)
goto err_initial;
/* Start the oscillator up before we can do anything else. */
ret = ili9320_write(lcd, ILI9320_OSCILATION, ILI9320_OSCILATION_OSC);
if (ret)
goto err_initial;
/* must wait at-lesat 10ms after starting */
mdelay(15);
ret = ili9320_write_regs(lcd, vgg_init0, ARRAY_SIZE(vgg_init0));
if (ret != 0)
goto err_initial;
ili9320_write(lcd, ILI9320_DISPLAY2, cfg->display2);
ili9320_write(lcd, ILI9320_DISPLAY3, cfg->display3);
ili9320_write(lcd, ILI9320_DISPLAY4, cfg->display4);
ili9320_write(lcd, ILI9320_RGB_IF1, cfg->rgb_if1);
ili9320_write(lcd, ILI9320_FRAMEMAKER, 0x0);
ili9320_write(lcd, ILI9320_RGB_IF2, cfg->rgb_if2);
ret = ili9320_write_regs(lcd, vgg_init1, ARRAY_SIZE(vgg_init1));
if (ret != 0)
goto err_vgg;
mdelay(300);
ret = ili9320_write_regs(lcd, vgg_init2, ARRAY_SIZE(vgg_init2));
if (ret != 0)
goto err_vgg2;
mdelay(100);
ili9320_write(lcd, ILI9320_POWER3, 0x13c);
mdelay(100);
ili9320_write(lcd, ILI9320_POWER4, 0x1c00);
ili9320_write(lcd, ILI9320_POWER7, 0x000e);
mdelay(100);
ili9320_write(lcd, ILI9320_GRAM_HORIZ_ADDR, 0x00);
ili9320_write(lcd, ILI9320_GRAM_VERT_ADD, 0x00);
ret = ili9320_write_regs(lcd, vgg_gamma, ARRAY_SIZE(vgg_gamma));
if (ret != 0)
goto err_vgg3;
ili9320_write(lcd, ILI9320_HORIZ_START, 0x0);
ili9320_write(lcd, ILI9320_HORIZ_END, cfg->hsize - 1);
ili9320_write(lcd, ILI9320_VERT_START, 0x0);
ili9320_write(lcd, ILI9320_VERT_END, cfg->vsize - 1);
ili9320_write(lcd, ILI9320_DRIVER2,
ILI9320_DRIVER2_NL(((cfg->vsize - 240) / 8) + 0x1D));
ili9320_write(lcd, ILI9320_BASE_IMAGE, 0x1);
ili9320_write(lcd, ILI9320_VERT_SCROLL, 0x00);
for (addr = ILI9320_PARTIAL1_POSITION; addr <= ILI9320_PARTIAL2_END;
addr++) {
ili9320_write(lcd, addr, 0x0);
}
ili9320_write(lcd, ILI9320_INTERFACE1, 0x10);
ili9320_write(lcd, ILI9320_INTERFACE2, cfg->interface2);
ili9320_write(lcd, ILI9320_INTERFACE3, cfg->interface3);
ili9320_write(lcd, ILI9320_INTERFACE4, cfg->interface4);
ili9320_write(lcd, ILI9320_INTERFACE5, cfg->interface5);
ili9320_write(lcd, ILI9320_INTERFACE6, cfg->interface6);
lcd->display1 = (ILI9320_DISPLAY1_D(3) | ILI9320_DISPLAY1_DTE |
ILI9320_DISPLAY1_GON | ILI9320_DISPLAY1_BASEE |
0x40);
ili9320_write(lcd, ILI9320_DISPLAY1, lcd->display1);
return 0;
err_vgg3:
err_vgg2:
err_vgg:
err_initial:
return ret;
}
#ifdef CONFIG_PM_SLEEP
static int vgg2432a4_suspend(struct device *dev)
{
return ili9320_suspend(dev_get_drvdata(dev));
}
static int vgg2432a4_resume(struct device *dev)
{
return ili9320_resume(dev_get_drvdata(dev));
}
#endif
static struct ili9320_client vgg2432a4_client = {
.name = "VGG2432A4",
.init = vgg2432a4_lcd_init,
};
/* Device probe */
static int vgg2432a4_probe(struct spi_device *spi)
{
int ret;
ret = ili9320_probe_spi(spi, &vgg2432a4_client);
if (ret != 0) {
dev_err(&spi->dev, "failed to initialise ili9320\n");
return ret;
}
return 0;
}
static int vgg2432a4_remove(struct spi_device *spi)
{
return ili9320_remove(spi_get_drvdata(spi));
}
static void vgg2432a4_shutdown(struct spi_device *spi)
{
ili9320_shutdown(spi_get_drvdata(spi));
}
static SIMPLE_DEV_PM_OPS(vgg2432a4_pm_ops, vgg2432a4_suspend, vgg2432a4_resume);
static struct spi_driver vgg2432a4_driver = {
.driver = {
.name = "VGG2432A4",
.owner = THIS_MODULE,
.pm = &vgg2432a4_pm_ops,
},
.probe = vgg2432a4_probe,
.remove = vgg2432a4_remove,
.shutdown = vgg2432a4_shutdown,
};
module_spi_driver(vgg2432a4_driver);
MODULE_AUTHOR("Ben Dooks <ben-linux@fluff.org>");
MODULE_DESCRIPTION("VGG2432A4 LCD Driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("spi:VGG2432A4");

View file

@ -0,0 +1,230 @@
/*
* Backlight driver for Wolfson Microelectronics WM831x PMICs
*
* Copyright 2009 Wolfson Microelectonics plc
*
* 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/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/slab.h>
#include <linux/mfd/wm831x/core.h>
#include <linux/mfd/wm831x/pdata.h>
#include <linux/mfd/wm831x/regulator.h>
struct wm831x_backlight_data {
struct wm831x *wm831x;
int isink_reg;
int current_brightness;
};
static int wm831x_backlight_set(struct backlight_device *bl, int brightness)
{
struct wm831x_backlight_data *data = bl_get_data(bl);
struct wm831x *wm831x = data->wm831x;
int power_up = !data->current_brightness && brightness;
int power_down = data->current_brightness && !brightness;
int ret;
if (power_up) {
/* Enable the ISINK */
ret = wm831x_set_bits(wm831x, data->isink_reg,
WM831X_CS1_ENA, WM831X_CS1_ENA);
if (ret < 0)
goto err;
/* Enable the DC-DC */
ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE,
WM831X_DC4_ENA, WM831X_DC4_ENA);
if (ret < 0)
goto err;
}
if (power_down) {
/* DCDC first */
ret = wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE,
WM831X_DC4_ENA, 0);
if (ret < 0)
goto err;
/* ISINK */
ret = wm831x_set_bits(wm831x, data->isink_reg,
WM831X_CS1_DRIVE | WM831X_CS1_ENA, 0);
if (ret < 0)
goto err;
}
/* Set the new brightness */
ret = wm831x_set_bits(wm831x, data->isink_reg,
WM831X_CS1_ISEL_MASK, brightness);
if (ret < 0)
goto err;
if (power_up) {
/* Drive current through the ISINK */
ret = wm831x_set_bits(wm831x, data->isink_reg,
WM831X_CS1_DRIVE, WM831X_CS1_DRIVE);
if (ret < 0)
return ret;
}
data->current_brightness = brightness;
return 0;
err:
/* If we were in the middle of a power transition always shut down
* for safety.
*/
if (power_up || power_down) {
wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0);
wm831x_set_bits(wm831x, data->isink_reg, WM831X_CS1_ENA, 0);
}
return ret;
}
static int wm831x_backlight_update_status(struct backlight_device *bl)
{
int brightness = bl->props.brightness;
if (bl->props.power != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.state & BL_CORE_SUSPENDED)
brightness = 0;
return wm831x_backlight_set(bl, brightness);
}
static int wm831x_backlight_get_brightness(struct backlight_device *bl)
{
struct wm831x_backlight_data *data = bl_get_data(bl);
return data->current_brightness;
}
static const struct backlight_ops wm831x_backlight_ops = {
.options = BL_CORE_SUSPENDRESUME,
.update_status = wm831x_backlight_update_status,
.get_brightness = wm831x_backlight_get_brightness,
};
static int wm831x_backlight_probe(struct platform_device *pdev)
{
struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
struct wm831x_pdata *wm831x_pdata = dev_get_platdata(pdev->dev.parent);
struct wm831x_backlight_pdata *pdata;
struct wm831x_backlight_data *data;
struct backlight_device *bl;
struct backlight_properties props;
int ret, i, max_isel, isink_reg, dcdc_cfg;
/* We need platform data */
if (wm831x_pdata)
pdata = wm831x_pdata->backlight;
else
pdata = NULL;
if (!pdata) {
dev_err(&pdev->dev, "No platform data supplied\n");
return -EINVAL;
}
/* Figure out the maximum current we can use */
for (i = 0; i < WM831X_ISINK_MAX_ISEL; i++) {
if (wm831x_isinkv_values[i] > pdata->max_uA)
break;
}
if (i == 0) {
dev_err(&pdev->dev, "Invalid max_uA: %duA\n", pdata->max_uA);
return -EINVAL;
}
max_isel = i - 1;
if (pdata->max_uA != wm831x_isinkv_values[max_isel])
dev_warn(&pdev->dev,
"Maximum current is %duA not %duA as requested\n",
wm831x_isinkv_values[max_isel], pdata->max_uA);
switch (pdata->isink) {
case 1:
isink_reg = WM831X_CURRENT_SINK_1;
dcdc_cfg = 0;
break;
case 2:
isink_reg = WM831X_CURRENT_SINK_2;
dcdc_cfg = WM831X_DC4_FBSRC;
break;
default:
dev_err(&pdev->dev, "Invalid ISINK %d\n", pdata->isink);
return -EINVAL;
}
/* Configure the ISINK to use for feedback */
ret = wm831x_reg_unlock(wm831x);
if (ret < 0)
return ret;
ret = wm831x_set_bits(wm831x, WM831X_DC4_CONTROL, WM831X_DC4_FBSRC,
dcdc_cfg);
wm831x_reg_lock(wm831x);
if (ret < 0)
return ret;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
data->wm831x = wm831x;
data->current_brightness = 0;
data->isink_reg = isink_reg;
memset(&props, 0, sizeof(props));
props.type = BACKLIGHT_RAW;
props.max_brightness = max_isel;
bl = devm_backlight_device_register(&pdev->dev, "wm831x", &pdev->dev,
data, &wm831x_backlight_ops, &props);
if (IS_ERR(bl)) {
dev_err(&pdev->dev, "failed to register backlight\n");
return PTR_ERR(bl);
}
bl->props.brightness = max_isel;
platform_set_drvdata(pdev, bl);
/* Disable the DCDC if it was started so we can bootstrap */
wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, WM831X_DC4_ENA, 0);
backlight_update_status(bl);
return 0;
}
static struct platform_driver wm831x_backlight_driver = {
.driver = {
.name = "wm831x-backlight",
},
.probe = wm831x_backlight_probe,
};
module_platform_driver(wm831x_backlight_driver);
MODULE_DESCRIPTION("Backlight Driver for WM831x PMICs");
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:wm831x-backlight");