summaryrefslogtreecommitdiff
path: root/drivers/video/backlight/sprd_pwm_bl.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/backlight/sprd_pwm_bl.c')
-rw-r--r--drivers/video/backlight/sprd_pwm_bl.c442
1 files changed, 442 insertions, 0 deletions
diff --git a/drivers/video/backlight/sprd_pwm_bl.c b/drivers/video/backlight/sprd_pwm_bl.c
new file mode 100644
index 00000000..1abb9f6a
--- /dev/null
+++ b/drivers/video/backlight/sprd_pwm_bl.c
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2012 Spreadtrum Communications Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/backlight.h>
+#include <linux/fb.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/earlysuspend.h>
+#include <linux/gpio.h>
+#include <linux/err.h>
+// HEAD
+
+#include <soc/sprd/hardware.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/adi.h>
+#include <soc/sprd/arch_misc.h>
+//=======
+// df3459c... Bug #365596 64bit-32bit compatible
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include <linux/sprd_pwm_bl.h>
+
+//#define SPRD_PWM_BL_DBG
+#ifdef SPRD_PWM_BL_DBG
+#define ENTER printk(KERN_INFO "[SPRD_PWM_BL_DBG] func: %s line: %04d\n", __func__, __LINE__);
+#define PRINT_DBG(x...) printk(KERN_INFO "[SPRD_PWM_BL_DBG] " x)
+#define PRINT_INFO(x...) printk(KERN_INFO "[SPRD_PWM_BL_INFO] " x)
+#define PRINT_WARN(x...) printk(KERN_INFO "[SPRD_PWM_BL_WARN] " x)
+#define PRINT_ERR(format,x...) printk(KERN_ERR "[SPRD_PWM_BL_ERR] func: %s line: %04d info: " format, __func__, __LINE__, ## x)
+#else
+#define ENTER
+#define PRINT_DBG(x...)
+#define PRINT_INFO(x...) printk(KERN_INFO "[SPRD_PWM_BL_INFO] " x)
+#define PRINT_WARN(x...) printk(KERN_INFO "[SPRD_PWM_BL_WARN] " x)
+#define PRINT_ERR(format,x...) printk(KERN_ERR "[SPRD_PWM_BL_ERR] func: %s line: %04d info: " format, __func__, __LINE__, ## x)
+#endif
+
+/* register definitions */
+#define PWM_PRESCALE (0x0000)
+#define PWM_CNT (0x0004)
+#define PWM_TONE_DIV (0x0008)
+#define PWM_PAT_LOW (0x000C)
+#define PWM_PAT_HIG (0x0010)
+
+#define PWM_ENABLE (1 << 8)
+#define PWM_DUTY (1 << 8)
+#define PWM_MOD 0
+#define PWM_DIV 0x190
+#define PWM_LOW 0xffff
+#define PWM_HIG 0xffff
+#define PWM_SCALE 1
+#define PWM2_SCALE 0x0
+#define PWM_REG_MSK 0xffff
+#define PWM_MOD_MAX 0xff
+#define PWM_DUTY_MAX 0x7f
+
+struct sprd_pwm_bl_device_data {
+ int suspend;
+ int brightness_max;
+ int brightness_min;
+ int pwm_index;
+ int gpio_ctrl_pin;
+ int gpio_active_level;
+ struct clk *clk;
+ struct backlight_device *bldev;
+ struct early_suspend sprd_early_suspend_desc;
+};
+
+static struct sprd_pwm_bl_device_data sprd_pwm_bl = {
+ .brightness_max = 0,
+ .brightness_min = 0,
+ .pwm_index = 0,
+ .gpio_ctrl_pin = -1,
+ .gpio_active_level = 0,
+};
+
+unsigned long SPRD_PWM_BASE_ADDR;
+
+static void pwm_clk_en(int enable)
+{
+ unsigned long spin_lock_flags;
+ static int current_state = 0;
+
+ if (1 == enable) {
+ if (0 == current_state) {
+ clk_prepare_enable(sprd_pwm_bl.clk);
+ current_state = 1;
+ }
+ } else {
+ if (1 == current_state) {
+ clk_disable_unprepare(sprd_pwm_bl.clk);
+ current_state = 0;
+ }
+ }
+
+ return;
+}
+
+static inline uint32_t pwm_read(int index, uint32_t reg)
+{
+ //this is the D-die PWM controller, here we use PWM2(index=3) for external backlight control.
+ return __raw_readl(SPRD_PWM_BASE_ADDR + index * 0x20 + reg);
+}
+
+static void pwm_write(int index, uint32_t value, uint32_t reg)
+{
+ //this is the D-die PWM controller, here we use PWM2(index=3) for external backlight control.
+ __raw_writel(value, SPRD_PWM_BASE_ADDR + index * 0x20 + reg);
+}
+
+//sprd used PWM2(mapped to gpio190) for external backlight control.
+static int sprd_pwm_bl_update_status(struct backlight_device *bldev)
+{
+ u32 led_level;
+
+ if ((bldev->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK)) ||
+ bldev->props.power != FB_BLANK_UNBLANK ||
+ sprd_pwm_bl.suspend ||
+ bldev->props.brightness == 0) {
+ /* disable backlight */
+ pwm_write(sprd_pwm_bl.pwm_index, 0, PWM_PRESCALE);
+ pwm_clk_en(0);
+ PRINT_INFO("disabled\n");
+ } else {
+ led_level = bldev->props.brightness & PWM_MOD_MAX;
+ //led_level = (led_level * (PWM_DUTY_MAX+1) / (PWM_MOD_MAX+1)) + 10;
+ led_level = led_level * sprd_pwm_bl.brightness_max / 255;
+ if (led_level <= sprd_pwm_bl.brightness_min)
+ led_level = sprd_pwm_bl.brightness_min;
+ PRINT_INFO("brightness = %d, led_level = %d\n", bldev->props.brightness, led_level);
+ pwm_clk_en(1);
+ pwm_write(sprd_pwm_bl.pwm_index, PWM2_SCALE, PWM_PRESCALE);
+ pwm_write(sprd_pwm_bl.pwm_index, (led_level << 8) | PWM_MOD_MAX, PWM_CNT);
+ pwm_write(sprd_pwm_bl.pwm_index, PWM_REG_MSK, PWM_PAT_LOW);
+ pwm_write(sprd_pwm_bl.pwm_index, PWM_REG_MSK, PWM_PAT_HIG);
+ pwm_write(sprd_pwm_bl.pwm_index, PWM_ENABLE, PWM_PRESCALE);
+ }
+ return 0;
+}
+
+static int sprd_pwm_bl_get_brightness(struct backlight_device *bldev)
+{
+ return (pwm_read(sprd_pwm_bl.pwm_index, PWM_CNT) >> 8) & PWM_MOD_MAX;
+}
+
+static const struct backlight_ops sprd_backlight_pwm_ops = {
+ .update_status = sprd_pwm_bl_update_status,
+ .get_brightness = sprd_pwm_bl_get_brightness,
+};
+
+void set_gpio_ctrll_pin_state(int on)
+{
+ on = on ? sprd_pwm_bl.gpio_active_level : !sprd_pwm_bl.gpio_active_level;
+ gpio_direction_output(sprd_pwm_bl.gpio_ctrl_pin,on);
+ PRINT_INFO("set_gpio_ctrll_pin_state=%d\n", on);
+}
+
+#ifdef CONFIG_EARLYSUSPEND
+static void sprd_backlight_earlysuspend(struct early_suspend *h)
+{
+ if(-1 != sprd_pwm_bl.gpio_ctrl_pin)
+ set_gpio_ctrll_pin_state(0);
+
+ sprd_pwm_bl.suspend = 1;
+ sprd_pwm_bl.bldev->ops->update_status(sprd_pwm_bl.bldev);
+ PRINT_INFO("early suspend\n");
+}
+
+static void sprd_backlight_lateresume(struct early_suspend *h)
+{
+ if(-1 != sprd_pwm_bl.gpio_ctrl_pin)
+ set_gpio_ctrll_pin_state(1);
+
+ sprd_pwm_bl.suspend = 0;
+ sprd_pwm_bl.bldev->ops->update_status(sprd_pwm_bl.bldev);
+ PRINT_INFO("late resume\n");
+}
+#endif
+
+#ifdef CONFIG_OF
+static struct sprd_pwm_bl_platform_data *sprd_pwm_bl_parse_dt(struct platform_device *pdev)
+{
+ int ret = -1;
+ struct device_node *np = pdev->dev.of_node;
+ struct sprd_pwm_bl_platform_data* pdata = NULL;
+#if !(defined(CONFIG_ARCH_SCX20L)||defined(CONFIG_ARCH_SCX20))
+ struct resource res;
+#else
+ struct resource *res;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (NULL == res) {
+ PRINT_ERR("cannot find IO resource\n");
+ goto fail;
+ }
+#endif
+ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ PRINT_ERR("failed to allocate pdata!\n");
+ return NULL;
+ }
+
+ ret = of_property_read_u32(np, "pwm_index", &pdata->pwm_index);
+ if(ret) {
+ PRINT_ERR("failed to get pwm_index\n");
+ goto fail;
+ }
+
+ ret = of_property_read_u32(np, "brightness_max", &pdata->brightness_max);
+ if(ret) {
+ pdata->brightness_max = 255;
+ PRINT_WARN("failed to get brightness_max\n");
+ }
+
+ ret = of_property_read_u32(np, "brightness_min", &pdata->brightness_min);
+ if(ret) {
+ pdata->brightness_min = 0;
+ PRINT_WARN("failed to get brightness_min\n");
+ }
+
+ ret = of_property_read_u32(np, "gpio_ctrl_pin", &pdata->gpio_ctrl_pin);
+ if(ret) {
+ pdata->gpio_ctrl_pin = -1;
+ PRINT_WARN("failed to get gpio_ctrl_pin\n");
+ }
+
+ ret = of_property_read_u32(np, "gpio_active_level", &pdata->gpio_active_level);
+ if(ret) {
+ pdata->gpio_active_level = 0;
+ PRINT_WARN("failed to get gpio_active_level\n");
+ }
+#if !(defined(CONFIG_ARCH_SCX20L)||defined(CONFIG_ARCH_SCX20))
+ ret = of_address_to_resource(np,0,&res);
+ if(ret){
+ PRINT_ERR("get dts addr-data failed!\n");
+ }
+ SPRD_PWM_BASE_ADDR = (unsigned long)ioremap_nocache(res.start,resource_size(&res));
+ PRINT_INFO("pwm-paddr = 0x%08x\n",res.start);
+#else
+ SPRD_PWM_BASE_ADDR = (unsigned long)ioremap_nocache(res->start,resource_size(res));
+ PRINT_INFO("pwm-paddr = 0x%08x\n",res->start);
+#endif
+ PRINT_INFO("pwm-vaddr = 0x%lx\n",SPRD_PWM_BASE_ADDR);
+
+ return pdata;
+fail:
+ kfree(pdata);
+ return NULL;
+}
+#endif
+
+static int sprd_backlight_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct backlight_properties props;
+ struct backlight_device *bldev;
+ struct clk* ext_26m = NULL;
+ char pwm_clk_name[32];
+ struct sprd_pwm_bl_platform_data* pdata = NULL;
+ int ret = -1;
+
+#ifdef CONFIG_OF
+ struct device_node *np = dev->of_node;
+
+ if(np) {
+ pdata = sprd_pwm_bl_parse_dt(pdev);
+ if (pdata == NULL) {
+ PRINT_ERR("get dts data failed!\n");
+ return -ENODEV;
+ }
+ } else {
+ PRINT_ERR("dev.of_node is NULL!\n");
+ return -ENODEV;
+ }
+
+#else
+ pdata = pdev->dev.platform_data;
+ if (pdata == NULL) {
+ PRINT_ERR("No platform data!\n");
+ return -ENODEV;
+ }
+#endif
+
+ if(pdata->gpio_ctrl_pin > 0) {
+ sprd_pwm_bl.gpio_ctrl_pin = pdata->gpio_ctrl_pin;
+ sprd_pwm_bl.gpio_active_level= pdata->gpio_active_level;
+ ret = gpio_request(sprd_pwm_bl.gpio_ctrl_pin, "sprd_pwm_bl_gpio_ctrl_pin");
+ if (ret) {
+ PRINT_ERR("request gpio_ctrl_pin error!\n");
+ return -EIO;
+ }
+ set_gpio_ctrll_pin_state(1);
+ } else {
+ sprd_pwm_bl.gpio_ctrl_pin = -1;
+ }
+ PRINT_INFO("gpio_ctrl_pin=%d\n", sprd_pwm_bl.gpio_ctrl_pin);
+
+ sprd_pwm_bl.brightness_max= pdata->brightness_max;
+ if (sprd_pwm_bl.brightness_max >= 255)
+ sprd_pwm_bl.brightness_max = 170;/*use default value*/
+ sprd_pwm_bl.brightness_min= pdata->brightness_min;
+ if (sprd_pwm_bl.brightness_min <= 0)
+ sprd_pwm_bl.brightness_min = 8;/*use default value*/
+ sprd_pwm_bl.pwm_index = pdata->pwm_index;
+
+ /*fixme, the pwm's clk name must like this:clk_pwmx*/
+ sprintf(pwm_clk_name, "%s%d", "clk_pwm", sprd_pwm_bl.pwm_index);
+ sprd_pwm_bl.clk = clk_get(&pdev->dev, pwm_clk_name);
+ if (IS_ERR(sprd_pwm_bl.clk)) {
+ PRINT_ERR("get pwm's clk failed!\n");
+ return -ENODEV;
+ }
+
+ ext_26m = clk_get(NULL, "ext_26m");
+ if (IS_ERR(ext_26m)) {
+ PRINT_ERR("get pwm's ext_26m failed!\n");
+ return -ENODEV;
+ }
+
+ clk_set_parent(sprd_pwm_bl.clk,ext_26m);
+
+ PRINT_INFO("PWM%d is used for brightness control (external backlight controller)\n", sprd_pwm_bl.pwm_index);
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.max_brightness = PWM_MOD_MAX;
+ props.type = BACKLIGHT_RAW;
+ /*the default brightness = 1/2 max brightness */
+ props.brightness = PWM_MOD_MAX >> 1;
+ props.power = FB_BLANK_UNBLANK;
+
+ bldev = backlight_device_register(
+ "sprd_backlight", &pdev->dev,
+ &sprd_pwm_bl, &sprd_backlight_pwm_ops, &props);
+
+ if (IS_ERR(bldev)) {
+ PRINT_ERR("regist backlight device failed!\n");
+ return -ENOMEM;
+ }
+
+ sprd_pwm_bl.bldev = bldev;
+ platform_set_drvdata(pdev, bldev);
+ bldev->ops->update_status(bldev);
+
+#ifdef CONFIG_EARLYSUSPEND
+ sprd_pwm_bl.sprd_early_suspend_desc.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN;
+ sprd_pwm_bl.sprd_early_suspend_desc.suspend = sprd_backlight_earlysuspend;
+ sprd_pwm_bl.sprd_early_suspend_desc.resume = sprd_backlight_lateresume;
+ register_early_suspend(&sprd_pwm_bl.sprd_early_suspend_desc);
+#endif
+
+ PRINT_INFO("probe success\n");
+ return 0;
+}
+
+static int sprd_backlight_remove(struct platform_device *pdev)
+{
+ struct backlight_device *bldev;
+
+ bldev = platform_get_drvdata(pdev);
+ bldev->props.power = FB_BLANK_UNBLANK;
+ bldev->props.brightness = PWM_MOD_MAX;
+
+ backlight_update_status(bldev);
+ backlight_device_unregister(bldev);
+ platform_set_drvdata(pdev, NULL);
+
+#ifdef CONFIG_EARLYSUSPEND
+ unregister_early_suspend(&sprd_pwm_bl.sprd_early_suspend_desc);
+#endif
+ return 0;
+}
+
+static void sprd_backlight_shutdown(struct platform_device *pdev)
+{
+ struct backlight_device *bldev;
+
+ bldev = platform_get_drvdata(pdev);
+ bldev->props.brightness = 0;
+ backlight_update_status(bldev);
+}
+
+
+#ifdef CONFIG_PM
+static int sprd_backlight_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ return 0;
+}
+
+static int sprd_backlight_resume(struct platform_device *pdev)
+{
+ return 0;
+}
+#else
+#define sprd_backlight_suspend NULL
+#define sprd_backlight_resume NULL
+#endif
+
+static const struct of_device_id backlight_of_match[] = {
+ { .compatible = "sprd,sprd_pwm_bl", },
+ { }
+};
+static struct platform_driver sprd_backlight_driver = {
+ .probe = sprd_backlight_probe,
+ .remove = sprd_backlight_remove,
+ .suspend = NULL,
+ .resume = NULL,
+ .shutdown = sprd_backlight_shutdown,
+ .driver = {
+ .name = "sprd_pwm_bl",
+ .owner = THIS_MODULE,
+ .of_match_table = backlight_of_match,
+ },
+};
+
+static int __init sprd_backlight_init(void)
+{
+ return platform_driver_register(&sprd_backlight_driver);
+}
+
+static void __exit sprd_backlight_exit(void)
+{
+ platform_driver_unregister(&sprd_backlight_driver);
+}
+
+module_init(sprd_backlight_init);
+module_exit(sprd_backlight_exit);
+
+MODULE_DESCRIPTION("Spreadtrum backlight Driver");