diff options
-rw-r--r-- | arch/arm/configs/imx37_3stack_defconfig | 8 | ||||
-rw-r--r-- | arch/arm/mach-mx37/mx37_3stack_pmic_wm8350.c | 40 | ||||
-rw-r--r-- | drivers/regulator/wm8350/reg-wm8350.c | 4 | ||||
-rw-r--r-- | drivers/video/backlight/Kconfig | 5 | ||||
-rw-r--r-- | drivers/video/backlight/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/backlight/wm8350_bl.c | 301 | ||||
-rw-r--r-- | include/linux/regulator/wm8350/wm8350-pmic.h | 3 |
7 files changed, 357 insertions, 5 deletions
diff --git a/arch/arm/configs/imx37_3stack_defconfig b/arch/arm/configs/imx37_3stack_defconfig index 426a8d6e95dc..da7e7269bdb5 100644 --- a/arch/arm/configs/imx37_3stack_defconfig +++ b/arch/arm/configs/imx37_3stack_defconfig @@ -781,7 +781,13 @@ CONFIG_SSB_POSSIBLE=y # CONFIG_VGASTATE is not set # CONFIG_VIDEO_OUTPUT_CONTROL is not set # CONFIG_FB is not set -# CONFIG_BACKLIGHT_LCD_SUPPORT is not set +CONFIG_BACKLIGHT_LCD_SUPPORT=y +CONFIG_LCD_CLASS_DEVICE=y +# CONFIG_LCD_LTV350QV is not set +CONFIG_BACKLIGHT_CLASS_DEVICE=y +# CONFIG_BACKLIGHT_CORGI is not set +CONFIG_BACKLIGHT_MXC=y +CONFIG_BACKLIGHT_WM8350=y # # Display device support diff --git a/arch/arm/mach-mx37/mx37_3stack_pmic_wm8350.c b/arch/arm/mach-mx37/mx37_3stack_pmic_wm8350.c index 0b625a4a59c0..839a2b710de9 100644 --- a/arch/arm/mach-mx37/mx37_3stack_pmic_wm8350.c +++ b/arch/arm/mach-mx37/mx37_3stack_pmic_wm8350.c @@ -209,7 +209,7 @@ static int config_hibernate(struct wm8350 *wm8350) struct regulation_constraints led_regulation_constraints = { .min_uA = 0, - .max_uA = 19727, + .max_uA = 230000, .valid_ops_mask = REGULATOR_CHANGE_CURRENT, }; @@ -227,6 +227,43 @@ static void set_regulator_constraints(struct wm8350 *wm8350) &led_regulation_constraints); } +static void wm8350_nop_release(struct device *dev) +{ + /* Nothing */ +} + +struct wm8350_bl_platform_data wm8350_bl_data = { + .isink = WM8350_ISINK_A, + .dcdc = WM8350_DCDC_5, + .voltage_ramp = WM8350_DC5_RMP_20V, + .retries = 5, + .max_brightness = 63, + .power = FB_BLANK_UNBLANK, + .brightness = 50, +}; + +static struct platform_device mxc_wm8350_devices[] = { + { + .name = "wm8350-bl", + .id = 2, + .dev = { + .release = wm8350_nop_release, + .platform_data = &wm8350_bl_data, + }, + }, +}; + +static inline void mxc_init_wm8350(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mxc_wm8350_devices); i++) { + if (platform_device_register(&mxc_wm8350_devices[i]) < 0) + dev_err(&mxc_wm8350_devices[i].dev, + "Unable to register WM8350 device\n"); + } +} + int wm8350_init(struct wm8350 *wm8350) { int ret = 0; @@ -239,6 +276,7 @@ int wm8350_init(struct wm8350 *wm8350) wm8350_device_register_wdg(wm8350); wm8350_device_register_power(wm8350); #endif + mxc_init_wm8350(); /* register sound */ printk("Registering imx37_snd_device"); diff --git a/drivers/regulator/wm8350/reg-wm8350.c b/drivers/regulator/wm8350/reg-wm8350.c index 32059a01a352..ffbec586610e 100644 --- a/drivers/regulator/wm8350/reg-wm8350.c +++ b/drivers/regulator/wm8350/reg-wm8350.c @@ -74,13 +74,13 @@ static int wm8350_isink_set_current(struct regulator *reg, int uA) val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_A) & ~WM8350_CS1_ISEL_MASK; wm8350_reg_write(wm8350, WM8350_CURRENT_SINK_DRIVER_A, val | - get_isink_val(uA)); + WM8350_CS1_ENABLE | get_isink_val(uA)); break; case WM8350_ISINK_B: val = wm8350_reg_read(wm8350, WM8350_CURRENT_SINK_DRIVER_B) & ~WM8350_CS1_ISEL_MASK; wm8350_reg_write(wm8350, WM8350_CURRENT_SINK_DRIVER_B, val | - get_isink_val(uA)); + WM8350_CS2_ENABLE | get_isink_val(uA)); break; default: return -EINVAL; diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index d00e50da4427..cf9036782dbb 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -113,3 +113,8 @@ config BACKLIGHT_MXC_PMIC tristate "PMIC Backlight Driver" depends on BACKLIGHT_MXC && MXC_MC13783_LIGHT && MXC_MC13783_POWER default y + +config BACKLIGHT_WM8350 + tristate "WM8350 Backlight Driver" + depends on BACKLIGHT_MXC && REGULATOR_WM8350 + default y diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 9280ca44c38d..1dda3908eff6 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o obj-$(CONFIG_BACKLIGHT_MXC_LCDC) += mxc_lcdc_bl.o obj-$(CONFIG_BACKLIGHT_MXC_IPU) += mxc_ipu_bl.o obj-$(CONFIG_BACKLIGHT_MXC_PMIC) += mxc_pmic_bl.o +obj-$(CONFIG_BACKLIGHT_WM8350) += wm8350_bl.o diff --git a/drivers/video/backlight/wm8350_bl.c b/drivers/video/backlight/wm8350_bl.c new file mode 100644 index 000000000000..152aea7f9f24 --- /dev/null +++ b/drivers/video/backlight/wm8350_bl.c @@ -0,0 +1,301 @@ +/* + * Backlight driver for DCDC2 on i.MX32ADS board + * + * Copyright(C) 2007 Wolfson Microelectronics 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/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/mutex.h> +#include <linux/fb.h> +#include <linux/platform_device.h> +#include <linux/backlight.h> +#include <linux/regulator/regulator.h> +#include <linux/regulator/wm8350/wm8350-bus.h> + +struct wm8350_backlight { + struct backlight_properties props; + struct backlight_device *device; + struct regulator *dcdc; + struct regulator *isink; + struct notifier_block notifier; + struct work_struct work; + struct mutex mutex; + int intensity; + int suspend; + int retries; +}; + +/* hundredths of uA, 405 = 4.05 uA */ +static const int intensity_huA[] = { + 405, 482, 573, 681, 810, 963, 1146, 1362, 1620, 1927, 2291, 2725, + 3240, 3853, 4582, 5449, 6480, 7706, 9164, 10898, 12960, 15412, 18328, + 21796, 25920, 30824, 36656, 43592, 51840, 61648, 73313, 87184, + 103680, 123297, 146626, 174368, 207360, 246594, 293251, 348737, + 414720, 493188, 586503, 697473, 829440, 986376, 1173005, 1394946, + 1658880, 1972752, 2346011, 2789892, 3317760, 3945504, 4692021, + 5579785, 6635520, 7891008, 9384042, 11159570, 13271040, 15782015, + 18768085, 22319140, +}; + +static void bl_work(struct work_struct *work) +{ + struct wm8350_backlight *bl = + container_of(work, struct wm8350_backlight, work); + struct regulator *isink = bl->isink; + + mutex_lock(&bl->mutex); + if (bl->intensity >= 0 && + bl->intensity < ARRAY_SIZE(intensity_huA)) { + bl->retries = 0; + regulator_set_current(isink, + intensity_huA[bl->intensity] / 100); + } else + printk(KERN_ERR "wm8350: Backlight intensity error\n"); + mutex_unlock(&bl->mutex); +} + +static int wm8350_bl_notifier(struct notifier_block *self, + unsigned long event, void *data) +{ + struct wm8350_backlight *bl = + container_of(self, struct wm8350_backlight, notifier); + struct regulator *isink = bl->isink; + + if (event & REGULATOR_EVENT_UNDER_VOLTAGE) + printk(KERN_ERR "wm8350: BL DCDC undervoltage\n"); + if (event & REGULATOR_EVENT_REGULATION_OUT) + printk(KERN_ERR "wm8350: BL ISINK out of regulation\n"); + + mutex_lock(&bl->mutex); + if (bl->retries) { + bl->retries--; + regulator_disable(isink); + regulator_set_current(isink, bl->intensity); + regulator_enable(isink); + } else { + printk(KERN_ERR + "wm8350: BL regulation retry failure - disable\n"); + bl->intensity = 0; + regulator_disable(isink); + } + mutex_unlock(&bl->mutex); + return 0; +} + +static int wm8350_bl_send_intensity(struct backlight_device *bd) +{ + struct wm8350_backlight *bl = + (struct wm8350_backlight *)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 (bl->suspend) + intensity = 0; + + mutex_lock(&bl->mutex); + bl->intensity = intensity; + mutex_unlock(&bl->mutex); + schedule_work(&bl->work); + + return 0; +} + +#ifdef CONFIG_PM +static int wm8350_bl_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct wm8350_backlight *bl = + (struct wm8350_backlight *)platform_get_drvdata(pdev); + + bl->suspend = 1; + backlight_update_status(bl->device); + return 0; +} + +static int wm8350_bl_resume(struct platform_device *pdev) +{ + struct wm8350_backlight *bl = + (struct wm8350_backlight *)platform_get_drvdata(pdev); + + bl->suspend = 0; + backlight_update_status(bl->device); + return 0; +} +#else +#define wm8350_bl_suspend NULL +#define wm8350_bl_resume NULL +#endif + +static int wm8350_bl_get_intensity(struct backlight_device *bd) +{ + struct wm8350_backlight *bl = + (struct wm8350_backlight *)dev_get_drvdata(&bd->dev); + return bl->intensity; +} + +static struct backlight_ops wm8350_bl_ops = { + .get_brightness = wm8350_bl_get_intensity, + .update_status = wm8350_bl_send_intensity, +}; + +static int wm8350_bl_probe(struct platform_device *pdev) +{ + struct regulator *isink, *dcdc; + struct wm8350_backlight *bl; + struct wm8350_bl_platform_data *pdata = pdev->dev.platform_data; + struct wm8350_pmic *pmic; + int ret; + + if (pdata == NULL) { + printk(KERN_ERR "%s: no platform data\n", __func__); + return -ENODEV; + } + + if (pdata->isink != WM8350_ISINK_A && pdata->isink != WM8350_ISINK_B) { + printk(KERN_ERR "%s: invalid ISINK\n", __func__); + return -EINVAL; + } + if (pdata->dcdc != WM8350_DCDC_2 && pdata->dcdc != WM8350_DCDC_5) { + printk(KERN_ERR "%s: invalid DCDC\n", __func__); + return -EINVAL; + } + + printk(KERN_INFO "wm8350: backlight using %s and %s\n", + pdata->isink == WM8350_ISINK_A ? "ISINKA" : "ISINKB", + pdata->dcdc == WM8350_DCDC_2 ? "DCDC2" : "DCDC5"); + + isink = regulator_get(&pdev->dev, + pdata->isink == WM8350_ISINK_A ? "ISINKA" : "ISINKB"); + if (IS_ERR(isink) || isink == NULL) { + printk(KERN_ERR "%s: cant get ISINK\n", __func__); + return PTR_ERR(isink); + } + + dcdc = regulator_get(&pdev->dev, + pdata->dcdc == WM8350_DCDC_2 ? "DCDC2" : "DCDC5"); + if (IS_ERR(dcdc) || dcdc == NULL) { + printk(KERN_ERR "%s: cant get DCDC\n", __func__); + regulator_put(isink, &pdev->dev); + return PTR_ERR(dcdc); + } + + bl = kzalloc(sizeof(*bl), GFP_KERNEL); + if (bl == NULL) { + regulator_put(isink, &pdev->dev); + regulator_put(dcdc, &pdev->dev); + return -ENOMEM; + } + + mutex_init(&bl->mutex); + INIT_WORK(&bl->work, bl_work); + bl->props.max_brightness = pdata->max_brightness; + bl->props.power = pdata->power; + bl->props.brightness = pdata->brightness; + bl->retries = pdata->retries; + bl->dcdc = dcdc; + bl->isink = isink; + platform_set_drvdata(pdev, bl); + pmic = regulator_get_drvdata(bl->isink); + + bl->device = backlight_device_register(pdev->dev.bus_id, &pdev->dev, + bl, &wm8350_bl_ops); + if (IS_ERR(bl->device)) { + ret = PTR_ERR(bl->device); + regulator_put(dcdc, &pdev->dev); + regulator_put(isink, &pdev->dev); + kfree(bl); + return ret; + } + + bl->notifier.notifier_call = wm8350_bl_notifier; + regulator_register_client(dcdc, &bl->notifier); + regulator_register_client(isink, &bl->notifier); + bl->device->props = bl->props; + + /* WM8350 ISINK & DCDC setup */ + if (pdata->isink == WM8350_ISINK_A) + pmic->isink_A_dcdc = pdata->dcdc; + else + pmic->isink_B_dcdc = pdata->dcdc; + + regulator_set_current(isink, 20000); + + wm8350_isink_set_flash(pmic, pdata->isink, + WM8350_ISINK_FLASH_DISABLE, + WM8350_ISINK_FLASH_TRIG_BIT, + WM8350_ISINK_FLASH_DUR_32MS, + WM8350_ISINK_FLASH_ON_1_00S, + WM8350_ISINK_FLASH_OFF_1_00S, + WM8350_ISINK_FLASH_MODE_EN); + + wm8350_dcdc25_set_mode(pmic, pdata->dcdc, + WM8350_ISINK_MODE_BOOST, WM8350_ISINK_ILIM_NORMAL, + pdata->voltage_ramp, pdata->isink == WM8350_ISINK_A ? + WM8350_DC5_FBSRC_ISINKA : WM8350_DC5_FBSRC_ISINKB); + + wm8350_dcdc_set_slot(pmic, pdata->dcdc, 15, 0, + pdata->dcdc == WM8350_DCDC_2 ? + WM8350_DC2_ERRACT_SHUTDOWN_CONV : WM8350_DC5_ERRACT_NONE); + + regulator_enable(isink); + backlight_update_status(bl->device); + return 0; +} + +static int wm8350_bl_remove(struct platform_device *pdev) +{ + struct wm8350_backlight *bl = + (struct wm8350_backlight *)platform_get_drvdata(pdev); + struct regulator *isink = bl->isink, *dcdc = bl->dcdc; + + bl->intensity = 0; + backlight_update_status(bl->device); + schedule_work(&bl->work); + flush_scheduled_work(); + backlight_device_unregister(bl->device); + + regulator_set_current(isink, 0); + regulator_disable(isink); + regulator_unregister_client(isink, &bl->notifier); + regulator_unregister_client(dcdc, &bl->notifier); + regulator_put(isink, &pdev->dev); + regulator_put(dcdc, &pdev->dev); + return 0; +} + +struct platform_driver imx32ads_backlight_driver = { + .driver = { + .name = "wm8350-bl", + .owner = THIS_MODULE, + }, + .probe = wm8350_bl_probe, + .remove = wm8350_bl_remove, + .suspend = wm8350_bl_suspend, + .resume = wm8350_bl_resume, +}; + +static int __devinit imx32ads_backlight_init(void) +{ + return platform_driver_register(&imx32ads_backlight_driver); +} + +static void imx32ads_backlight_exit(void) +{ + platform_driver_unregister(&imx32ads_backlight_driver); +} + +device_initcall_sync(imx32ads_backlight_init); +module_exit(imx32ads_backlight_exit); + +MODULE_AUTHOR("Liam Girdwood <lg@opensource.wolfsonmicro.com>"); +MODULE_DESCRIPTION("WM8350 Backlight driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/regulator/wm8350/wm8350-pmic.h b/include/linux/regulator/wm8350/wm8350-pmic.h index 7e767eff260b..7054c75b46bb 100644 --- a/include/linux/regulator/wm8350/wm8350-pmic.h +++ b/include/linux/regulator/wm8350/wm8350-pmic.h @@ -84,6 +84,7 @@ #define WM8350_CS1_ISEL_SHIFT 0 /* CS1_ISEL - [5:0] */ /* Bit values for R172 (0xAC) */ +#define WM8350_CS1_ENABLE 0x8000 /* CS1_ENA */ #define WM8350_CS1_HIB_MODE_DISABLE 0 /* Disable current sink in hibernate */ #define WM8350_CS1_HIB_MODE_LEAVE 1 /* Leave current sink as-is in hibernate */ @@ -102,7 +103,7 @@ /* * R174 (0xAE) - Current Sink Driver B */ -// lg #define WM8350_CS2_ENA 0x8000 /* CS2_ENA */ +#define WM8350_CS2_ENABLE 0x8000 /* CS2_ENA */ #define WM8350_CS2_HIB_MODE 0x1000 /* CS2_HIB_MODE */ #define WM8350_CS2_ISEL_MASK 0x003F /* CS2_ISEL - [5:0] */ |