diff options
Diffstat (limited to 'drivers/video/backlight')
49 files changed, 14725 insertions, 0 deletions
diff --git a/drivers/video/backlight/88pm860x_bl.c b/drivers/video/backlight/88pm860x_bl.c new file mode 100644 index 00000000..f49181c7 --- /dev/null +++ b/drivers/video/backlight/88pm860x_bl.c @@ -0,0 +1,281 @@ +/* + * 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/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; +}; + +static inline int wled_a(int port) +{ + int ret; + + ret = ((port - PM8606_BACKLIGHT1) << 1) + 2; + return ret; +} + +static inline int wled_b(int port) +{ + int ret; + + ret = ((port - PM8606_BACKLIGHT1) << 1) + 3; + return ret; +} + +/* WLED2 & WLED3 share the same IDC */ +static inline int wled_idc(int port) +{ + int ret; + + switch (port) { + case PM8606_BACKLIGHT1: + case PM8606_BACKLIGHT2: + ret = ((port - PM8606_BACKLIGHT1) << 1) + 3; + break; + case PM8606_BACKLIGHT3: + default: + ret = ((port - PM8606_BACKLIGHT2) << 1) + 3; + break; + } + return ret; +} + +static int backlight_power_set(struct pm860x_chip *chip, int port, + int on) +{ + int ret = -EINVAL; + + switch (port) { + case PM8606_BACKLIGHT1: + ret = on ? pm8606_osc_enable(chip, WLED1_DUTY) : + pm8606_osc_disable(chip, WLED1_DUTY); + break; + case PM8606_BACKLIGHT2: + ret = on ? pm8606_osc_enable(chip, WLED2_DUTY) : + pm8606_osc_disable(chip, WLED2_DUTY); + break; + case PM8606_BACKLIGHT3: + 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, wled_a(data->port), value); + if (ret < 0) + goto out; + + if ((data->current_brightness == 0) && brightness) { + if (data->iset) { + ret = pm860x_set_bits(data->i2c, wled_idc(data->port), + 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, wled_b(data->port), + PM8606_WLED_ON, PM8606_WLED_ON); + } + } else { + if (brightness == MAX_BRIGHTNESS) { + /* set WLED_ON bit as 100% */ + ret = pm860x_set_bits(data->i2c, wled_b(data->port), + PM8606_WLED_ON, PM8606_WLED_ON); + } else { + /* clear WLED_ON bit since it's not 100% */ + ret = pm860x_set_bits(data->i2c, wled_b(data->port), + 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, wled_a(data->port)); + 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, +}; + +static int pm860x_backlight_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_backlight_pdata *pdata = NULL; + struct pm860x_backlight_data *data; + struct backlight_device *bl; + struct resource *res; + struct backlight_properties props; + char name[MFD_NAME_SIZE]; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource!\n"); + return -EINVAL; + } + + pdata = pdev->dev.platform_data; + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data isn't assigned to " + "backlight\n"); + return -EINVAL; + } + + data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_backlight_data), + GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + strncpy(name, res->name, MFD_NAME_SIZE); + data->chip = chip; + data->i2c = (chip->id == CHIP_PM8606) ? chip->client \ + : chip->companion; + data->current_brightness = MAX_BRIGHTNESS; + data->pwm = pdata->pwm; + data->iset = pdata->iset; + data->port = pdata->flags; + if (data->port < 0) { + dev_err(&pdev->dev, "wrong platform data is assigned"); + return -EINVAL; + } + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = MAX_BRIGHTNESS; + bl = backlight_device_register(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) + goto out; + + backlight_update_status(bl); + return 0; +out: + backlight_device_unregister(bl); + return ret; +} + +static int pm860x_backlight_remove(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + + backlight_device_unregister(bl); + return 0; +} + +static struct platform_driver pm860x_backlight_driver = { + .driver = { + .name = "88pm860x-backlight", + .owner = THIS_MODULE, + }, + .probe = pm860x_backlight_probe, + .remove = pm860x_backlight_remove, +}; + +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"); diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig new file mode 100644 index 00000000..af168844 --- /dev/null +++ b/drivers/video/backlight/Kconfig @@ -0,0 +1,366 @@ +# +# 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 + 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 && GENERIC_GPIO + 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 && GENERIC_GPIO + 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_ILI9320 + tristate + 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 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. + +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_SAM9261EK || MACH_SAM9G10EK || MACH_SAM9263EK + 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_ATMEL_PWM + tristate "Atmel PWM backlight control" + depends on ATMEL_PWM + help + Say Y here if you want to use the PWM peripheral in Atmel AT91 and + AVR32 devices. This driver will need additional platform data to know + which PWM instance to use and how to configure it. + + To compile this driver as a module, choose M here: the module will be + called atmel-pwm-bl. + +config BACKLIGHT_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_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_PROGEAR + tristate "Frontpath ProGear Backlight Driver" + depends on PCI && X86 + help + If you have a Frontpath ProGear say Y to enable the + backlight 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 HAVE_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_LP855X + tristate "Backlight driver for TI LP855X" + depends on BACKLIGHT_CLASS_DEVICE && I2C + help + This supports TI LP8550, LP8551, LP8552, LP8553 and LP8556 + 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. + +endif # BACKLIGHT_CLASS_DEVICE + +endif # BACKLIGHT_LCD_SUPPORT diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile new file mode 100644 index 00000000..622f0008 --- /dev/null +++ b/drivers/video/backlight/Makefile @@ -0,0 +1,46 @@ +# Backlight & LCD drivers + +obj-$(CONFIG_LCD_CLASS_DEVICE) += lcd.o +obj-$(CONFIG_LCD_CORGI) += corgi_lcd.o +obj-$(CONFIG_LCD_HP700) += jornada720_lcd.o +obj-$(CONFIG_LCD_L4F00242T03) += l4f00242t03.o +obj-$(CONFIG_LCD_LMS283GF05) += lms283gf05.o +obj-$(CONFIG_LCD_LTV350QV) += ltv350qv.o +obj-$(CONFIG_LCD_ILI9320) += ili9320.o +obj-$(CONFIG_LCD_PLATFORM) += platform_lcd.o +obj-$(CONFIG_LCD_VGG2432A4) += vgg2432a4.o +obj-$(CONFIG_LCD_TDO24M) += tdo24m.o +obj-$(CONFIG_LCD_TOSA) += tosa_lcd.o +obj-$(CONFIG_LCD_S6E63M0) += s6e63m0.o +obj-$(CONFIG_LCD_LD9040) += ld9040.o +obj-$(CONFIG_LCD_AMS369FG06) += ams369fg06.o + +obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o +obj-$(CONFIG_BACKLIGHT_ATMEL_PWM) += atmel-pwm-bl.o +obj-$(CONFIG_BACKLIGHT_EP93XX) += ep93xx_bl.o +obj-$(CONFIG_BACKLIGHT_GENERIC) += generic_bl.o +obj-$(CONFIG_BACKLIGHT_HP700) += jornada720_bl.o +obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o +obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o +obj-$(CONFIG_BACKLIGHT_LP855X) += lp855x_bl.o +obj-$(CONFIG_BACKLIGHT_OMAP1) += omap1_bl.o +obj-$(CONFIG_BACKLIGHT_PANDORA) += pandora_bl.o +obj-$(CONFIG_BACKLIGHT_PROGEAR) += progear_bl.o +obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o +obj-$(CONFIG_BACKLIGHT_PWM) += pwm_bl.o +obj-$(CONFIG_BACKLIGHT_DA903X) += da903x_bl.o +obj-$(CONFIG_BACKLIGHT_DA9052) += da9052_bl.o +obj-$(CONFIG_BACKLIGHT_MAX8925) += max8925_bl.o +obj-$(CONFIG_BACKLIGHT_APPLE) += apple_bl.o +obj-$(CONFIG_BACKLIGHT_TOSA) += tosa_bl.o +obj-$(CONFIG_BACKLIGHT_SAHARA) += kb3886_bl.o +obj-$(CONFIG_BACKLIGHT_WM831X) += wm831x_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_88PM860X) += 88pm860x_bl.o +obj-$(CONFIG_BACKLIGHT_PCF50633) += pcf50633-backlight.o +obj-$(CONFIG_BACKLIGHT_AAT2870) += aat2870_bl.o +obj-$(CONFIG_BACKLIGHT_OT200) += ot200_bl.o +#obj-$(CONFIG_BACKLIGHT_WMT) += wmt_bl.o +obj-y += wmt_bl.o diff --git a/drivers/video/backlight/aat2870_bl.c b/drivers/video/backlight/aat2870_bl.c new file mode 100644 index 00000000..7ff75228 --- /dev/null +++ b/drivers/video/backlight/aat2870_bl.c @@ -0,0 +1,245 @@ +/* + * 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_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static int aat2870_bl_update_status(struct backlight_device *bd) +{ + struct aat2870_bl_driver_data *aat2870_bl = dev_get_drvdata(&bd->dev); + 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, + .get_brightness = aat2870_bl_get_brightness, + .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 = pdev->dev.platform_data; + 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) { + dev_err(&pdev->dev, + "Failed to allocate memory for aat2870 backlight\n"); + ret = -ENOMEM; + goto out; + } + + memset(&props, 0, sizeof(struct backlight_properties)); + + props.type = BACKLIGHT_RAW; + bd = backlight_device_register("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"); + goto out_bl_dev_unregister; + } + + return 0; + +out_bl_dev_unregister: + backlight_device_unregister(bd); +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); + + backlight_device_unregister(bd); + + return 0; +} + +static struct platform_driver aat2870_bl_driver = { + .driver = { + .name = "aat2870-backlight", + .owner = THIS_MODULE, + }, + .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>"); diff --git a/drivers/video/backlight/adp5520_bl.c b/drivers/video/backlight/adp5520_bl.c new file mode 100644 index 00000000..4911ea79 --- /dev/null +++ b/drivers/video/backlight/adp5520_bl.c @@ -0,0 +1,388 @@ +/* + * 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, ®_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 error; + uint8_t reg_val; + + mutex_lock(&data->lock); + error = adp5520_read(data->master, reg, ®_val); + mutex_unlock(&data->lock); + + 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 = strict_strtoul(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 = strict_strtoul(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 __devinit 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 = pdev->dev.platform_data; + + 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 = backlight_device_register(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"); + backlight_device_unregister(bl); + } + + platform_set_drvdata(pdev, bl); + ret |= adp5520_bl_setup(bl); + backlight_update_status(bl); + + return ret; +} + +static int __devexit 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); + + backlight_device_unregister(bl); + + return 0; +} + +#ifdef CONFIG_PM +static int adp5520_bl_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + return adp5520_bl_set(bl, 0); +} + +static int adp5520_bl_resume(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + + backlight_update_status(bl); + return 0; +} +#else +#define adp5520_bl_suspend NULL +#define adp5520_bl_resume NULL +#endif + +static struct platform_driver adp5520_bl_driver = { + .driver = { + .name = "adp5520-backlight", + .owner = THIS_MODULE, + }, + .probe = adp5520_bl_probe, + .remove = __devexit_p(adp5520_bl_remove), + .suspend = adp5520_bl_suspend, + .resume = adp5520_bl_resume, +}; + +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"); diff --git a/drivers/video/backlight/adp8860_bl.c b/drivers/video/backlight/adp8860_bl.c new file mode 100644 index 00000000..550dbf0b --- /dev/null +++ b/drivers/video/backlight/adp8860_bl.c @@ -0,0 +1,827 @@ +/* + * 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, ®_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, ®_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 __devinit adp8860_led_probe(struct i2c_client *client) +{ + struct adp8860_backlight_platform_data *pdata = + client->dev.platform_data; + struct adp8860_bl *data = i2c_get_clientdata(client); + struct adp8860_led *led, *led_dat; + struct led_info *cur_led; + int ret, i; + + led = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL); + if (led == NULL) { + dev_err(&client->dev, "failed to alloc memory\n"); + 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"); + goto err_free; + } + + 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); + 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); + 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); + } + + err_free: + kfree(led); + + return ret; +} + +static int __devexit adp8860_led_remove(struct i2c_client *client) +{ + struct adp8860_backlight_platform_data *pdata = + client->dev.platform_data; + 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); + } + + kfree(data->led); + return 0; +} +#else +static int __devinit adp8860_led_probe(struct i2c_client *client) +{ + return 0; +} + +static int __devexit 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, ®_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 = strict_strtoul(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 = strict_strtoul(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, ®_val); + ret_val = reg_val; + error |= adp8860_read(data->client, ADP8860_PH1LEVH, ®_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, ®_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 = strict_strtoul(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, ®_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 __devinit 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 = + client->dev.platform_data; + 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 = kzalloc(sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + ret = adp8860_read(client, ADP8860_MFDVID, ®_val); + if (ret < 0) + goto out2; + + 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"); + ret = -ENODEV; + goto out2; + } + + /* 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 = backlight_device_register(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"); + ret = PTR_ERR(bl); + goto out2; + } + + 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"); + goto out1; + } + + 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); +out1: + backlight_device_unregister(bl); +out2: + kfree(data); + + return ret; +} + +static int __devexit 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); + + backlight_device_unregister(data->bl); + kfree(data); + + return 0; +} + +#ifdef CONFIG_PM +static int adp8860_i2c_suspend(struct i2c_client *client, pm_message_t message) +{ + adp8860_clr_bits(client, ADP8860_MDCR, NSTBY); + + return 0; +} + +static int adp8860_i2c_resume(struct i2c_client *client) +{ + adp8860_set_bits(client, ADP8860_MDCR, NSTBY); + + return 0; +} +#else +#define adp8860_i2c_suspend NULL +#define adp8860_i2c_resume NULL +#endif + +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, + }, + .probe = adp8860_probe, + .remove = __devexit_p(adp8860_remove), + .suspend = adp8860_i2c_suspend, + .resume = adp8860_i2c_resume, + .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"); diff --git a/drivers/video/backlight/adp8870_bl.c b/drivers/video/backlight/adp8870_bl.c new file mode 100644 index 00000000..9be58c6f --- /dev/null +++ b/drivers/video/backlight/adp8870_bl.c @@ -0,0 +1,999 @@ +/* + * 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, ®_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, ®_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 __devinit adp8870_led_probe(struct i2c_client *client) +{ + struct adp8870_backlight_platform_data *pdata = + client->dev.platform_data; + struct adp8870_bl *data = i2c_get_clientdata(client); + struct adp8870_led *led, *led_dat; + struct led_info *cur_led; + int ret, i; + + + led = kcalloc(pdata->num_leds, sizeof(*led), GFP_KERNEL); + if (led == NULL) { + dev_err(&client->dev, "failed to alloc memory\n"); + return -ENOMEM; + } + + ret = adp8870_write(client, ADP8870_ISCLAW, pdata->led_fade_law); + if (ret) + goto err_free; + + ret = adp8870_write(client, ADP8870_ISCT1, + (pdata->led_on_time & 0x3) << 6); + if (ret) + goto err_free; + + ret = adp8870_write(client, ADP8870_ISCF, + FADE_VAL(pdata->led_fade_in, pdata->led_fade_out)); + if (ret) + goto err_free; + + 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); + 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); + 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); + } + + err_free: + kfree(led); + + return ret; +} + +static int __devexit adp8870_led_remove(struct i2c_client *client) +{ + struct adp8870_backlight_platform_data *pdata = + client->dev.platform_data; + 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); + } + + kfree(data->led); + return 0; +} +#else +static int __devinit adp8870_led_probe(struct i2c_client *client) +{ + return 0; +} + +static int __devexit 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, ®_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 = strict_strtoul(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 = strict_strtoul(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, ®_val); + if (error < 0) { + mutex_unlock(&data->lock); + return error; + } + ret_val = reg_val; + error = adp8870_read(data->client, ADP8870_PH1LEVH, ®_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, ®_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 = strict_strtoul(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, ®_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 __devinit 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 = + client->dev.platform_data; + 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, ®_val); + if (ret < 0) + return -EIO; + + if (ADP8870_MANID(reg_val) != ADP8870_MANUFID) { + dev_err(&client->dev, "failed to probe\n"); + return -ENODEV; + } + + data = kzalloc(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 = backlight_device_register(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"); + ret = PTR_ERR(bl); + goto out2; + } + + 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"); + goto out1; + } + + 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); +out1: + backlight_device_unregister(bl); +out2: + kfree(data); + + return ret; +} + +static int __devexit 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); + + backlight_device_unregister(data->bl); + kfree(data); + + return 0; +} + +#ifdef CONFIG_PM +static int adp8870_i2c_suspend(struct i2c_client *client, pm_message_t message) +{ + adp8870_clr_bits(client, ADP8870_MDCR, NSTBY); + + return 0; +} + +static int adp8870_i2c_resume(struct i2c_client *client) +{ + adp8870_set_bits(client, ADP8870_MDCR, NSTBY); + + return 0; +} +#else +#define adp8870_i2c_suspend NULL +#define adp8870_i2c_resume NULL +#endif + +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, + }, + .probe = adp8870_probe, + .remove = __devexit_p(adp8870_remove), + .suspend = adp8870_i2c_suspend, + .resume = adp8870_i2c_resume, + .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"); diff --git a/drivers/video/backlight/ams369fg06.c b/drivers/video/backlight/ams369fg06.c new file mode 100644 index 00000000..7bdadc79 --- /dev/null +++ b/drivers/video/backlight/ams369fg06.c @@ -0,0 +1,636 @@ +/* + * 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. + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/wait.h> +#include <linux/module.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/spi/spi.h> +#include <linux/lcd.h> +#include <linux/backlight.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 + mdelay(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 = NULL; + struct backlight_device *bd = NULL; + + pd = lcd->lcd_pd; + if (!pd) { + dev_err(lcd->dev, "platform data is NULL.\n"); + return -EFAULT; + } + + bd = lcd->bd; + if (!bd) { + dev_err(lcd->dev, "backlight device is NULL.\n"); + return -EFAULT; + } + + if (!pd->power_on) { + dev_err(lcd->dev, "power_on is NULL.\n"); + return -EFAULT; + } else { + pd->power_on(lcd->ld, 1); + mdelay(pd->power_on_delay); + } + + if (!pd->reset) { + dev_err(lcd->dev, "reset is NULL.\n"); + return -EFAULT; + } else { + pd->reset(lcd->ld); + mdelay(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 = 0; + struct lcd_platform_data *pd = NULL; + + pd = lcd->lcd_pd; + if (!pd) { + dev_err(lcd->dev, "platform data is NULL\n"); + return -EFAULT; + } + + ret = ams369fg06_ldi_disable(lcd); + if (ret) { + dev_err(lcd->dev, "lcd setting failed.\n"); + return -EIO; + } + + mdelay(pd->power_off_delay); + + if (!pd->power_on) { + dev_err(lcd->dev, "power_on is NULL.\n"); + return -EFAULT; + } else + 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_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +static int ams369fg06_set_brightness(struct backlight_device *bd) +{ + int ret = 0; + int brightness = bd->props.brightness; + struct ams369fg06 *lcd = dev_get_drvdata(&bd->dev); + + 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 = { + .get_brightness = ams369fg06_get_brightness, + .update_status = ams369fg06_set_brightness, +}; + +static int __devinit 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 = kzalloc(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"); + goto out_free_lcd; + } + + lcd->spi = spi; + lcd->dev = &spi->dev; + + lcd->lcd_pd = spi->dev.platform_data; + if (!lcd->lcd_pd) { + dev_err(&spi->dev, "platform data is NULL\n"); + goto out_free_lcd; + } + + ld = lcd_device_register("ams369fg06", &spi->dev, lcd, + &ams369fg06_lcd_ops); + if (IS_ERR(ld)) { + ret = PTR_ERR(ld); + goto out_free_lcd; + } + + lcd->ld = ld; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = MAX_BRIGHTNESS; + + bd = backlight_device_register("ams369fg06-bl", &spi->dev, lcd, + &ams369fg06_backlight_ops, &props); + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + goto out_lcd_unregister; + } + + 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; + + dev_set_drvdata(&spi->dev, lcd); + + dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n"); + + return 0; + +out_lcd_unregister: + lcd_device_unregister(ld); +out_free_lcd: + kfree(lcd); + return ret; +} + +static int __devexit ams369fg06_remove(struct spi_device *spi) +{ + struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); + + ams369fg06_power(lcd, FB_BLANK_POWERDOWN); + backlight_device_unregister(lcd->bd); + lcd_device_unregister(lcd->ld); + kfree(lcd); + + return 0; +} + +#if defined(CONFIG_PM) +static unsigned int before_power; + +static int ams369fg06_suspend(struct spi_device *spi, pm_message_t mesg) +{ + int ret = 0; + struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); + + dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power); + + before_power = lcd->power; + + /* + * when lcd panel is suspend, lcd panel becomes off + * regardless of status. + */ + ret = ams369fg06_power(lcd, FB_BLANK_POWERDOWN); + + return ret; +} + +static int ams369fg06_resume(struct spi_device *spi) +{ + int ret = 0; + struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); + + /* + * after suspended, if lcd panel status is FB_BLANK_UNBLANK + * (at that time, before_power is FB_BLANK_UNBLANK) then + * it changes that status to FB_BLANK_POWERDOWN to get lcd on. + */ + if (before_power == FB_BLANK_UNBLANK) + lcd->power = FB_BLANK_POWERDOWN; + + dev_dbg(&spi->dev, "before_power = %d\n", before_power); + + ret = ams369fg06_power(lcd, before_power); + + return ret; +} +#else +#define ams369fg06_suspend NULL +#define ams369fg06_resume NULL +#endif + +static void ams369fg06_shutdown(struct spi_device *spi) +{ + struct ams369fg06 *lcd = dev_get_drvdata(&spi->dev); + + ams369fg06_power(lcd, FB_BLANK_POWERDOWN); +} + +static struct spi_driver ams369fg06_driver = { + .driver = { + .name = "ams369fg06", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ams369fg06_probe, + .remove = __devexit_p(ams369fg06_remove), + .shutdown = ams369fg06_shutdown, + .suspend = ams369fg06_suspend, + .resume = ams369fg06_resume, +}; + +module_spi_driver(ams369fg06_driver); + +MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>"); +MODULE_DESCRIPTION("ams369fg06 LCD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/apple_bl.c b/drivers/video/backlight/apple_bl.c new file mode 100644 index 00000000..a523b255 --- /dev/null +++ b/drivers/video/backlight/apple_bl.c @@ -0,0 +1,260 @@ +/* + * 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. + */ + +#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> + +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; + +#define DRIVER "apple_backlight: " + +/* 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) + printk(KERN_DEBUG DRIVER "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) + printk(KERN_DEBUG DRIVER "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) + printk(KERN_DEBUG DRIVER "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) + printk(KERN_DEBUG DRIVER "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 __devinit 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) { + printk(KERN_ERR DRIVER "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) { + printk(KERN_ERR DRIVER "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 __devexit apple_bl_remove(struct acpi_device *dev, int type) +{ + 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"); diff --git a/drivers/video/backlight/atmel-pwm-bl.c b/drivers/video/backlight/atmel-pwm-bl.c new file mode 100644 index 00000000..0443a4f7 --- /dev/null +++ b/drivers/video/backlight/atmel-pwm-bl.c @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2008 Atmel Corporation + * + * Backlight driver using Atmel PWM peripheral. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/fb.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/backlight.h> +#include <linux/atmel_pwm.h> +#include <linux/atmel-pwm-bl.h> +#include <linux/slab.h> + +struct atmel_pwm_bl { + const struct atmel_pwm_bl_platform_data *pdata; + struct backlight_device *bldev; + struct platform_device *pdev; + struct pwm_channel pwmc; + int gpio_on; +}; + +static int atmel_pwm_bl_set_intensity(struct backlight_device *bd) +{ + struct atmel_pwm_bl *pwmbl = bl_get_data(bd); + int intensity = bd->props.brightness; + int pwm_duty; + + if (bd->props.power != FB_BLANK_UNBLANK) + intensity = 0; + if (bd->props.fb_blank != FB_BLANK_UNBLANK) + intensity = 0; + + if (pwmbl->pdata->pwm_active_low) + pwm_duty = pwmbl->pdata->pwm_duty_min + intensity; + else + pwm_duty = pwmbl->pdata->pwm_duty_max - intensity; + + if (pwm_duty > pwmbl->pdata->pwm_duty_max) + pwm_duty = pwmbl->pdata->pwm_duty_max; + if (pwm_duty < pwmbl->pdata->pwm_duty_min) + pwm_duty = pwmbl->pdata->pwm_duty_min; + + if (!intensity) { + if (pwmbl->gpio_on != -1) { + gpio_set_value(pwmbl->gpio_on, + 0 ^ pwmbl->pdata->on_active_low); + } + pwm_channel_writel(&pwmbl->pwmc, PWM_CUPD, pwm_duty); + pwm_channel_disable(&pwmbl->pwmc); + } else { + pwm_channel_enable(&pwmbl->pwmc); + pwm_channel_writel(&pwmbl->pwmc, PWM_CUPD, pwm_duty); + if (pwmbl->gpio_on != -1) { + gpio_set_value(pwmbl->gpio_on, + 1 ^ pwmbl->pdata->on_active_low); + } + } + + return 0; +} + +static int atmel_pwm_bl_get_intensity(struct backlight_device *bd) +{ + struct atmel_pwm_bl *pwmbl = bl_get_data(bd); + u8 intensity; + + if (pwmbl->pdata->pwm_active_low) { + intensity = pwm_channel_readl(&pwmbl->pwmc, PWM_CDTY) - + pwmbl->pdata->pwm_duty_min; + } else { + intensity = pwmbl->pdata->pwm_duty_max - + pwm_channel_readl(&pwmbl->pwmc, PWM_CDTY); + } + + return intensity; +} + +static int atmel_pwm_bl_init_pwm(struct atmel_pwm_bl *pwmbl) +{ + unsigned long pwm_rate = pwmbl->pwmc.mck; + unsigned long prescale = DIV_ROUND_UP(pwm_rate, + (pwmbl->pdata->pwm_frequency * + pwmbl->pdata->pwm_compare_max)) - 1; + + /* + * Prescale must be power of two and maximum 0xf in size because of + * hardware limit. PWM speed will be: + * PWM module clock speed / (2 ^ prescale). + */ + prescale = fls(prescale); + if (prescale > 0xf) + prescale = 0xf; + + pwm_channel_writel(&pwmbl->pwmc, PWM_CMR, prescale); + pwm_channel_writel(&pwmbl->pwmc, PWM_CDTY, + pwmbl->pdata->pwm_duty_min + + pwmbl->bldev->props.brightness); + pwm_channel_writel(&pwmbl->pwmc, PWM_CPRD, + pwmbl->pdata->pwm_compare_max); + + dev_info(&pwmbl->pdev->dev, "Atmel PWM backlight driver " + "(%lu Hz)\n", pwmbl->pwmc.mck / + pwmbl->pdata->pwm_compare_max / + (1 << prescale)); + + return pwm_channel_enable(&pwmbl->pwmc); +} + +static const struct backlight_ops atmel_pwm_bl_ops = { + .get_brightness = atmel_pwm_bl_get_intensity, + .update_status = atmel_pwm_bl_set_intensity, +}; + +static int atmel_pwm_bl_probe(struct platform_device *pdev) +{ + struct backlight_properties props; + const struct atmel_pwm_bl_platform_data *pdata; + struct backlight_device *bldev; + struct atmel_pwm_bl *pwmbl; + int retval; + + pwmbl = kzalloc(sizeof(struct atmel_pwm_bl), GFP_KERNEL); + if (!pwmbl) + return -ENOMEM; + + pwmbl->pdev = pdev; + + pdata = pdev->dev.platform_data; + if (!pdata) { + retval = -ENODEV; + goto err_free_mem; + } + + if (pdata->pwm_compare_max < pdata->pwm_duty_max || + pdata->pwm_duty_min > pdata->pwm_duty_max || + pdata->pwm_frequency == 0) { + retval = -EINVAL; + goto err_free_mem; + } + + pwmbl->pdata = pdata; + pwmbl->gpio_on = pdata->gpio_on; + + retval = pwm_channel_alloc(pdata->pwm_channel, &pwmbl->pwmc); + if (retval) + goto err_free_mem; + + if (pwmbl->gpio_on != -1) { + retval = gpio_request(pwmbl->gpio_on, "gpio_atmel_pwm_bl"); + if (retval) { + pwmbl->gpio_on = -1; + goto err_free_pwm; + } + + /* Turn display off by default. */ + retval = gpio_direction_output(pwmbl->gpio_on, + 0 ^ pdata->on_active_low); + if (retval) + goto err_free_gpio; + } + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = pdata->pwm_duty_max - pdata->pwm_duty_min; + bldev = backlight_device_register("atmel-pwm-bl", &pdev->dev, pwmbl, + &atmel_pwm_bl_ops, &props); + if (IS_ERR(bldev)) { + retval = PTR_ERR(bldev); + goto err_free_gpio; + } + + pwmbl->bldev = bldev; + + platform_set_drvdata(pdev, pwmbl); + + /* Power up the backlight by default at middle intesity. */ + bldev->props.power = FB_BLANK_UNBLANK; + bldev->props.brightness = bldev->props.max_brightness / 2; + + retval = atmel_pwm_bl_init_pwm(pwmbl); + if (retval) + goto err_free_bl_dev; + + atmel_pwm_bl_set_intensity(bldev); + + return 0; + +err_free_bl_dev: + platform_set_drvdata(pdev, NULL); + backlight_device_unregister(bldev); +err_free_gpio: + if (pwmbl->gpio_on != -1) + gpio_free(pwmbl->gpio_on); +err_free_pwm: + pwm_channel_free(&pwmbl->pwmc); +err_free_mem: + kfree(pwmbl); + return retval; +} + +static int __exit atmel_pwm_bl_remove(struct platform_device *pdev) +{ + struct atmel_pwm_bl *pwmbl = platform_get_drvdata(pdev); + + if (pwmbl->gpio_on != -1) { + gpio_set_value(pwmbl->gpio_on, 0); + gpio_free(pwmbl->gpio_on); + } + pwm_channel_disable(&pwmbl->pwmc); + pwm_channel_free(&pwmbl->pwmc); + backlight_device_unregister(pwmbl->bldev); + platform_set_drvdata(pdev, NULL); + kfree(pwmbl); + + return 0; +} + +static struct platform_driver atmel_pwm_bl_driver = { + .driver = { + .name = "atmel-pwm-bl", + }, + /* REVISIT add suspend() and resume() */ + .remove = __exit_p(atmel_pwm_bl_remove), +}; + +static int __init atmel_pwm_bl_init(void) +{ + return platform_driver_probe(&atmel_pwm_bl_driver, atmel_pwm_bl_probe); +} +module_init(atmel_pwm_bl_init); + +static void __exit atmel_pwm_bl_exit(void) +{ + platform_driver_unregister(&atmel_pwm_bl_driver); +} +module_exit(atmel_pwm_bl_exit); + +MODULE_AUTHOR("Hans-Christian egtvedt <hans-christian.egtvedt@atmel.com>"); +MODULE_DESCRIPTION("Atmel PWM backlight driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c new file mode 100644 index 00000000..bf5b1ece --- /dev/null +++ b/drivers/video/backlight/backlight.c @@ -0,0 +1,401 @@ +/* + * Backlight Lowlevel Control Abstraction + * + * Copyright (C) 2003,2004 Hewlett-Packard Company + * + */ + +#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 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, 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; + + /* 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)) { + bd->props.fb_blank = *(int *)evdata->data; + if (bd->props.fb_blank == FB_BLANK_UNBLANK) + bd->props.state &= ~BL_CORE_FBBLANK; + else + bd->props.state |= BL_CORE_FBBLANK; + 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 backlight_show_power(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 backlight_store_power(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("backlight: 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 ssize_t backlight_show_brightness(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 backlight_store_brightness(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("backlight: 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 ssize_t backlight_show_type(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 ssize_t backlight_show_max_brightness(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 ssize_t backlight_show_actual_brightness(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)); + mutex_unlock(&bd->ops_lock); + + return rc; +} + +static struct class *backlight_class; + +static int backlight_suspend(struct device *dev, pm_message_t state) +{ + 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; +} + +static void bl_device_release(struct device *dev) +{ + struct backlight_device *bd = to_backlight_device(dev); + kfree(bd); +} + +static struct device_attribute bl_device_attributes[] = { + __ATTR(bl_power, 0644, backlight_show_power, backlight_store_power), + __ATTR(brightness, 0644, backlight_show_brightness, + backlight_store_brightness), + __ATTR(actual_brightness, 0444, backlight_show_actual_brightness, + NULL), + __ATTR(max_brightness, 0444, backlight_show_max_brightness, NULL), + __ATTR(type, 0444, backlight_show_type, NULL), + __ATTR_NULL, +}; + +/** + * 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, 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) { + kfree(new_bd); + 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 + + return new_bd; +} +EXPORT_SYMBOL(backlight_device_register); + +/** + * 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; + +#ifdef CONFIG_PMAC_BACKLIGHT + mutex_lock(&pmac_backlight_mutex); + if (pmac_backlight == bd) + pmac_backlight = NULL; + mutex_unlock(&pmac_backlight_mutex); +#endif + 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 __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)) { + printk(KERN_WARNING "Unable to create backlight class; errno = %ld\n", + PTR_ERR(backlight_class)); + return PTR_ERR(backlight_class); + } + + backlight_class->dev_attrs = bl_device_attributes; + backlight_class->suspend = backlight_suspend; + backlight_class->resume = backlight_resume; + 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"); diff --git a/drivers/video/backlight/corgi_lcd.c b/drivers/video/backlight/corgi_lcd.c new file mode 100644 index 00000000..6dab13fe --- /dev/null +++ b/drivers/video/backlight/corgi_lcd.c @@ -0,0 +1,637 @@ +/* + * 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 = dev_get_drvdata(&ld->dev); + 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 = dev_get_drvdata(&ld->dev); + + 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 = dev_get_drvdata(&ld->dev); + + 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 = dev_get_drvdata(&bd->dev); + + 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(lcd->gpio_backlight_cont, cont); + + if (gpio_is_valid(lcd->gpio_backlight_on)) + gpio_set_value(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 = dev_get_drvdata(&bd->dev); + 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 +static int corgi_lcd_suspend(struct spi_device *spi, pm_message_t state) +{ + struct corgi_lcd *lcd = dev_get_drvdata(&spi->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 spi_device *spi) +{ + struct corgi_lcd *lcd = dev_get_drvdata(&spi->dev); + + corgibl_flags &= ~CORGIBL_SUSPENDED; + corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK); + backlight_update_status(lcd->bl_dev); + return 0; +} +#else +#define corgi_lcd_suspend NULL +#define corgi_lcd_resume NULL +#endif + +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 = gpio_request(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 = gpio_request(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); + goto err_free_backlight_on; + } + + 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; + +err_free_backlight_on: + if (gpio_is_valid(lcd->gpio_backlight_on)) + gpio_free(lcd->gpio_backlight_on); + return err; +} + +static int __devinit corgi_lcd_probe(struct spi_device *spi) +{ + struct backlight_properties props; + struct corgi_lcd_platform_data *pdata = spi->dev.platform_data; + struct corgi_lcd *lcd; + int ret = 0; + + if (pdata == NULL) { + dev_err(&spi->dev, "platform data not available\n"); + return -EINVAL; + } + + lcd = kzalloc(sizeof(struct corgi_lcd), GFP_KERNEL); + if (!lcd) { + dev_err(&spi->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + lcd->spi_dev = spi; + + lcd->lcd_dev = lcd_device_register("corgi_lcd", &spi->dev, + lcd, &corgi_lcd_ops); + if (IS_ERR(lcd->lcd_dev)) { + ret = PTR_ERR(lcd->lcd_dev); + goto err_free_lcd; + } + lcd->power = FB_BLANK_POWERDOWN; + lcd->mode = (pdata) ? pdata->init_mode : CORGI_LCD_MODE_VGA; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = pdata->max_intensity; + lcd->bl_dev = backlight_device_register("corgi_bl", &spi->dev, lcd, + &corgi_bl_ops, &props); + if (IS_ERR(lcd->bl_dev)) { + ret = PTR_ERR(lcd->bl_dev); + goto err_unregister_lcd; + } + lcd->bl_dev->props.brightness = pdata->default_intensity; + lcd->bl_dev->props.power = FB_BLANK_UNBLANK; + + ret = setup_gpio_backlight(lcd, pdata); + if (ret) + goto err_unregister_bl; + + lcd->kick_battery = pdata->kick_battery; + + dev_set_drvdata(&spi->dev, lcd); + corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK); + backlight_update_status(lcd->bl_dev); + + lcd->limit_mask = pdata->limit_mask; + the_corgi_lcd = lcd; + return 0; + +err_unregister_bl: + backlight_device_unregister(lcd->bl_dev); +err_unregister_lcd: + lcd_device_unregister(lcd->lcd_dev); +err_free_lcd: + kfree(lcd); + return ret; +} + +static int __devexit corgi_lcd_remove(struct spi_device *spi) +{ + struct corgi_lcd *lcd = dev_get_drvdata(&spi->dev); + + lcd->bl_dev->props.power = FB_BLANK_UNBLANK; + lcd->bl_dev->props.brightness = 0; + backlight_update_status(lcd->bl_dev); + backlight_device_unregister(lcd->bl_dev); + + if (gpio_is_valid(lcd->gpio_backlight_on)) + gpio_free(lcd->gpio_backlight_on); + + if (gpio_is_valid(lcd->gpio_backlight_cont)) + gpio_free(lcd->gpio_backlight_cont); + + corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN); + lcd_device_unregister(lcd->lcd_dev); + kfree(lcd); + + return 0; +} + +static struct spi_driver corgi_lcd_driver = { + .driver = { + .name = "corgi-lcd", + .owner = THIS_MODULE, + }, + .probe = corgi_lcd_probe, + .remove = __devexit_p(corgi_lcd_remove), + .suspend = corgi_lcd_suspend, + .resume = corgi_lcd_resume, +}; + +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"); diff --git a/drivers/video/backlight/cr_bllcd.c b/drivers/video/backlight/cr_bllcd.c new file mode 100644 index 00000000..22489eb5 --- /dev/null +++ b/drivers/video/backlight/cr_bllcd.c @@ -0,0 +1,289 @@ +/* + * 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> + */ + +#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) { + printk("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)) { + printk(KERN_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 = backlight_device_register("cr-backlight", &pdev->dev, NULL, + &cr_backlight_ops, &props); + if (IS_ERR(bdp)) { + pci_dev_put(lpc_dev); + return PTR_ERR(bdp); + } + + ldp = lcd_device_register("cr-lcd", &pdev->dev, NULL, &cr_lcd_ops); + if (IS_ERR(ldp)) { + backlight_device_unregister(bdp); + 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) { + lcd_device_unregister(ldp); + backlight_device_unregister(bdp); + 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); + backlight_device_unregister(crp->cr_backlight_device); + lcd_device_unregister(crp->cr_lcd_device); + 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); + } + + printk("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"); diff --git a/drivers/video/backlight/da903x_bl.c b/drivers/video/backlight/da903x_bl.c new file mode 100644 index 00000000..30e19681 --- /dev/null +++ b/drivers/video/backlight/da903x_bl.c @@ -0,0 +1,204 @@ +/* + * 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; + + 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 = { + .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 = pdev->dev.platform_data; + 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)); + + props.type = BACKLIGHT_RAW; + props.max_brightness = max_brightness; + bl = backlight_device_register(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 int da903x_backlight_remove(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + + backlight_device_unregister(bl); + return 0; +} + +#ifdef CONFIG_PM +static int da903x_backlight_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct backlight_device *bl = platform_get_drvdata(pdev); + return da903x_backlight_set(bl, 0); +} + +static int da903x_backlight_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct backlight_device *bl = platform_get_drvdata(pdev); + + backlight_update_status(bl); + return 0; +} + +static const struct dev_pm_ops da903x_backlight_pm_ops = { + .suspend = da903x_backlight_suspend, + .resume = da903x_backlight_resume, +}; +#endif + +static struct platform_driver da903x_backlight_driver = { + .driver = { + .name = "da903x-backlight", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &da903x_backlight_pm_ops, +#endif + }, + .probe = da903x_backlight_probe, + .remove = da903x_backlight_remove, +}; + +module_platform_driver(da903x_backlight_driver); + +MODULE_DESCRIPTION("Backlight Driver for Dialog Semiconductor DA9030/DA9034"); +MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>" + "Mike Rapoport <mike@compulab.co.il>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da903x-backlight"); diff --git a/drivers/video/backlight/da9052_bl.c b/drivers/video/backlight/da9052_bl.c new file mode 100644 index 00000000..b628d68f --- /dev/null +++ b/drivers/video/backlight/da9052_bl.c @@ -0,0 +1,187 @@ +/* + * 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 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; + + msleep(10); + + 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 = backlight_device_register(pdev->name, wleds->da9052->dev, wleds, + &da9052_backlight_ops, &props); + if (IS_ERR(bl)) { + dev_err(&pdev->dev, "Failed to register backlight\n"); + devm_kfree(&pdev->dev, wleds); + 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); + backlight_device_unregister(bl); + devm_kfree(&pdev->dev, 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", + .owner = THIS_MODULE, + }, +}; + +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"); diff --git a/drivers/video/backlight/ep93xx_bl.c b/drivers/video/backlight/ep93xx_bl.c new file mode 100644 index 00000000..08214e1f --- /dev/null +++ b/drivers/video/backlight/ep93xx_bl.c @@ -0,0 +1,154 @@ +/* + * 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 __init 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 = backlight_device_register(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; +} + +static int ep93xxbl_remove(struct platform_device *dev) +{ + struct backlight_device *bl = platform_get_drvdata(dev); + + backlight_device_unregister(bl); + platform_set_drvdata(dev, NULL); + return 0; +} + +#ifdef CONFIG_PM +static int ep93xxbl_suspend(struct platform_device *dev, pm_message_t state) +{ + struct backlight_device *bl = platform_get_drvdata(dev); + + return ep93xxbl_set(bl, 0); +} + +static int ep93xxbl_resume(struct platform_device *dev) +{ + struct backlight_device *bl = platform_get_drvdata(dev); + + backlight_update_status(bl); + return 0; +} +#else +#define ep93xxbl_suspend NULL +#define ep93xxbl_resume NULL +#endif + +static struct platform_driver ep93xxbl_driver = { + .driver = { + .name = "ep93xx-bl", + .owner = THIS_MODULE, + }, + .probe = ep93xxbl_probe, + .remove = __devexit_p(ep93xxbl_remove), + .suspend = ep93xxbl_suspend, + .resume = ep93xxbl_resume, +}; + +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"); diff --git a/drivers/video/backlight/generic_bl.c b/drivers/video/backlight/generic_bl.c new file mode 100644 index 00000000..9ce6170c --- /dev/null +++ b/drivers/video/backlight/generic_bl.c @@ -0,0 +1,139 @@ +/* + * 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; +} + +/* + * Called when the battery is low to limit the backlight intensity. + * If limit==0 clear any limit, otherwise limit the intensity + */ +void genericbl_limit_intensity(int limit) +{ + struct backlight_device *bd = generic_backlight_device; + + mutex_lock(&bd->ops_lock); + if (limit) + bd->props.state |= GENERICBL_BATTLOW; + else + bd->props.state &= ~GENERICBL_BATTLOW; + backlight_update_status(generic_backlight_device); + mutex_unlock(&bd->ops_lock); +} +EXPORT_SYMBOL(genericbl_limit_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 = pdev->dev.platform_data; + 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 = backlight_device_register(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; + + printk("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); + + backlight_device_unregister(bd); + + printk("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"); diff --git a/drivers/video/backlight/hp680_bl.c b/drivers/video/backlight/hp680_bl.c new file mode 100644 index 00000000..38aa0027 --- /dev/null +++ b/drivers/video/backlight/hp680_bl.c @@ -0,0 +1,179 @@ +/* + * 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 = 0; +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 +static int hp680bl_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + hp680bl_suspended = 1; + hp680bl_send_intensity(bd); + return 0; +} + +static int hp680bl_resume(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + hp680bl_suspended = 0; + hp680bl_send_intensity(bd); + return 0; +} +#else +#define hp680bl_suspend NULL +#define hp680bl_resume NULL +#endif + +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 __devinit 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 = backlight_device_register("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); + + backlight_device_unregister(bd); + + return 0; +} + +static struct platform_driver hp680bl_driver = { + .probe = hp680bl_probe, + .remove = hp680bl_remove, + .suspend = hp680bl_suspend, + .resume = hp680bl_resume, + .driver = { + .name = "hp680-bl", + }, +}; + +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"); diff --git a/drivers/video/backlight/ili9320.c b/drivers/video/backlight/ili9320.c new file mode 100644 index 00000000..5118a9f0 --- /dev/null +++ b/drivers/video/backlight/ili9320.c @@ -0,0 +1,331 @@ +/* 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, + 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 __devinit 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 __devinit ili9320_probe_spi(struct spi_device *spi, + struct ili9320_client *client) +{ + struct ili9320_platdata *cfg = spi->dev.platform_data; + 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 = kzalloc(sizeof(struct ili9320), GFP_KERNEL); + if (ili == NULL) { + dev_err(dev, "no memory for device\n"); + 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; + + dev_set_drvdata(&spi->dev, ili); + + ili9320_setup_spi(ili, spi); + + lcd = lcd_device_register("ili9320", dev, ili, &ili9320_ops); + if (IS_ERR(lcd)) { + dev_err(dev, "failed to register lcd device\n"); + ret = PTR_ERR(lcd); + goto err_free; + } + + 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"); + goto err_unregister; + } + + return 0; + + err_unregister: + lcd_device_unregister(lcd); + + err_free: + kfree(ili); + + return ret; +} + +EXPORT_SYMBOL_GPL(ili9320_probe_spi); + +int __devexit ili9320_remove(struct ili9320 *ili) +{ + ili9320_power(ili, FB_BLANK_POWERDOWN); + + lcd_device_unregister(ili->lcd); + kfree(ili); + + return 0; +} + +EXPORT_SYMBOL_GPL(ili9320_remove); + +#ifdef CONFIG_PM +int ili9320_suspend(struct ili9320 *lcd, pm_message_t state) +{ + int ret; + + dev_dbg(lcd->dev, "%s: event %d\n", __func__, state.event); + + if (state.event == PM_EVENT_SUSPEND) { + 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; + } + + return 0; +} + +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"); diff --git a/drivers/video/backlight/ili9320.h b/drivers/video/backlight/ili9320.h new file mode 100644 index 00000000..e388eca7 --- /dev/null +++ b/drivers/video/backlight/ili9320.h @@ -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, + 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, pm_message_t state); +extern int ili9320_resume(struct ili9320 *lcd); diff --git a/drivers/video/backlight/jornada720_bl.c b/drivers/video/backlight/jornada720_bl.c new file mode 100644 index 00000000..2f8af5d7 --- /dev/null +++ b/drivers/video/backlight/jornada720_bl.c @@ -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) { + printk(KERN_ERR "bl : get brightness timeout\n"); + jornada_ssp_end(); + return -ETIMEDOUT; + } else /* 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) { + printk(KERN_INFO "bl : 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) { + printk(KERN_INFO "bl : 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) { + printk(KERN_ERR "bl : 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 = backlight_device_register(S1D_DEVICENAME, &pdev->dev, NULL, + &jornada_bl_ops, &props); + + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + printk(KERN_ERR "bl : 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); + printk(KERN_INFO "HP Jornada 700 series backlight driver\n"); + + return 0; +} + +static int jornada_bl_remove(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + backlight_device_unregister(bd); + + return 0; +} + +static struct platform_driver jornada_bl_driver = { + .probe = jornada_bl_probe, + .remove = jornada_bl_remove, + .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"); diff --git a/drivers/video/backlight/jornada720_lcd.c b/drivers/video/backlight/jornada720_lcd.c new file mode 100644 index 00000000..22d231a1 --- /dev/null +++ b/drivers/video/backlight/jornada720_lcd.c @@ -0,0 +1,142 @@ +/* + * + * 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 *dev) +{ + /* LDD2 in PPC = LCD POWER */ + if (PPSR & PPC_LDD2) + return FB_BLANK_UNBLANK; /* PW ON */ + else + return FB_BLANK_POWERDOWN; /* PW OFF */ +} + +static int jornada_lcd_get_contrast(struct lcd_device *dev) +{ + int ret; + + if (jornada_lcd_get_power(dev) != FB_BLANK_UNBLANK) + return 0; + + jornada_ssp_start(); + + if (jornada_ssp_byte(GETCONTRAST) != TXDUMMY) { + printk(KERN_ERR "lcd: get contrast failed\n"); + jornada_ssp_end(); + return -ETIMEDOUT; + } else { + ret = jornada_ssp_byte(TXDUMMY); + jornada_ssp_end(); + return ret; + } +} + +static int jornada_lcd_set_contrast(struct lcd_device *dev, int value) +{ + int ret; + + jornada_ssp_start(); + + /* start by sending our set contrast cmd to mcu */ + ret = jornada_ssp_byte(SETCONTRAST); + + /* push the new value */ + if (jornada_ssp_byte(value) != TXDUMMY) { + printk(KERN_ERR "lcd : set contrast failed\n"); + jornada_ssp_end(); + return -ETIMEDOUT; + } + + /* if we get here we can assume everything went well */ + jornada_ssp_end(); + + return 0; +} + +static int jornada_lcd_set_power(struct lcd_device *dev, 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 = lcd_device_register(S1D_DEVICENAME, &pdev->dev, NULL, &jornada_lcd_props); + + if (IS_ERR(lcd_device)) { + ret = PTR_ERR(lcd_device); + printk(KERN_ERR "lcd : 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 int jornada_lcd_remove(struct platform_device *pdev) +{ + struct lcd_device *lcd_device = platform_get_drvdata(pdev); + + lcd_device_unregister(lcd_device); + + return 0; +} + +static struct platform_driver jornada_lcd_driver = { + .probe = jornada_lcd_probe, + .remove = jornada_lcd_remove, + .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"); diff --git a/drivers/video/backlight/kb3886_bl.c b/drivers/video/backlight/kb3886_bl.c new file mode 100644 index 00000000..72dd5556 --- /dev/null +++ b/drivers/video/backlight/kb3886_bl.c @@ -0,0 +1,209 @@ +/* + * 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); + msleep(10); + outb(KB3886_PWM0_WRITE, KB3886_IO); + msleep(10); + 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 __initdata kb3886bl_device_table[] = { + { + .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 +static int kb3886bl_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + kb3886bl_flags |= KB3886BL_SUSPENDED; + backlight_update_status(bd); + return 0; +} + +static int kb3886bl_resume(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + kb3886bl_flags &= ~KB3886BL_SUSPENDED; + backlight_update_status(bd); + return 0; +} +#else +#define kb3886bl_suspend NULL +#define kb3886bl_resume NULL +#endif + +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 = pdev->dev.platform_data; + + 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 = backlight_device_register("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 int kb3886bl_remove(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + + backlight_device_unregister(bd); + + return 0; +} + +static struct platform_driver kb3886bl_driver = { + .probe = kb3886bl_probe, + .remove = kb3886bl_remove, + .suspend = kb3886bl_suspend, + .resume = kb3886bl_resume, + .driver = { + .name = "kb3886-bl", + }, +}; + +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:*"); diff --git a/drivers/video/backlight/l4f00242t03.c b/drivers/video/backlight/l4f00242t03.c new file mode 100644 index 00000000..6022b672 --- /dev/null +++ b/drivers/video/backlight/l4f00242t03.c @@ -0,0 +1,281 @@ +/* + * 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. + */ + +#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 = spi->dev.platform_data; + struct l4f00242t03_priv *priv = dev_get_drvdata(&spi->dev); + const u16 cmd[] = { 0x36, param(0), 0x3A, param(0x60) }; + + dev_dbg(&spi->dev, "initializing LCD\n"); + + regulator_set_voltage(priv->io_reg, 1800000, 1800000); + regulator_enable(priv->io_reg); + + regulator_set_voltage(priv->core_reg, 2800000, 2800000); + regulator_enable(priv->core_reg); + + 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 = spi->dev.platform_data; + struct l4f00242t03_priv *priv = dev_get_drvdata(&spi->dev); + + 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 __devinit l4f00242t03_probe(struct spi_device *spi) +{ + struct l4f00242t03_priv *priv; + struct l4f00242t03_pdata *pdata = spi->dev.platform_data; + int ret; + + if (pdata == NULL) { + dev_err(&spi->dev, "Uninitialized platform data.\n"); + return -EINVAL; + } + + priv = kzalloc(sizeof(struct l4f00242t03_priv), GFP_KERNEL); + + if (priv == NULL) { + dev_err(&spi->dev, "No memory for this device.\n"); + return -ENOMEM; + } + + dev_set_drvdata(&spi->dev, priv); + spi->bits_per_word = 9; + spi_setup(spi); + + priv->spi = spi; + + ret = gpio_request_one(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"); + goto err; + } + + ret = gpio_request_one(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"); + goto err2; + } + + priv->io_reg = regulator_get(&spi->dev, "vdd"); + if (IS_ERR(priv->io_reg)) { + ret = PTR_ERR(priv->io_reg); + dev_err(&spi->dev, "%s: Unable to get the IO regulator\n", + __func__); + goto err3; + } + + priv->core_reg = regulator_get(&spi->dev, "vcore"); + if (IS_ERR(priv->core_reg)) { + ret = PTR_ERR(priv->core_reg); + dev_err(&spi->dev, "%s: Unable to get the core regulator\n", + __func__); + goto err4; + } + + priv->ld = lcd_device_register("l4f00242t03", + &spi->dev, priv, &l4f_ops); + if (IS_ERR(priv->ld)) { + ret = PTR_ERR(priv->ld); + goto err5; + } + + /* 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; + +err5: + regulator_put(priv->core_reg); +err4: + regulator_put(priv->io_reg); +err3: + gpio_free(pdata->data_enable_gpio); +err2: + gpio_free(pdata->reset_gpio); +err: + kfree(priv); + + return ret; +} + +static int __devexit l4f00242t03_remove(struct spi_device *spi) +{ + struct l4f00242t03_priv *priv = dev_get_drvdata(&spi->dev); + struct l4f00242t03_pdata *pdata = priv->spi->dev.platform_data; + + l4f00242t03_lcd_power_set(priv->ld, FB_BLANK_POWERDOWN); + lcd_device_unregister(priv->ld); + + dev_set_drvdata(&spi->dev, NULL); + + gpio_free(pdata->data_enable_gpio); + gpio_free(pdata->reset_gpio); + + regulator_put(priv->io_reg); + regulator_put(priv->core_reg); + + kfree(priv); + + return 0; +} + +static void l4f00242t03_shutdown(struct spi_device *spi) +{ + struct l4f00242t03_priv *priv = dev_get_drvdata(&spi->dev); + + 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 = __devexit_p(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"); diff --git a/drivers/video/backlight/lcd.c b/drivers/video/backlight/lcd.c new file mode 100644 index 00000000..79c1b0d6 --- /dev/null +++ b/drivers/video/backlight/lcd.c @@ -0,0 +1,274 @@ +/* + * LCD Lowlevel Control Abstraction + * + * Copyright (C) 2003,2004 Hewlett-Packard Company + * + */ + +#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: + 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 (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_show_power(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_store_power(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int rc = -ENXIO; + struct lcd_device *ld = to_lcd_device(dev); + unsigned long power; + + rc = kstrtoul(buf, 0, &power); + if (rc) + return rc; + + mutex_lock(&ld->ops_lock); + if (ld->ops && ld->ops->set_power) { + pr_debug("lcd: set power to %lu\n", power); + ld->ops->set_power(ld, power); + rc = count; + } + mutex_unlock(&ld->ops_lock); + + return rc; +} + +static ssize_t lcd_show_contrast(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 lcd_store_contrast(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int rc = -ENXIO; + struct lcd_device *ld = to_lcd_device(dev); + unsigned long contrast; + + rc = kstrtoul(buf, 0, &contrast); + if (rc) + return rc; + + mutex_lock(&ld->ops_lock); + if (ld->ops && ld->ops->set_contrast) { + pr_debug("lcd: set contrast to %lu\n", contrast); + ld->ops->set_contrast(ld, contrast); + rc = count; + } + mutex_unlock(&ld->ops_lock); + + return rc; +} + +static ssize_t lcd_show_max_contrast(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 struct class *lcd_class; + +static void lcd_device_release(struct device *dev) +{ + struct lcd_device *ld = to_lcd_device(dev); + kfree(ld); +} + +static struct device_attribute lcd_device_attributes[] = { + __ATTR(lcd_power, 0644, lcd_show_power, lcd_store_power), + __ATTR(contrast, 0644, lcd_show_contrast, lcd_store_contrast), + __ATTR(max_contrast, 0444, lcd_show_max_contrast, NULL), + __ATTR_NULL, +}; + +/** + * 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, name); + dev_set_drvdata(&new_ld->dev, devdata); + + rc = device_register(&new_ld->dev); + if (rc) { + kfree(new_ld); + 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 __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)) { + printk(KERN_WARNING "Unable to create backlight class; errno = %ld\n", + PTR_ERR(lcd_class)); + return PTR_ERR(lcd_class); + } + + lcd_class->dev_attrs = lcd_device_attributes; + 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"); diff --git a/drivers/video/backlight/ld9040.c b/drivers/video/backlight/ld9040.c new file mode 100644 index 00000000..efd352be --- /dev/null +++ b/drivers/video/backlight/ld9040.c @@ -0,0 +1,863 @@ +/* + * 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. + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/wait.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/spi/spi.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/lcd.h> +#include <linux/backlight.h> +#include <linux/module.h> +#include <linux/regulator/consumer.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 +#define power_is_on(pwr) ((pwr) <= FB_BLANK_NORMAL) + +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; + } + mdelay(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 + udelay(wbuf[i+1]*1000); + 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) +{ + int ret = 0; + + ret = _ld9040_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]); + + return ret; +} + + +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 */ + udelay(300); + if (ret) + break; + } + + return ret; +} + +static int ld9040_ldi_enable(struct ld9040 *lcd) +{ + int ret = 0; + + ret = ld9040_panel_send_sequence(lcd, seq_display_on); + + return ret; +} + +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_on(struct ld9040 *lcd) +{ + int ret = 0; + struct lcd_platform_data *pd = NULL; + pd = lcd->lcd_pd; + if (!pd) { + dev_err(lcd->dev, "platform data is NULL.\n"); + return -EFAULT; + } + + /* lcd power on */ + ld9040_regulator_enable(lcd); + + if (!pd->reset) { + dev_err(lcd->dev, "reset is NULL.\n"); + return -EFAULT; + } else { + pd->reset(lcd->ld); + mdelay(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 = 0; + struct lcd_platform_data *pd = NULL; + + pd = lcd->lcd_pd; + if (!pd) { + dev_err(lcd->dev, "platform data is NULL.\n"); + return -EFAULT; + } + + ret = ld9040_ldi_disable(lcd); + if (ret) { + dev_err(lcd->dev, "lcd setting failed.\n"); + return -EIO; + } + + mdelay(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 (power_is_on(power) && !power_is_on(lcd->power)) + ret = ld9040_power_on(lcd); + else if (!power_is_on(power) && 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_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +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 = { + .get_brightness = ld9040_get_brightness, + .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 = kzalloc(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"); + goto out_free_lcd; + } + + lcd->spi = spi; + lcd->dev = &spi->dev; + + lcd->lcd_pd = spi->dev.platform_data; + if (!lcd->lcd_pd) { + dev_err(&spi->dev, "platform data is NULL.\n"); + goto out_free_lcd; + } + + mutex_init(&lcd->lock); + + ret = regulator_bulk_get(lcd->dev, ARRAY_SIZE(supplies), supplies); + if (ret) { + dev_err(lcd->dev, "Failed to get regulators: %d\n", ret); + goto out_free_lcd; + } + + ld = lcd_device_register("ld9040", &spi->dev, lcd, &ld9040_lcd_ops); + if (IS_ERR(ld)) { + ret = PTR_ERR(ld); + goto out_free_lcd; + } + + lcd->ld = ld; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = MAX_BRIGHTNESS; + + bd = backlight_device_register("ld9040-bl", &spi->dev, + lcd, &ld9040_backlight_ops, &props); + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + goto out_unregister_lcd; + } + + 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; + + dev_set_drvdata(&spi->dev, lcd); + + dev_info(&spi->dev, "ld9040 panel driver has been probed.\n"); + return 0; + +out_unregister_lcd: + lcd_device_unregister(lcd->ld); +out_free_lcd: + regulator_bulk_free(ARRAY_SIZE(supplies), supplies); + + kfree(lcd); + return ret; +} + +static int __devexit ld9040_remove(struct spi_device *spi) +{ + struct ld9040 *lcd = dev_get_drvdata(&spi->dev); + + ld9040_power(lcd, FB_BLANK_POWERDOWN); + backlight_device_unregister(lcd->bd); + lcd_device_unregister(lcd->ld); + regulator_bulk_free(ARRAY_SIZE(supplies), supplies); + kfree(lcd); + + return 0; +} + +#if defined(CONFIG_PM) +static int ld9040_suspend(struct spi_device *spi, pm_message_t mesg) +{ + int ret = 0; + struct ld9040 *lcd = dev_get_drvdata(&spi->dev); + + dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power); + + /* + * when lcd panel is suspend, lcd panel becomes off + * regardless of status. + */ + ret = ld9040_power(lcd, FB_BLANK_POWERDOWN); + + return ret; +} + +static int ld9040_resume(struct spi_device *spi) +{ + int ret = 0; + struct ld9040 *lcd = dev_get_drvdata(&spi->dev); + + lcd->power = FB_BLANK_POWERDOWN; + + ret = ld9040_power(lcd, FB_BLANK_UNBLANK); + + return ret; +} +#else +#define ld9040_suspend NULL +#define ld9040_resume NULL +#endif + +/* Power down all displays on reboot, poweroff or halt. */ +static void ld9040_shutdown(struct spi_device *spi) +{ + struct ld9040 *lcd = dev_get_drvdata(&spi->dev); + + ld9040_power(lcd, FB_BLANK_POWERDOWN); +} + +static struct spi_driver ld9040_driver = { + .driver = { + .name = "ld9040", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ld9040_probe, + .remove = __devexit_p(ld9040_remove), + .shutdown = ld9040_shutdown, + .suspend = ld9040_suspend, + .resume = ld9040_resume, +}; + +module_spi_driver(ld9040_driver); + +MODULE_AUTHOR("Donghwa Lee <dh09.lee@samsung.com>"); +MODULE_DESCRIPTION("ld9040 LCD Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/ld9040_gamma.h b/drivers/video/backlight/ld9040_gamma.h new file mode 100644 index 00000000..038d9c86 --- /dev/null +++ b/drivers/video/backlight/ld9040_gamma.h @@ -0,0 +1,200 @@ +/* + * 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]; +} 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 diff --git a/drivers/video/backlight/lms283gf05.c b/drivers/video/backlight/lms283gf05.c new file mode 100644 index 00000000..4161f9e3 --- /dev/null +++ b/drivers/video/backlight/lms283gf05.c @@ -0,0 +1,233 @@ +/* + * 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 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 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, + 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 = spi->dev.platform_data; + + 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 __devinit lms283gf05_probe(struct spi_device *spi) +{ + struct lms283gf05_state *st; + struct lms283gf05_pdata *pdata = spi->dev.platform_data; + struct lcd_device *ld; + int ret = 0; + + if (pdata != NULL) { + ret = gpio_request(pdata->reset_gpio, "LMS285GF05 RESET"); + if (ret) + return ret; + + ret = gpio_direction_output(pdata->reset_gpio, + !pdata->reset_inverted); + if (ret) + goto err; + } + + st = kzalloc(sizeof(struct lms283gf05_state), GFP_KERNEL); + if (st == NULL) { + dev_err(&spi->dev, "No memory for device state\n"); + ret = -ENOMEM; + goto err; + } + + ld = lcd_device_register("lms283gf05", &spi->dev, st, &lms_ops); + if (IS_ERR(ld)) { + ret = PTR_ERR(ld); + goto err2; + } + + st->spi = spi; + st->ld = ld; + + dev_set_drvdata(&spi->dev, 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; + +err2: + kfree(st); +err: + if (pdata != NULL) + gpio_free(pdata->reset_gpio); + + return ret; +} + +static int __devexit lms283gf05_remove(struct spi_device *spi) +{ + struct lms283gf05_state *st = dev_get_drvdata(&spi->dev); + struct lms283gf05_pdata *pdata = st->spi->dev.platform_data; + + lcd_device_unregister(st->ld); + + if (pdata != NULL) + gpio_free(pdata->reset_gpio); + + kfree(st); + + return 0; +} + +static struct spi_driver lms283gf05_driver = { + .driver = { + .name = "lms283gf05", + .owner = THIS_MODULE, + }, + .probe = lms283gf05_probe, + .remove = __devexit_p(lms283gf05_remove), +}; + +module_spi_driver(lms283gf05_driver); + +MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); +MODULE_DESCRIPTION("LCD283GF05 LCD"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/backlight/locomolcd.c b/drivers/video/backlight/locomolcd.c new file mode 100644 index 00000000..3a6d5419 --- /dev/null +++ b/drivers/video/backlight/locomolcd.c @@ -0,0 +1,245 @@ +/* + * 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 +static int locomolcd_suspend(struct locomo_dev *dev, pm_message_t state) +{ + locomolcd_flags |= LOCOMOLCD_SUSPENDED; + locomolcd_set_intensity(locomolcd_bl_device); + return 0; +} + +static int locomolcd_resume(struct locomo_dev *dev) +{ + locomolcd_flags &= ~LOCOMOLCD_SUSPENDED; + locomolcd_set_intensity(locomolcd_bl_device); + return 0; +} +#else +#define locomolcd_suspend NULL +#define locomolcd_resume NULL +#endif + +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", + }, + .devid = LOCOMO_DEVID_BACKLIGHT, + .probe = locomolcd_probe, + .remove = locomolcd_remove, + .suspend = locomolcd_suspend, + .resume = locomolcd_resume, +}; + + +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"); diff --git a/drivers/video/backlight/lp855x_bl.c b/drivers/video/backlight/lp855x_bl.c new file mode 100644 index 00000000..72a0e0c9 --- /dev/null +++ b/drivers/video/backlight/lp855x_bl.c @@ -0,0 +1,331 @@ +/* + * 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/lp855x.h> + +/* Registers */ +#define BRIGHTNESS_CTRL (0x00) +#define DEVICE_CTRL (0x01) + +#define BUF_SIZE 20 +#define DEFAULT_BL_NAME "lcd-backlight" +#define MAX_BRIGHTNESS 255 + +struct lp855x { + const char *chipname; + enum lp855x_chip_id chip_id; + struct i2c_client *client; + struct backlight_device *bl; + struct device *dev; + struct mutex xfer_lock; + struct lp855x_platform_data *pdata; +}; + +static int lp855x_read_byte(struct lp855x *lp, u8 reg, u8 *data) +{ + int ret; + + mutex_lock(&lp->xfer_lock); + ret = i2c_smbus_read_byte_data(lp->client, reg); + if (ret < 0) { + mutex_unlock(&lp->xfer_lock); + dev_err(lp->dev, "failed to read 0x%.2x\n", reg); + return ret; + } + mutex_unlock(&lp->xfer_lock); + + *data = (u8)ret; + return 0; +} + +static int lp855x_write_byte(struct lp855x *lp, u8 reg, u8 data) +{ + int ret; + + mutex_lock(&lp->xfer_lock); + ret = i2c_smbus_write_byte_data(lp->client, reg, data); + mutex_unlock(&lp->xfer_lock); + + return ret; +} + +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 = EEPROM_START; + end = EEPROM_END; + break; + case LP8556: + start = EPROM_START; + end = EPROM_END; + break; + default: + return false; + } + + return (addr >= start && addr <= end); +} + +static int lp855x_init_registers(struct lp855x *lp) +{ + u8 val, addr; + int i, ret; + struct lp855x_platform_data *pd = lp->pdata; + + val = pd->initial_brightness; + ret = lp855x_write_byte(lp, BRIGHTNESS_CTRL, val); + if (ret) + return ret; + + val = pd->device_control; + ret = lp855x_write_byte(lp, DEVICE_CTRL, val); + if (ret) + return ret; + + if (pd->load_new_rom_data && pd->size_program) { + 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) + return ret; + } + } + + return ret; +} + +static int lp855x_bl_update_status(struct backlight_device *bl) +{ + struct lp855x *lp = bl_get_data(bl); + enum lp855x_brightness_ctrl_mode mode = lp->pdata->mode; + + if (bl->props.state & BL_CORE_SUSPENDED) + bl->props.brightness = 0; + + if (mode == PWM_BASED) { + struct lp855x_pwm_data *pd = &lp->pdata->pwm_data; + int br = bl->props.brightness; + int max_br = bl->props.max_brightness; + + if (pd->pwm_set_intensity) + pd->pwm_set_intensity(br, max_br); + + } else if (mode == REGISTER_BASED) { + u8 val = bl->props.brightness; + lp855x_write_byte(lp, BRIGHTNESS_CTRL, val); + } + + return 0; +} + +static int lp855x_bl_get_brightness(struct backlight_device *bl) +{ + struct lp855x *lp = bl_get_data(bl); + enum lp855x_brightness_ctrl_mode mode = lp->pdata->mode; + + if (mode == PWM_BASED) { + struct lp855x_pwm_data *pd = &lp->pdata->pwm_data; + int max_br = bl->props.max_brightness; + + if (pd->pwm_get_intensity) + bl->props.brightness = pd->pwm_get_intensity(max_br); + + } else if (mode == REGISTER_BASED) { + u8 val = 0; + + lp855x_read_byte(lp, BRIGHTNESS_CTRL, &val); + bl->props.brightness = val; + } + + return bl->props.brightness; +} + +static const struct backlight_ops lp855x_bl_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = lp855x_bl_update_status, + .get_brightness = lp855x_bl_get_brightness, +}; + +static int lp855x_backlight_register(struct lp855x *lp) +{ + struct backlight_device *bl; + struct backlight_properties props; + struct lp855x_platform_data *pdata = lp->pdata; + 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 = backlight_device_register(name, lp->dev, lp, + &lp855x_bl_ops, &props); + if (IS_ERR(bl)) + return PTR_ERR(bl); + + lp->bl = bl; + + return 0; +} + +static void lp855x_backlight_unregister(struct lp855x *lp) +{ + if (lp->bl) + backlight_device_unregister(lp->bl); +} + +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, BUF_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); + enum lp855x_brightness_ctrl_mode mode = lp->pdata->mode; + char *strmode = NULL; + + if (mode == PWM_BASED) + strmode = "pwm based"; + else if (mode == REGISTER_BASED) + strmode = "register based"; + + return scnprintf(buf, BUF_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, +}; + +static int lp855x_probe(struct i2c_client *cl, const struct i2c_device_id *id) +{ + struct lp855x *lp; + struct lp855x_platform_data *pdata = cl->dev.platform_data; + enum lp855x_brightness_ctrl_mode mode; + int ret; + + if (!pdata) { + dev_err(&cl->dev, "no platform data supplied\n"); + return -EINVAL; + } + + 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; + + mode = pdata->mode; + 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); + + mutex_init(&lp->xfer_lock); + + ret = lp855x_init_registers(lp); + if (ret) { + dev_err(lp->dev, "i2c communication err: %d", ret); + if (mode == REGISTER_BASED) + goto err_dev; + } + + ret = lp855x_backlight_register(lp); + if (ret) { + dev_err(lp->dev, + "failed to register backlight. err: %d\n", ret); + goto err_dev; + } + + ret = sysfs_create_group(&lp->dev->kobj, &lp855x_attr_group); + if (ret) { + dev_err(lp->dev, "failed to register sysfs. err: %d\n", ret); + goto err_sysfs; + } + + backlight_update_status(lp->bl); + return 0; + +err_sysfs: + lp855x_backlight_unregister(lp); +err_dev: + return ret; +} + +static int __devexit 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); + lp855x_backlight_unregister(lp); + + return 0; +} + +static const struct i2c_device_id lp855x_ids[] = { + {"lp8550", LP8550}, + {"lp8551", LP8551}, + {"lp8552", LP8552}, + {"lp8553", LP8553}, + {"lp8556", LP8556}, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp855x_ids); + +static struct i2c_driver lp855x_driver = { + .driver = { + .name = "lp855x", + }, + .probe = lp855x_probe, + .remove = __devexit_p(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"); diff --git a/drivers/video/backlight/ltv350qv.c b/drivers/video/backlight/ltv350qv.c new file mode 100644 index 00000000..333949ff --- /dev/null +++ b/drivers/video/backlight/ltv350qv.c @@ -0,0 +1,329 @@ +/* + * 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; + msleep(15); + + /* 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); + msleep(1); +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 */ + msleep(1); + + /* 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 __devinit ltv350qv_probe(struct spi_device *spi) +{ + struct ltv350qv *lcd; + struct lcd_device *ld; + int ret; + + lcd = kzalloc(sizeof(struct ltv350qv), GFP_KERNEL); + if (!lcd) + return -ENOMEM; + + lcd->spi = spi; + lcd->power = FB_BLANK_POWERDOWN; + lcd->buffer = kzalloc(8, GFP_KERNEL); + if (!lcd->buffer) { + ret = -ENOMEM; + goto out_free_lcd; + } + + ld = lcd_device_register("ltv350qv", &spi->dev, lcd, <v_ops); + if (IS_ERR(ld)) { + ret = PTR_ERR(ld); + goto out_free_buffer; + } + lcd->ld = ld; + + ret = ltv350qv_power(lcd, FB_BLANK_UNBLANK); + if (ret) + goto out_unregister; + + dev_set_drvdata(&spi->dev, lcd); + + return 0; + +out_unregister: + lcd_device_unregister(ld); +out_free_buffer: + kfree(lcd->buffer); +out_free_lcd: + kfree(lcd); + return ret; +} + +static int __devexit ltv350qv_remove(struct spi_device *spi) +{ + struct ltv350qv *lcd = dev_get_drvdata(&spi->dev); + + ltv350qv_power(lcd, FB_BLANK_POWERDOWN); + lcd_device_unregister(lcd->ld); + kfree(lcd->buffer); + kfree(lcd); + + return 0; +} + +#ifdef CONFIG_PM +static int ltv350qv_suspend(struct spi_device *spi, pm_message_t state) +{ + struct ltv350qv *lcd = dev_get_drvdata(&spi->dev); + + return ltv350qv_power(lcd, FB_BLANK_POWERDOWN); +} + +static int ltv350qv_resume(struct spi_device *spi) +{ + struct ltv350qv *lcd = dev_get_drvdata(&spi->dev); + + return ltv350qv_power(lcd, FB_BLANK_UNBLANK); +} +#else +#define ltv350qv_suspend NULL +#define ltv350qv_resume NULL +#endif + +/* Power down all displays on reboot, poweroff or halt */ +static void ltv350qv_shutdown(struct spi_device *spi) +{ + struct ltv350qv *lcd = dev_get_drvdata(&spi->dev); + + ltv350qv_power(lcd, FB_BLANK_POWERDOWN); +} + +static struct spi_driver ltv350qv_driver = { + .driver = { + .name = "ltv350qv", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + + .probe = ltv350qv_probe, + .remove = __devexit_p(ltv350qv_remove), + .shutdown = ltv350qv_shutdown, + .suspend = ltv350qv_suspend, + .resume = ltv350qv_resume, +}; + +module_spi_driver(ltv350qv_driver); + +MODULE_AUTHOR("Haavard Skinnemoen (Atmel)"); +MODULE_DESCRIPTION("Samsung LTV350QV LCD Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:ltv350qv"); diff --git a/drivers/video/backlight/ltv350qv.h b/drivers/video/backlight/ltv350qv.h new file mode 100644 index 00000000..189112e3 --- /dev/null +++ b/drivers/video/backlight/ltv350qv.h @@ -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 */ diff --git a/drivers/video/backlight/max8925_bl.c b/drivers/video/backlight/max8925_bl.c new file mode 100644 index 00000000..e833ac72 --- /dev/null +++ b/drivers/video/backlight/max8925_bl.c @@ -0,0 +1,193 @@ +/* + * 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; +}; + +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, MAX8925_WLED_CNTL, value); + if (ret < 0) + goto out; + + if (!data->current_brightness && brightness) + /* enable WLED output */ + ret = max8925_set_bits(chip->i2c, MAX8925_WLED_MODE_CNTL, 1, 1); + else if (!brightness) + /* disable WLED output */ + ret = max8925_set_bits(chip->i2c, MAX8925_WLED_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, MAX8925_WLED_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 int __devinit max8925_backlight_probe(struct platform_device *pdev) +{ + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct max8925_platform_data *max8925_pdata; + struct max8925_backlight_pdata *pdata = NULL; + struct max8925_backlight_data *data; + struct backlight_device *bl; + struct backlight_properties props; + struct resource *res; + char name[MAX8925_NAME_SIZE]; + unsigned char value; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (res == NULL) { + dev_err(&pdev->dev, "No I/O resource!\n"); + return -EINVAL; + } + + if (pdev->dev.parent->platform_data) { + max8925_pdata = pdev->dev.parent->platform_data; + pdata = max8925_pdata->backlight; + } + + if (!pdata) { + dev_err(&pdev->dev, "platform data isn't assigned to " + "backlight\n"); + return -EINVAL; + } + + data = devm_kzalloc(&pdev->dev, sizeof(struct max8925_backlight_data), + GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + strncpy(name, res->name, MAX8925_NAME_SIZE); + data->chip = chip; + data->current_brightness = 0; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = MAX_BRIGHTNESS; + bl = backlight_device_register(name, &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 (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, MAX8925_WLED_MODE_CNTL, 0xfe, value); + if (ret < 0) + goto out; + + backlight_update_status(bl); + return 0; +out: + backlight_device_unregister(bl); + return ret; +} + +static int __devexit max8925_backlight_remove(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + + backlight_device_unregister(bl); + return 0; +} + +static struct platform_driver max8925_backlight_driver = { + .driver = { + .name = "max8925-backlight", + .owner = THIS_MODULE, + }, + .probe = max8925_backlight_probe, + .remove = __devexit_p(max8925_backlight_remove), +}; + +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"); diff --git a/drivers/video/backlight/omap1_bl.c b/drivers/video/backlight/omap1_bl.c new file mode 100644 index 00000000..0175bfb0 --- /dev/null +++ b/drivers/video/backlight/omap1_bl.c @@ -0,0 +1,199 @@ +/* + * 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 <mach/hardware.h> +#include <plat/board.h> +#include <plat/mux.h> + +#define OMAPBL_MAX_INTENSITY 0xff + +struct omap_backlight { + int powermode; + int current_intensity; + + struct device *dev; + struct omap_backlight_config *pdata; +}; + +static void inline omapbl_send_intensity(int intensity) +{ + omap_writeb(intensity, OMAP_PWL_ENABLE); +} + +static void inline 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 +static int omapbl_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct backlight_device *dev = platform_get_drvdata(pdev); + struct omap_backlight *bl = dev_get_drvdata(&dev->dev); + + omapbl_blank(bl, FB_BLANK_POWERDOWN); + return 0; +} + +static int omapbl_resume(struct platform_device *pdev) +{ + struct backlight_device *dev = platform_get_drvdata(pdev); + struct omap_backlight *bl = dev_get_drvdata(&dev->dev); + + omapbl_blank(bl, bl->powermode); + return 0; +} +#else +#define omapbl_suspend NULL +#define omapbl_resume NULL +#endif + +static int omapbl_set_power(struct backlight_device *dev, int state) +{ + struct omap_backlight *bl = dev_get_drvdata(&dev->dev); + + omapbl_blank(bl, state); + bl->powermode = state; + + return 0; +} + +static int omapbl_update_status(struct backlight_device *dev) +{ + struct omap_backlight *bl = dev_get_drvdata(&dev->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 = dev_get_drvdata(&dev->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 = pdev->dev.platform_data; + + 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 = backlight_device_register("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); + + printk(KERN_INFO "OMAP LCD backlight initialised\n"); + + return 0; +} + +static int omapbl_remove(struct platform_device *pdev) +{ + struct backlight_device *dev = platform_get_drvdata(pdev); + + backlight_device_unregister(dev); + + return 0; +} + +static struct platform_driver omapbl_driver = { + .probe = omapbl_probe, + .remove = omapbl_remove, + .suspend = omapbl_suspend, + .resume = omapbl_resume, + .driver = { + .name = "omap-bl", + }, +}; + +module_platform_driver(omapbl_driver); + +MODULE_AUTHOR("Andrzej Zaborowski <balrog@zabor.org>"); +MODULE_DESCRIPTION("OMAP LCD Backlight driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/ot200_bl.c b/drivers/video/backlight/ot200_bl.c new file mode 100644 index 00000000..f519d55a --- /dev/null +++ b/drivers/video/backlight/ot200_bl.c @@ -0,0 +1,175 @@ +/* + * 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/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 (gpio_request(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"); + retval = -ENODEV; + goto error_mfgpt_alloc; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + retval = -ENOMEM; + goto error_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 = backlight_device_register(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_backlight_device_register; + } + + platform_set_drvdata(pdev, bl); + + return 0; + +error_backlight_device_register: + kfree(data); +error_kzalloc: + cs5535_mfgpt_free_timer(pwm_timer); +error_mfgpt_alloc: + gpio_free(GPIO_DIMM); + return retval; +} + +static int ot200_backlight_remove(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + struct ot200_backlight_data *data = bl_get_data(bl); + + backlight_device_unregister(bl); + + /* 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); + gpio_free(GPIO_DIMM); + + kfree(data); + return 0; +} + +static struct platform_driver ot200_backlight_driver = { + .driver = { + .name = "ot200-backlight", + .owner = THIS_MODULE, + }, + .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"); diff --git a/drivers/video/backlight/pandora_bl.c b/drivers/video/backlight/pandora_bl.c new file mode 100644 index 00000000..4ec30748 --- /dev/null +++ b/drivers/video/backlight/pandora_bl.c @@ -0,0 +1,171 @@ +/* + * 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(TWL4030_MODULE_PWM0, 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(TWL4030_MODULE_PWM0, 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 int pandora_backlight_get_brightness(struct backlight_device *bl) +{ + return bl->props.brightness; +} + +static const struct backlight_ops pandora_backlight_ops = { + .options = BL_CORE_SUSPENDRESUME, + .update_status = pandora_backlight_update_status, + .get_brightness = pandora_backlight_get_brightness, +}; + +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 = backlight_device_register(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(TWL4030_MODULE_PWM0, 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 int pandora_backlight_remove(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + backlight_device_unregister(bl); + return 0; +} + +static struct platform_driver pandora_backlight_driver = { + .driver = { + .name = "pandora-backlight", + .owner = THIS_MODULE, + }, + .probe = pandora_backlight_probe, + .remove = pandora_backlight_remove, +}; + +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"); diff --git a/drivers/video/backlight/pcf50633-backlight.c b/drivers/video/backlight/pcf50633-backlight.c new file mode 100644 index 00000000..c65853cb --- /dev/null +++ b/drivers/video/backlight/pcf50633-backlight.c @@ -0,0 +1,171 @@ +/* + * 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 __devinit pcf50633_bl_probe(struct platform_device *pdev) +{ + struct pcf50633_bl *pcf_bl; + struct device *parent = pdev->dev.parent; + struct pcf50633_platform_data *pcf50633_data = parent->platform_data; + 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; + + 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 = backlight_device_register(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 int __devexit pcf50633_bl_remove(struct platform_device *pdev) +{ + struct pcf50633_bl *pcf_bl = platform_get_drvdata(pdev); + + backlight_device_unregister(pcf_bl->bl); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver pcf50633_bl_driver = { + .probe = pcf50633_bl_probe, + .remove = __devexit_p(pcf50633_bl_remove), + .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"); diff --git a/drivers/video/backlight/platform_lcd.c b/drivers/video/backlight/platform_lcd.c new file mode 100644 index 00000000..b6672340 --- /dev/null +++ b/drivers/video/backlight/platform_lcd.c @@ -0,0 +1,164 @@ +/* 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/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 __devinit platform_lcd_probe(struct platform_device *pdev) +{ + struct plat_lcd_data *pdata; + struct platform_lcd *plcd; + struct device *dev = &pdev->dev; + int err; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(dev, "no platform data supplied\n"); + return -EINVAL; + } + + plcd = devm_kzalloc(&pdev->dev, sizeof(struct platform_lcd), + GFP_KERNEL); + if (!plcd) { + dev_err(dev, "no memory for state\n"); + return -ENOMEM; + } + + plcd->us = dev; + plcd->pdata = pdata; + plcd->lcd = lcd_device_register(dev_name(dev), dev, + plcd, &platform_lcd_ops); + if (IS_ERR(plcd->lcd)) { + dev_err(dev, "cannot register lcd device\n"); + err = PTR_ERR(plcd->lcd); + goto err; + } + + platform_set_drvdata(pdev, plcd); + platform_lcd_set_power(plcd->lcd, FB_BLANK_NORMAL); + + return 0; + + err: + return err; +} + +static int __devexit platform_lcd_remove(struct platform_device *pdev) +{ + struct platform_lcd *plcd = platform_get_drvdata(pdev); + + lcd_device_unregister(plcd->lcd); + + return 0; +} + +#ifdef CONFIG_PM +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; +} + +static SIMPLE_DEV_PM_OPS(platform_lcd_pm_ops, platform_lcd_suspend, + platform_lcd_resume); +#endif + +static struct platform_driver platform_lcd_driver = { + .driver = { + .name = "platform-lcd", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &platform_lcd_pm_ops, +#endif + }, + .probe = platform_lcd_probe, + .remove = __devexit_p(platform_lcd_remove), +}; + +module_platform_driver(platform_lcd_driver); + +MODULE_AUTHOR("Ben Dooks <ben-linux@fluff.org>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:platform-lcd"); diff --git a/drivers/video/backlight/progear_bl.c b/drivers/video/backlight/progear_bl.c new file mode 100644 index 00000000..6af183d6 --- /dev/null +++ b/drivers/video/backlight/progear_bl.c @@ -0,0 +1,160 @@ +/* + * Backlight Driver for Frontpath ProGear HX1050+ + * + * Copyright (c) 2006 Marcin Juszkiewicz + * + * Based on Progear LCD driver by M Schacht + * <mschacht at alumni dot washington dot edu> + * + * Based on Sharp's Corgi Backlight Driver + * Based on Backlight Driver for HP Jornada 680 + * + * 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/pci.h> + +#define PMU_LPCR 0xB0 +#define SB_MPS1 0x61 +#define HW_LEVEL_MAX 0x77 +#define HW_LEVEL_MIN 0x4f + +static struct pci_dev *pmu_dev = NULL; +static struct pci_dev *sb_dev = NULL; + +static int progearbl_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; + + pci_write_config_byte(pmu_dev, PMU_LPCR, intensity + HW_LEVEL_MIN); + + return 0; +} + +static int progearbl_get_intensity(struct backlight_device *bd) +{ + u8 intensity; + pci_read_config_byte(pmu_dev, PMU_LPCR, &intensity); + + return intensity - HW_LEVEL_MIN; +} + +static const struct backlight_ops progearbl_ops = { + .get_brightness = progearbl_get_intensity, + .update_status = progearbl_set_intensity, +}; + +static int progearbl_probe(struct platform_device *pdev) +{ + struct backlight_properties props; + u8 temp; + struct backlight_device *progear_backlight_device; + int ret; + + pmu_dev = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101, NULL); + if (!pmu_dev) { + printk("ALI M7101 PMU not found.\n"); + return -ENODEV; + } + + sb_dev = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, NULL); + if (!sb_dev) { + printk("ALI 1533 SB not found.\n"); + ret = -ENODEV; + goto put_pmu; + } + + /* Set SB_MPS1 to enable brightness control. */ + pci_read_config_byte(sb_dev, SB_MPS1, &temp); + pci_write_config_byte(sb_dev, SB_MPS1, temp | 0x20); + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = HW_LEVEL_MAX - HW_LEVEL_MIN; + progear_backlight_device = backlight_device_register("progear-bl", + &pdev->dev, NULL, + &progearbl_ops, + &props); + if (IS_ERR(progear_backlight_device)) { + ret = PTR_ERR(progear_backlight_device); + goto put_sb; + } + + platform_set_drvdata(pdev, progear_backlight_device); + + progear_backlight_device->props.power = FB_BLANK_UNBLANK; + progear_backlight_device->props.brightness = HW_LEVEL_MAX - HW_LEVEL_MIN; + progearbl_set_intensity(progear_backlight_device); + + return 0; +put_sb: + pci_dev_put(sb_dev); +put_pmu: + pci_dev_put(pmu_dev); + return ret; +} + +static int progearbl_remove(struct platform_device *pdev) +{ + struct backlight_device *bd = platform_get_drvdata(pdev); + backlight_device_unregister(bd); + + return 0; +} + +static struct platform_driver progearbl_driver = { + .probe = progearbl_probe, + .remove = progearbl_remove, + .driver = { + .name = "progear-bl", + }, +}; + +static struct platform_device *progearbl_device; + +static int __init progearbl_init(void) +{ + int ret = platform_driver_register(&progearbl_driver); + + if (ret) + return ret; + progearbl_device = platform_device_register_simple("progear-bl", -1, + NULL, 0); + if (IS_ERR(progearbl_device)) { + platform_driver_unregister(&progearbl_driver); + return PTR_ERR(progearbl_device); + } + + return 0; +} + +static void __exit progearbl_exit(void) +{ + pci_dev_put(pmu_dev); + pci_dev_put(sb_dev); + + platform_device_unregister(progearbl_device); + platform_driver_unregister(&progearbl_driver); +} + +module_init(progearbl_init); +module_exit(progearbl_exit); + +MODULE_AUTHOR("Marcin Juszkiewicz <linux@hrw.one.pl>"); +MODULE_DESCRIPTION("ProGear Backlight Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/backlight/pwm_bl.c b/drivers/video/backlight/pwm_bl.c new file mode 100644 index 00000000..a8d900d0 --- /dev/null +++ b/drivers/video/backlight/pwm_bl.c @@ -0,0 +1,394 @@ +/* + * 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/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/slab.h> +#include <linux/irq.h> + +#include <mach/hardware.h> + +#include <linux/delay.h> +#include <linux/gpio.h> +#include <mach/wmt_iomux.h> + +int pwm_or_gpio=1; +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + + +struct pwm_bl_data { + struct pwm_device *pwm; + struct device *dev; + unsigned int invert; + unsigned int period; + unsigned int lth_ns; + unsigned int hth_ns; + int (*notify)(struct device *, + int brightness); + void (*notify_after)(struct device *, + int brightness); + int (*check_fb)(struct device *, struct fb_info *); +}; + +static inline void __pwm0_gpio_setup(void) +{ + /* select pwm function */ + REG8_VAL(GPIO_CTRL_GP20_PWM0_BYTE_ADDR) &= ~0x1; + + /* pwm0 output enable */ + REG8_VAL(GPIO_OD_GP20_PWM0_BYTE_ADDR) &= ~0x01; + + /* share pin */ + REG32_VAL(PIN_SHARING_SEL_4BYTE_ADDR) &= ~0x1000; +} + +static int pre_brightness_value; +void sgm3727_bl_brightness(int brightness_value) +{ + int i = 0; + int pulse_num = 0; + brightness_value= (brightness_value-10)*31/245; + brightness_value = 31 - brightness_value; + + if (brightness_value >= 32) + { + brightness_value = 31; + } + if (brightness_value < 0) + { + brightness_value = 0; + } + //printk("%s,brightness_value=%d,%d\n",__FUNCTION__,pre_brightness_value,brightness_value); + if(pre_brightness_value == brightness_value) + { + return; + } + else if(pre_brightness_value == 0xFF) + { + pulse_num = brightness_value; + } + else if(brightness_value>pre_brightness_value) + { + pulse_num = brightness_value - pre_brightness_value; + } + else + { + pulse_num = (brightness_value + 32 ) - pre_brightness_value; + } + + //printk("%s,pulse_num=%d\n",__FUNCTION__,pulse_num); + if (pulse_num < 32) + { + local_irq_disable(); + if(pre_brightness_value == 0xFF) + { + gpio_direction_output(WMT_PIN_GP0_GPIO0,0); + mdelay(5);//5 + gpio_direction_output(WMT_PIN_GP0_GPIO0,1); + udelay(50); + } + for (i=0; i < pulse_num; i++) + { + gpio_direction_output(WMT_PIN_GP0_GPIO0,0); + udelay(10);//50 + gpio_direction_output(WMT_PIN_GP0_GPIO0,1); + udelay(10);//50 + } + local_irq_enable(); + } + + pre_brightness_value = brightness_value; +} + +static int cw500_backlight_update_status(struct backlight_device *bl) +{ + struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); + int brightness = bl->props.brightness; + int max = bl->props.max_brightness; + + if(!brightness) + { + gpio_direction_output(WMT_PIN_GP0_GPIO0,0); + pre_brightness_value = 0xFF; + mdelay(10); + return; + } + else + { + sgm3727_bl_brightness(brightness); + } + + return 0; +} + +static int pwm_backlight_update_status(struct backlight_device *bl) +{ + struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); + int brightness = bl->props.brightness; + int max = bl->props.max_brightness; + +// if (bl->props.power != FB_BLANK_UNBLANK) +// brightness = 0; +// +// if (bl->props.fb_blank != FB_BLANK_UNBLANK) +// brightness = 0; + + if (pb->notify) + brightness = pb->notify(pb->dev, brightness); + + if (brightness == 0) { + if (pb->invert) { + pwm_config(pb->pwm, pb->period, pb->period); + } else { + pwm_config(pb->pwm, 0, pb->period); + pwm_disable(pb->pwm); + } + } else { + brightness = pb->lth_ns + + (brightness * (pb->period - pb->lth_ns - pb->hth_ns) / max); + if (pb->invert) + brightness = pb->period - brightness; + pwm_config(pb->pwm, brightness, pb->period); + pwm_enable(pb->pwm); + } + + if (pb->notify_after) + pb->notify_after(pb->dev, brightness); + + return 0; +} + +static int pwm_backlight_get_brightness(struct backlight_device *bl) +{ + return bl->props.brightness; +} + +static int pwm_backlight_check_fb(struct backlight_device *bl, + struct fb_info *info) +{ + struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); + + return !pb->check_fb || pb->check_fb(pb->dev, info); +} + +backlight_update_status_select(struct backlight_device *bl) +{ + if(pwm_or_gpio){ + //printk("pwm branch\n"); + pwm_backlight_update_status(bl); + }else{ + printk("gpio branch\n"); + cw500_backlight_update_status(bl); + } + +} + +static const struct backlight_ops pwm_backlight_ops = { + //.update_status = pwm_backlight_update_status,//TODO: add uboot control rubbit + //.update_status = cw500_backlight_update_status, + .update_status = backlight_update_status_select, + .get_brightness = pwm_backlight_get_brightness, + .check_fb = pwm_backlight_check_fb, +}; +/* +* wmt.bl_select: +* 0----->gpio control back light +* 1----->pwm control back light +* default pwm control back light +*/ +static int pwm_backlight_probe(struct platform_device *pdev) +{ + struct backlight_properties props; + struct platform_pwm_backlight_data *data = pdev->dev.platform_data; + struct backlight_device *bl; + struct pwm_bl_data *pb; + int ret; + unsigned char buf[64]; + int varlen = sizeof(buf); + + memset(buf, 0x0, sizeof(buf)); + ret = wmt_getsyspara("wmt.bl_select", buf, &varlen); + if (ret == 0) { + sscanf(buf, "%d", &pwm_or_gpio); + } + else { + pwm_or_gpio = 1; + printk("default use pwm to control backlight,pwm_or_gpio:%d\n",pwm_or_gpio); + } + if (!data) { + dev_err(&pdev->dev, "failed to find platform data\n"); + return -EINVAL; + } + + if (data->init) { + ret = data->init(&pdev->dev); + if (ret < 0) + return ret; + } + + pb = devm_kzalloc(&pdev->dev, sizeof(*pb), GFP_KERNEL); + if (!pb) { + dev_err(&pdev->dev, "no memory for state\n"); + ret = -ENOMEM; + goto err_alloc; + } + if(pwm_or_gpio){ + __pwm0_gpio_setup(); + }else{ + pre_brightness_value = 0; + //ret = gpio_request(WMT_PIN_GP0_GPIO0,"gpio lcd back light"); + //if (ret){ + // printk("gpio_request gpio lcd back light failed\n"); + // goto err_alloc; + //} + } + pb->invert = data->invert; + pb->period = data->pwm_period_ns; + pb->notify = data->notify; + pb->notify_after = data->notify_after; + pb->check_fb = data->check_fb; + pb->lth_ns = data->lth_brightness * (data->pwm_period_ns / data->max_brightness); + pb->hth_ns = (data->max_brightness - data->hth_brightness) * + (data->pwm_period_ns / data->max_brightness); + pb->dev = &pdev->dev; + + pb->pwm = pwm_request(data->pwm_id, "backlight"); + if (IS_ERR(pb->pwm)) { + dev_err(&pdev->dev, "unable to request PWM for backlight\n"); + ret = PTR_ERR(pb->pwm); + goto err_alloc; + } else + dev_dbg(&pdev->dev, "got pwm for backlight\n"); + + 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_bl; + } + + bl->props.brightness = data->dft_brightness; + //backlight_update_status(bl); + + platform_set_drvdata(pdev, bl); + return 0; + +err_bl: + pwm_free(pb->pwm); +err_alloc: + if (data->exit) + data->exit(&pdev->dev); + return ret; +} + +static int pwm_backlight_remove(struct platform_device *pdev) +{ + struct platform_pwm_backlight_data *data = pdev->dev.platform_data; + struct backlight_device *bl = platform_get_drvdata(pdev); + struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); + + backlight_device_unregister(bl); + pwm_config(pb->pwm, 0, pb->period); + pwm_disable(pb->pwm); + pwm_free(pb->pwm); + if (data->exit) + data->exit(&pdev->dev); + if(!pwm_or_gpio) + gpio_free(WMT_PIN_GP0_GPIO0); + return 0; +} + +#ifdef CONFIG_PM +static int pwm_backlight_suspend(struct device *dev) +{ + int ret=0; + struct backlight_device *bl = dev_get_drvdata(dev); + struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev); + printk("%s\n",__func__); + if (pb->notify) + pb->notify(pb->dev, 0); + if(pwm_or_gpio){ + if (pb->invert) { + pwm_config(pb->pwm, pb->period, pb->period); + } else { + pwm_config(pb->pwm, 0, pb->period); + pwm_disable(pb->pwm); + } + }else{ + + } + + if (pb->notify_after) + pb->notify_after(pb->dev, 0); + if(!pwm_or_gpio){ + gpio_free(WMT_PIN_GP0_GPIO0); + } + return 0; +} + +static int pwm_backlight_resume(struct device *dev) +{ + int ret=0; + printk("%s\n",__func__); + if(pwm_or_gpio){ + __pwm0_gpio_setup(); + }else{ + ret = gpio_request(WMT_PIN_GP0_GPIO0,"gpio lcd back light"); + if (ret){ + printk("gpio_request gpio lcd back light failed\n"); + return -1; + } + gpio_direction_output(WMT_PIN_GP0_GPIO0,0); + } + return 0; +} + +static SIMPLE_DEV_PM_OPS(pwm_backlight_pm_ops, pwm_backlight_suspend, + pwm_backlight_resume); + +#endif + +static void pwm_backlight_shutdown(struct platform_device *pdev) +{ + pwm_backlight_suspend(&pdev->dev); +} + +static struct platform_driver pwm_backlight_driver = { + .driver = { + .name = "pwm-backlight", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &pwm_backlight_pm_ops, +#endif + }, + .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"); + diff --git a/drivers/video/backlight/s6e63m0.c b/drivers/video/backlight/s6e63m0.c new file mode 100644 index 00000000..e264f55b --- /dev/null +++ b/drivers/video/backlight/s6e63m0.c @@ -0,0 +1,917 @@ +/* + * 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. + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <linux/wait.h> +#include <linux/fb.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/spi/spi.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/lcd.h> +#include <linux/backlight.h> +#include <linux/module.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 + +#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL) + +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 + udelay(wbuf[i+1]*1000); + 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_on(struct s6e63m0 *lcd) +{ + int ret = 0; + struct lcd_platform_data *pd = NULL; + struct backlight_device *bd = NULL; + + pd = lcd->lcd_pd; + if (!pd) { + dev_err(lcd->dev, "platform data is NULL.\n"); + return -EFAULT; + } + + bd = lcd->bd; + if (!bd) { + dev_err(lcd->dev, "backlight device is NULL.\n"); + return -EFAULT; + } + + if (!pd->power_on) { + dev_err(lcd->dev, "power_on is NULL.\n"); + return -EFAULT; + } else { + pd->power_on(lcd->ld, 1); + mdelay(pd->power_on_delay); + } + + if (!pd->reset) { + dev_err(lcd->dev, "reset is NULL.\n"); + return -EFAULT; + } else { + pd->reset(lcd->ld); + mdelay(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 = 0; + struct lcd_platform_data *pd = NULL; + + pd = lcd->lcd_pd; + if (!pd) { + dev_err(lcd->dev, "platform data is NULL.\n"); + return -EFAULT; + } + + ret = s6e63m0_ldi_disable(lcd); + if (ret) { + dev_err(lcd->dev, "lcd setting failed.\n"); + return -EIO; + } + + mdelay(pd->power_off_delay); + + if (!pd->power_on) { + dev_err(lcd->dev, "power_on is NULL.\n"); + return -EFAULT; + } else + pd->power_on(lcd->ld, 0); + + return 0; +} + +static int s6e63m0_power(struct s6e63m0 *lcd, int power) +{ + int ret = 0; + + if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power)) + ret = s6e63m0_power_on(lcd); + else if (!POWER_IS_ON(power) && 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_get_brightness(struct backlight_device *bd) +{ + return bd->props.brightness; +} + +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 = { + .get_brightness = s6e63m0_get_brightness, + .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, "%d\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 __devinit 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 = kzalloc(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"); + goto out_free_lcd; + } + + lcd->spi = spi; + lcd->dev = &spi->dev; + + lcd->lcd_pd = (struct lcd_platform_data *)spi->dev.platform_data; + if (!lcd->lcd_pd) { + dev_err(&spi->dev, "platform data is NULL.\n"); + goto out_free_lcd; + } + + ld = lcd_device_register("s6e63m0", &spi->dev, lcd, &s6e63m0_lcd_ops); + if (IS_ERR(ld)) { + ret = PTR_ERR(ld); + goto out_free_lcd; + } + + lcd->ld = ld; + + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = MAX_BRIGHTNESS; + + bd = backlight_device_register("s6e63m0bl-bl", &spi->dev, lcd, + &s6e63m0_backlight_ops, &props); + if (IS_ERR(bd)) { + ret = PTR_ERR(bd); + goto out_lcd_unregister; + } + + 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; + + dev_set_drvdata(&spi->dev, lcd); + + dev_info(&spi->dev, "s6e63m0 panel driver has been probed.\n"); + + return 0; + +out_lcd_unregister: + lcd_device_unregister(ld); +out_free_lcd: + kfree(lcd); + return ret; +} + +static int __devexit s6e63m0_remove(struct spi_device *spi) +{ + struct s6e63m0 *lcd = dev_get_drvdata(&spi->dev); + + s6e63m0_power(lcd, FB_BLANK_POWERDOWN); + device_remove_file(&spi->dev, &dev_attr_gamma_table); + device_remove_file(&spi->dev, &dev_attr_gamma_mode); + backlight_device_unregister(lcd->bd); + lcd_device_unregister(lcd->ld); + kfree(lcd); + + return 0; +} + +#if defined(CONFIG_PM) +static unsigned int before_power; + +static int s6e63m0_suspend(struct spi_device *spi, pm_message_t mesg) +{ + int ret = 0; + struct s6e63m0 *lcd = dev_get_drvdata(&spi->dev); + + dev_dbg(&spi->dev, "lcd->power = %d\n", lcd->power); + + before_power = lcd->power; + + /* + * when lcd panel is suspend, lcd panel becomes off + * regardless of status. + */ + ret = s6e63m0_power(lcd, FB_BLANK_POWERDOWN); + + return ret; +} + +static int s6e63m0_resume(struct spi_device *spi) +{ + int ret = 0; + struct s6e63m0 *lcd = dev_get_drvdata(&spi->dev); + + /* + * after suspended, if lcd panel status is FB_BLANK_UNBLANK + * (at that time, before_power is FB_BLANK_UNBLANK) then + * it changes that status to FB_BLANK_POWERDOWN to get lcd on. + */ + if (before_power == FB_BLANK_UNBLANK) + lcd->power = FB_BLANK_POWERDOWN; + + dev_dbg(&spi->dev, "before_power = %d\n", before_power); + + ret = s6e63m0_power(lcd, before_power); + + return ret; +} +#else +#define s6e63m0_suspend NULL +#define s6e63m0_resume NULL +#endif + +/* Power down all displays on reboot, poweroff or halt. */ +static void s6e63m0_shutdown(struct spi_device *spi) +{ + struct s6e63m0 *lcd = dev_get_drvdata(&spi->dev); + + s6e63m0_power(lcd, FB_BLANK_POWERDOWN); +} + +static struct spi_driver s6e63m0_driver = { + .driver = { + .name = "s6e63m0", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = s6e63m0_probe, + .remove = __devexit_p(s6e63m0_remove), + .shutdown = s6e63m0_shutdown, + .suspend = s6e63m0_suspend, + .resume = s6e63m0_resume, +}; + +module_spi_driver(s6e63m0_driver); + +MODULE_AUTHOR("InKi Dae <inki.dae@samsung.com>"); +MODULE_DESCRIPTION("S6E63M0 LCD Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/video/backlight/s6e63m0_gamma.h b/drivers/video/backlight/s6e63m0_gamma.h new file mode 100644 index 00000000..2c44bdb0 --- /dev/null +++ b/drivers/video/backlight/s6e63m0_gamma.h @@ -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 + diff --git a/drivers/video/backlight/tdo24m.c b/drivers/video/backlight/tdo24m.c new file mode 100644 index 00000000..2368b8e5 --- /dev/null +++ b/drivers/video/backlight/tdo24m.c @@ -0,0 +1,467 @@ +/* + * 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 uint32_t lcd_panel_reset[] = { + CMD0(0x1), /* reset */ + CMD0(0x0), /* nop */ + CMD0(0x0), /* nop */ + CMD0(0x0), /* nop */ + CMD_NULL, +}; + +static 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 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 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 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 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 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 uint32_t lcd_vga_pass_through_tdo35s[] = { + CMD1(0xB0, 0x16), + CMD1(0xBC, 0x80), + CMD1(0xE1, 0x00), + CMD1(0x3B, 0x00), + CMD_NULL, +}; + +static uint32_t lcd_qvga_pass_through_tdo35s[] = { + CMD1(0xB0, 0x16), + CMD1(0xBC, 0x81), + CMD1(0xE1, 0x00), + CMD1(0x3B, 0x22), + CMD_NULL, +}; + +static 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 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, uint32_t *array) +{ + struct spi_transfer *x = &lcd->xfer; + uint32_t data, *p = array; + 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 __devinit 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 = spi->dev.platform_data; + 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 = kzalloc(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 = kmalloc(TDO24M_SPI_BUFF_SIZE, GFP_KERNEL); + if (lcd->buf == NULL) { + kfree(lcd); + 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"); + goto out_free; + } + + lcd->lcd_dev = lcd_device_register("tdo24m", &spi->dev, + lcd, &tdo24m_ops); + if (IS_ERR(lcd->lcd_dev)) { + err = PTR_ERR(lcd->lcd_dev); + goto out_free; + } + + dev_set_drvdata(&spi->dev, lcd); + err = tdo24m_power(lcd, FB_BLANK_UNBLANK); + if (err) + goto out_unregister; + + return 0; + +out_unregister: + lcd_device_unregister(lcd->lcd_dev); +out_free: + kfree(lcd->buf); + kfree(lcd); + return err; +} + +static int __devexit tdo24m_remove(struct spi_device *spi) +{ + struct tdo24m *lcd = dev_get_drvdata(&spi->dev); + + tdo24m_power(lcd, FB_BLANK_POWERDOWN); + lcd_device_unregister(lcd->lcd_dev); + kfree(lcd->buf); + kfree(lcd); + + return 0; +} + +#ifdef CONFIG_PM +static int tdo24m_suspend(struct spi_device *spi, pm_message_t state) +{ + struct tdo24m *lcd = dev_get_drvdata(&spi->dev); + + return tdo24m_power(lcd, FB_BLANK_POWERDOWN); +} + +static int tdo24m_resume(struct spi_device *spi) +{ + struct tdo24m *lcd = dev_get_drvdata(&spi->dev); + + return tdo24m_power(lcd, FB_BLANK_UNBLANK); +} +#else +#define tdo24m_suspend NULL +#define tdo24m_resume NULL +#endif + +/* Power down all displays on reboot, poweroff or halt */ +static void tdo24m_shutdown(struct spi_device *spi) +{ + struct tdo24m *lcd = dev_get_drvdata(&spi->dev); + + tdo24m_power(lcd, FB_BLANK_POWERDOWN); +} + +static struct spi_driver tdo24m_driver = { + .driver = { + .name = "tdo24m", + .owner = THIS_MODULE, + }, + .probe = tdo24m_probe, + .remove = __devexit_p(tdo24m_remove), + .shutdown = tdo24m_shutdown, + .suspend = tdo24m_suspend, + .resume = tdo24m_resume, +}; + +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"); diff --git a/drivers/video/backlight/tosa_bl.c b/drivers/video/backlight/tosa_bl.c new file mode 100644 index 00000000..2b241abc --- /dev/null +++ b/drivers/video/backlight/tosa_bl.c @@ -0,0 +1,189 @@ +/* + * 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 = data->i2c->dev.platform_data; + + 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 = dev_get_drvdata(&dev->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 __devinit tosa_bl_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct backlight_properties props; + struct tosa_bl_data *data = kzalloc(sizeof(struct tosa_bl_data), GFP_KERNEL); + int ret = 0; + if (!data) + return -ENOMEM; + + data->comadj = sharpsl_param.comadj == -1 ? COMADJ_DEFAULT : sharpsl_param.comadj; + + ret = gpio_request(TOSA_GPIO_BL_C20MA, "backlight"); + if (ret) { + dev_dbg(&data->bl->dev, "Unable to request gpio!\n"); + goto err_gpio_bl; + } + ret = gpio_direction_output(TOSA_GPIO_BL_C20MA, 0); + if (ret) + goto err_gpio_dir; + + 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 = backlight_device_register("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; +err_gpio_dir: + gpio_free(TOSA_GPIO_BL_C20MA); +err_gpio_bl: + kfree(data); + return ret; +} + +static int __devexit tosa_bl_remove(struct i2c_client *client) +{ + struct tosa_bl_data *data = i2c_get_clientdata(client); + + backlight_device_unregister(data->bl); + data->bl = NULL; + + gpio_free(TOSA_GPIO_BL_C20MA); + + kfree(data); + + return 0; +} + +#ifdef CONFIG_PM +static int tosa_bl_suspend(struct i2c_client *client, pm_message_t pm) +{ + struct tosa_bl_data *data = i2c_get_clientdata(client); + + tosa_bl_set_backlight(data, 0); + + return 0; +} + +static int tosa_bl_resume(struct i2c_client *client) +{ + struct tosa_bl_data *data = i2c_get_clientdata(client); + + backlight_update_status(data->bl); + return 0; +} +#else +#define tosa_bl_suspend NULL +#define tosa_bl_resume NULL +#endif + +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, + }, + .probe = tosa_bl_probe, + .remove = __devexit_p(tosa_bl_remove), + .suspend = tosa_bl_suspend, + .resume = tosa_bl_resume, + .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"); + diff --git a/drivers/video/backlight/tosa_lcd.c b/drivers/video/backlight/tosa_lcd.c new file mode 100644 index 00000000..2231aec2 --- /dev/null +++ b/drivers/video/backlight/tosa_lcd.c @@ -0,0 +1,293 @@ +/* + * 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 __devinit tosa_lcd_probe(struct spi_device *spi) +{ + int ret; + struct tosa_lcd_data *data; + + data = kzalloc(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) + goto err_spi; + + data->spi = spi; + dev_set_drvdata(&spi->dev, data); + + ret = gpio_request(TOSA_GPIO_TG_ON, "tg #pwr"); + if (ret < 0) + goto err_gpio_tg; + + mdelay(60); + + ret = gpio_direction_output(TOSA_GPIO_TG_ON, 0); + if (ret < 0) + goto err_gpio_dir; + + mdelay(60); + tosa_lcd_tg_init(data); + + tosa_lcd_tg_on(data); + + data->lcd = lcd_device_register("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); +err_gpio_dir: + gpio_free(TOSA_GPIO_TG_ON); +err_gpio_tg: + dev_set_drvdata(&spi->dev, NULL); +err_spi: + kfree(data); + return ret; +} + +static int __devexit tosa_lcd_remove(struct spi_device *spi) +{ + struct tosa_lcd_data *data = dev_get_drvdata(&spi->dev); + + lcd_device_unregister(data->lcd); + + if (data->i2c) + i2c_unregister_device(data->i2c); + + tosa_lcd_tg_off(data); + + gpio_free(TOSA_GPIO_TG_ON); + dev_set_drvdata(&spi->dev, NULL); + kfree(data); + + return 0; +} + +#ifdef CONFIG_PM +static int tosa_lcd_suspend(struct spi_device *spi, pm_message_t state) +{ + struct tosa_lcd_data *data = dev_get_drvdata(&spi->dev); + + tosa_lcd_tg_off(data); + + return 0; +} + +static int tosa_lcd_resume(struct spi_device *spi) +{ + struct tosa_lcd_data *data = dev_get_drvdata(&spi->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; +} +#else +#define tosa_lcd_suspend NULL +#define tosa_lcd_resume NULL +#endif + +static struct spi_driver tosa_lcd_driver = { + .driver = { + .name = "tosa-lcd", + .owner = THIS_MODULE, + }, + .probe = tosa_lcd_probe, + .remove = __devexit_p(tosa_lcd_remove), + .suspend = tosa_lcd_suspend, + .resume = tosa_lcd_resume, +}; + +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"); diff --git a/drivers/video/backlight/vgg2432a4.c b/drivers/video/backlight/vgg2432a4.c new file mode 100644 index 00000000..b617fae9 --- /dev/null +++ b/drivers/video/backlight/vgg2432a4.c @@ -0,0 +1,270 @@ +/* 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 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 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 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 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 +static int vgg2432a4_suspend(struct spi_device *spi, pm_message_t state) +{ + return ili9320_suspend(dev_get_drvdata(&spi->dev), state); +} + +static int vgg2432a4_resume(struct spi_device *spi) +{ + return ili9320_resume(dev_get_drvdata(&spi->dev)); +} +#else +#define vgg2432a4_suspend NULL +#define vgg2432a4_resume NULL +#endif + +static struct ili9320_client vgg2432a4_client = { + .name = "VGG2432A4", + .init = vgg2432a4_lcd_init, +}; + +/* Device probe */ + +static int __devinit 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 __devexit vgg2432a4_remove(struct spi_device *spi) +{ + return ili9320_remove(dev_get_drvdata(&spi->dev)); +} + +static void vgg2432a4_shutdown(struct spi_device *spi) +{ + ili9320_shutdown(dev_get_drvdata(&spi->dev)); +} + +static struct spi_driver vgg2432a4_driver = { + .driver = { + .name = "VGG2432A4", + .owner = THIS_MODULE, + }, + .probe = vgg2432a4_probe, + .remove = __devexit_p(vgg2432a4_remove), + .shutdown = vgg2432a4_shutdown, + .suspend = vgg2432a4_suspend, + .resume = vgg2432a4_resume, +}; + +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"); diff --git a/drivers/video/backlight/wm831x_bl.c b/drivers/video/backlight/wm831x_bl.c new file mode 100644 index 00000000..5d365deb --- /dev/null +++ b/drivers/video/backlight/wm831x_bl.c @@ -0,0 +1,240 @@ +/* + * 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; + 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 (pdev->dev.parent->platform_data) { + wm831x_pdata = pdev->dev.parent->platform_data; + 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; + + props.type = BACKLIGHT_RAW; + props.max_brightness = max_isel; + bl = backlight_device_register("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 int wm831x_backlight_remove(struct platform_device *pdev) +{ + struct backlight_device *bl = platform_get_drvdata(pdev); + + backlight_device_unregister(bl); + return 0; +} + +static struct platform_driver wm831x_backlight_driver = { + .driver = { + .name = "wm831x-backlight", + .owner = THIS_MODULE, + }, + .probe = wm831x_backlight_probe, + .remove = wm831x_backlight_remove, +}; + +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"); diff --git a/drivers/video/backlight/wmt_bl.c b/drivers/video/backlight/wmt_bl.c new file mode 100755 index 00000000..97087216 --- /dev/null +++ b/drivers/video/backlight/wmt_bl.c @@ -0,0 +1,191 @@ +/* + * for backlight control on wmt platform, base on pwm_bl.c + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/pwm_backlight.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/leds.h> + +#include <mach/wmt_env.h> +#include "wmt_bl.h" + +#define LTH_BRIGHTNESS 0 +#define DFT_BRIGHTNESS 180 +#define DFT_PWM0_FREQ 4 /* kHz */ + +static struct { + int gpio; + int active; +} power = { -1, 0, }; + +static int backlight_delay = 0; + +static struct led_trigger *logo_led; +static BLOCKING_NOTIFIER_HEAD(bl_chain_head); + +int register_bl_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&bl_chain_head, nb); +} +EXPORT_SYMBOL_GPL(register_bl_notifier); + +int unregister_bl_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&bl_chain_head, nb); +} +EXPORT_SYMBOL_GPL(unregister_bl_notifier); + +static inline int bl_notifier_call_chain(unsigned long val) +{ + int ret = blocking_notifier_call_chain(&bl_chain_head, val, NULL); + return notifier_to_errno(ret); +} + +static void backlight_on(bool on) +{ + static int old_state = -1; + + if (!gpio_is_valid(power.gpio)) + return; + + if (old_state != on) { + if (old_state == false) { + /* delay 100 ms between 'lcd power on' and 'backlight + * power on' at resume sequence. */ + msleep(100); + /* wmt.backlight.delay */ + msleep(backlight_delay); + } + gpio_direction_output(power.gpio, on ? power.active : !power.active); + led_trigger_event(logo_led, on ? LED_FULL : LED_OFF); + + if (!on) { + /* + * delay 100 ms between 'backlight power off' and 'lcd power off' + * at suspend sequence to avoid to lcd flicker + */ + msleep(100); + } + + old_state = on; + bl_notifier_call_chain(on ? BL_OPEN : BL_CLOSE); + } +} + +static int wmt_bl_notify(struct device *dev, int brightness) +{ + return brightness; +} + +static void wmt_bl_notify_after(struct device *dev, int brightness) +{ + backlight_on(brightness != 0); +} + +#define KHZ2PICOS(hz) (1000000000/(hz)) +static struct platform_pwm_backlight_data wm8880_pwmbl_data = { + .pwm_id = 0, + .invert = 1, + .lth_brightness = LTH_BRIGHTNESS, /* low threshold */ + .hth_brightness = 256, /* high threshold */ + .max_brightness = 256, + .dft_brightness = DFT_BRIGHTNESS, + .pwm_period_ns = KHZ2PICOS(DFT_PWM0_FREQ*1000), /* revisit when clocks are implemented */ + .notify = wmt_bl_notify, + .notify_after = wmt_bl_notify_after, +}; + +struct platform_device wm8880_device_pwmbl = { + .name = "pwm-backlight", + .id = 0, + .dev = { + .platform_data = &wm8880_pwmbl_data, + }, +}; + +static int parse_backlight_param(void) +{ + static char env[] = "wmt.backlight.param"; + struct platform_pwm_backlight_data *pdata = &wm8880_pwmbl_data; + uint8_t buf[64]; + size_t l = sizeof(buf); + int pwm_freq = DFT_PWM0_FREQ; + int rc; + + if (wmt_getsyspara(env, buf, &l)) { + pr_err("please set %s\n", env); + return -EINVAL; + } + + /* wmt.backlight.param + * <pwmid>:<invert>:<gpio>:<active>: + * <lth_brightness>:<hth_brightness>:<pwm0freq>:<dft_brightness> + */ + rc = sscanf(buf, "%d:%d:%d:%d:%d:%d:%d:%d", + &pdata->pwm_id, + &pdata->invert, + &power.gpio, &power.active, + &pdata->lth_brightness, + &pdata->hth_brightness, + &pwm_freq, + &pdata->dft_brightness); + + if ((rc < 4)) { + pr_err("bad env %s %s\n", env, buf); + return -EINVAL; + } + if ((pdata->hth_brightness > pdata->max_brightness) || + (pdata->lth_brightness > pdata->hth_brightness)) + pdata->hth_brightness = pdata->max_brightness; + + pdata->pwm_period_ns = KHZ2PICOS(pwm_freq * 1000); + + pr_info("backlight param: pwm%d(%dkHz), invert %d, brightness %d(%d~%d) ", + pdata->pwm_id, pwm_freq, pdata->invert, pdata->dft_brightness, + pdata->lth_brightness, pdata->hth_brightness); + + if (gpio_is_valid(power.gpio)) { + if (gpio_request(power.gpio, "pwm_bl switch")) { + pr_warning("gpio request %d failed\n", power.gpio); + power.gpio = -1; + } + pr_info("gpio%d (active %d)\n", power.gpio, power.active); + } else + pr_info("no gpio control\n"); + + /* parse wmt.backlight.delay */ + l = sizeof(buf); + if (wmt_getsyspara("wmt.backlight.delay", buf, &l) == 0) { + sscanf(buf, "%d", &backlight_delay); + } + pr_info("backlight delay: %dms\n", backlight_delay); + + return 0; +} + +static int __init wmt_bl_init(void) +{ + int rc; + + rc = parse_backlight_param(); + if (rc) + return rc; + + led_trigger_register_simple("logoled", &logo_led); + + return platform_device_register(&wm8880_device_pwmbl); +} + +static void __exit wmt_bl_exit(void) +{ + led_trigger_unregister_simple(logo_led); + platform_device_unregister(&wm8880_device_pwmbl); +} + +module_init(wmt_bl_init); +module_exit(wmt_bl_exit); + diff --git a/drivers/video/backlight/wmt_bl.h b/drivers/video/backlight/wmt_bl.h new file mode 100755 index 00000000..a25117b9 --- /dev/null +++ b/drivers/video/backlight/wmt_bl.h @@ -0,0 +1,12 @@ +#ifndef _LINUX_WMT_BL_H
+#define _LINUX_WMT_BL_H
+
+
+/* Backlight close or open events */
+#define BL_CLOSE 0x0001 /* Close the backlight */
+#define BL_OPEN 0x0002 /* Open the backlight */
+
+//extern int bl_notifier_call_chain(unsigned long val);
+
+#endif
+
|