diff options
Diffstat (limited to 'arch/arm/mach-s5pv210/power-domain.c')
-rw-r--r-- | arch/arm/mach-s5pv210/power-domain.c | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/arch/arm/mach-s5pv210/power-domain.c b/arch/arm/mach-s5pv210/power-domain.c new file mode 100644 index 0000000..59a1a98 --- /dev/null +++ b/arch/arm/mach-s5pv210/power-domain.c @@ -0,0 +1,567 @@ +/* linux/arch/arm/mach-s5pv210/power-domain.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * S5PV210 - Power domain support + * + * 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/err.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <mach/power-domain.h> + +#include <mach/regs-clock.h> +#include <plat/devs.h> + +struct s5pv210_pd_data { + struct regulator_desc desc; + struct regulator_dev *dev; + int microvolts; + unsigned startup_delay; + struct clk_should_be_running *clk_run; + int ctrlbit; +}; + +struct clk_should_be_running { + const char *clk_name; + struct device *dev; +}; +static spinlock_t pd_lock; + +static struct regulator_consumer_supply s5pv210_pd_audio_supply[] = { + REGULATOR_SUPPLY("pd", "samsung-i2s.0"), +}; + +static struct regulator_consumer_supply s5pv210_pd_cam_supply[] = { + REGULATOR_SUPPLY("pd", "s3c-fimc.0"), + REGULATOR_SUPPLY("pd", "s3c-fimc.1"), + REGULATOR_SUPPLY("pd", "s3c-fimc.2"), + REGULATOR_SUPPLY("pd", "s3c-jpg"), + REGULATOR_SUPPLY("pd", "s3c-csis"), + REGULATOR_SUPPLY("pd", "s5p-rotator"), +}; + +static struct regulator_consumer_supply s5pv210_pd_tv_supply[] = { + REGULATOR_SUPPLY("pd", "s5p-tvout"), +}; + +static struct regulator_consumer_supply s5pv210_pd_lcd_supply[] = { + REGULATOR_SUPPLY("pd", "s3cfb"), +}; + +static struct regulator_consumer_supply s5pv210_pd_g3d_supply[] = { + REGULATOR_SUPPLY("pd", "pvrsrvkm"), +}; + +static struct regulator_consumer_supply s5pv210_pd_mfc_supply[] = { + REGULATOR_SUPPLY("pd", "s3c-mfc"), +}; + +static struct regulator_init_data s5pv210_pd_audio_data = { + .constraints = { + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(s5pv210_pd_audio_supply), + .consumer_supplies = s5pv210_pd_audio_supply, +}; + +static struct regulator_init_data s5pv210_pd_cam_data = { + .constraints = { + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(s5pv210_pd_cam_supply), + .consumer_supplies = s5pv210_pd_cam_supply, +}; + +static struct regulator_init_data s5pv210_pd_tv_data = { + .constraints = { + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(s5pv210_pd_tv_supply), + .consumer_supplies = s5pv210_pd_tv_supply, +}; + +static struct regulator_init_data s5pv210_pd_lcd_data = { + .constraints = { + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(s5pv210_pd_lcd_supply), + .consumer_supplies = s5pv210_pd_lcd_supply, +}; + +static struct regulator_init_data s5pv210_pd_g3d_data = { + .constraints = { + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(s5pv210_pd_g3d_supply), + .consumer_supplies = s5pv210_pd_g3d_supply, +}; + +static struct regulator_init_data s5pv210_pd_mfc_data = { + .constraints = { + .valid_modes_mask = REGULATOR_MODE_NORMAL, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(s5pv210_pd_mfc_supply), + .consumer_supplies = s5pv210_pd_mfc_supply, +}; + +struct clk_should_be_running s5pv210_pd_audio_clk[] = { + { + .clk_name = "i2scdclk", + .dev = &s5pv210_device_iis0.dev, + }, { + /* end of the clock array */ + }, +}; + +struct clk_should_be_running s5pv210_pd_cam_clk[] = { + { + .clk_name = "fimc", + .dev = &s3c_device_fimc0.dev, + }, { + .clk_name = "fimc", + .dev = &s3c_device_fimc1.dev, + }, { + .clk_name = "fimc", + .dev = &s3c_device_fimc2.dev, + }, { + .clk_name = "sclk_csis", + .dev = &s5p_device_mipi_csis0.dev, + }, { + .clk_name = "jpeg", + .dev = &s3c_device_jpeg.dev, + }, { + .clk_name = "rot", + .dev = &s5p_device_rotator.dev, + }, { + /* end of the clock array */ + }, +}; + +struct clk_should_be_running s5pv210_pd_tv_clk[] = { + { + .clk_name = "vp", + .dev = &s5p_device_tvout.dev, + }, { + .clk_name = "mixer", + .dev = &s5p_device_tvout.dev, + }, { + .clk_name = "tvenc", + .dev = &s5p_device_tvout.dev, + }, { + .clk_name = "hdmi", + .dev = &s5p_device_tvout.dev, + }, { + /* end of the clock array */ + }, +}; + +struct clk_should_be_running s5pv210_pd_lcd_clk[] = { + { + .clk_name = "lcd", + .dev = &s3c_device_fb.dev, + }, { + .clk_name = "dsim", + .dev = &s3c_device_fb.dev, + }, { + .clk_name = "sclk_fimg2d", + .dev = &s3c_device_fb.dev, + }, { + /* end of the clock array */ + }, +}; + +struct clk_should_be_running s5pv210_pd_g3d_clk[] = { + { + .clk_name = "sclk", + .dev = &s3c_device_g3d.dev, + }, { + /* end of the clock array */ + }, +}; + +struct clk_should_be_running s5pv210_pd_mfc_clk[] = { + { + .clk_name = "mfc", + .dev = &s3c_device_mfc.dev, + }, { + /* end of the clock array */ + }, +}; + +static struct s5pv210_pd_config s5pv210_pd_audio_pdata = { + .supply_name = "pd_audio_supply", + .microvolts = 5000000, + .init_data = &s5pv210_pd_audio_data, + .clk_run = s5pv210_pd_audio_clk, + .ctrlbit = S5PV210_PD_AUDIO, +}; + +static struct s5pv210_pd_config s5pv210_pd_cam_pdata = { + .supply_name = "pd_cam_supply", + .microvolts = 5000000, + .init_data = &s5pv210_pd_cam_data, + .clk_run = s5pv210_pd_cam_clk, + .ctrlbit = S5PV210_PD_CAM, +}; + +static struct s5pv210_pd_config s5pv210_pd_tv_pdata = { + .supply_name = "pd_tv_supply", + .microvolts = 5000000, + .init_data = &s5pv210_pd_tv_data, + .clk_run = s5pv210_pd_tv_clk, + .ctrlbit = S5PV210_PD_TV, +}; + +static struct s5pv210_pd_config s5pv210_pd_lcd_pdata = { + .supply_name = "pd_lcd_supply", + .microvolts = 5000000, + .init_data = &s5pv210_pd_lcd_data, + .clk_run = s5pv210_pd_lcd_clk, + .ctrlbit = S5PV210_PD_LCD, +}; + +static struct s5pv210_pd_config s5pv210_pd_g3d_pdata = { + .supply_name = "pd_g3d_supply", + .microvolts = 5000000, + .init_data = &s5pv210_pd_g3d_data, + .clk_run = s5pv210_pd_g3d_clk, + .ctrlbit = S5PV210_PD_G3D, +}; + +static struct s5pv210_pd_config s5pv210_pd_mfc_pdata = { + .supply_name = "pd_mfc_supply", + .microvolts = 5000000, + .init_data = &s5pv210_pd_mfc_data, + .clk_run = s5pv210_pd_mfc_clk, + .ctrlbit = S5PV210_PD_MFC, +}; + +struct platform_device s5pv210_pd_audio = { + .name = "reg-s5pv210-pd", + .id = 0, + .dev = { + .platform_data = &s5pv210_pd_audio_pdata, + }, +}; + +struct platform_device s5pv210_pd_cam = { + .name = "reg-s5pv210-pd", + .id = 1, + .dev = { + .platform_data = &s5pv210_pd_cam_pdata, + }, +}; + +struct platform_device s5pv210_pd_tv = { + .name = "reg-s5pv210-pd", + .id = 2, + .dev = { + .platform_data = &s5pv210_pd_tv_pdata, + }, +}; + +struct platform_device s5pv210_pd_lcd = { + .name = "reg-s5pv210-pd", + .id = 3, + .dev = { + .platform_data = &s5pv210_pd_lcd_pdata, + }, +}; + +struct platform_device s5pv210_pd_g3d = { + .name = "reg-s5pv210-pd", + .id = 4, + .dev = { + .platform_data = &s5pv210_pd_g3d_pdata, + }, +}; + +struct platform_device s5pv210_pd_mfc = { + .name = "reg-s5pv210-pd", + .id = 5, + .dev = { + .platform_data = &s5pv210_pd_mfc_pdata, + }, +}; + +static int s5pv210_pd_pwr_done(int ctrl) +{ + unsigned int cnt; + cnt = 1000; + + do { + if (__raw_readl(S5P_BLK_PWR_STAT) & ctrl) + return 0; + udelay(1); + } while (cnt-- > 0); + + return -ETIME; +} + +static int s5pv210_pd_pwr_off(int ctrl) +{ + unsigned int cnt; + cnt = 1000; + + do { + if (!(__raw_readl(S5P_BLK_PWR_STAT) & ctrl)) + return 0; + udelay(1); + } while (cnt-- > 0); + + return -ETIME; +} + +static int s5pv210_pd_ctrl(int ctrlbit, int enable) +{ + u32 pd_reg; + + spin_lock(&pd_lock); + pd_reg = __raw_readl(S5P_NORMAL_CFG); + if (enable) { + __raw_writel((pd_reg | ctrlbit), S5P_NORMAL_CFG); + if (s5pv210_pd_pwr_done(ctrlbit)) + goto out; + } else { + __raw_writel((pd_reg & ~(ctrlbit)), S5P_NORMAL_CFG); + if (s5pv210_pd_pwr_off(ctrlbit)) + goto out; + } + spin_unlock(&pd_lock); + return 0; +out: + spin_unlock(&pd_lock); + return -ETIME; + +} + +static int s5pv210_pd_clk_enable(struct clk_should_be_running *clk_run) +{ + struct clk *clkp; + int i; + + for (i = 0;; i++) { + if (clk_run[i].clk_name == NULL) + break; + + clkp = clk_get(clk_run[i].dev, clk_run[i].clk_name); + + if (IS_ERR(clkp)) { + printk(KERN_ERR "unable to get clock %s\n", + clk_run[i].clk_name); + } else { + clk_enable(clkp); + clk_put(clkp); + } + } + + return 0; +} + +static int s5pv210_pd_clk_disable(struct clk_should_be_running *clk_run) +{ + struct clk *clkp; + int i; + + for (i = 0;; i++) { + if (clk_run[i].clk_name == NULL) + break; + + clkp = clk_get(clk_run[i].dev, clk_run[i].clk_name); + + if (IS_ERR(clkp)) { + printk(KERN_ERR "unable to get clock %s\n", + clk_run[i].clk_name); + } else { + clk_disable(clkp); + clk_put(clkp); + } + } + + return 0; +} + +static int s5pv210_pd_is_enabled(struct regulator_dev *dev) +{ + struct s5pv210_pd_data *data = rdev_get_drvdata(dev); + + return (__raw_readl(S5P_BLK_PWR_STAT) & data->ctrlbit) ? 1 : 0; +} + +static int s5pv210_pd_enable(struct regulator_dev *dev) +{ + struct s5pv210_pd_data *data = rdev_get_drvdata(dev); + int ret; + + if (data->clk_run) + s5pv210_pd_clk_enable(data->clk_run); + + ret = s5pv210_pd_ctrl(data->ctrlbit, 1); + if (ret < 0) { + printk(KERN_ERR "failed to enable power domain\n"); + return ret; + } + + if (data->clk_run) + s5pv210_pd_clk_disable(data->clk_run); + + return 0; +} + +static int s5pv210_pd_disable(struct regulator_dev *dev) +{ + struct s5pv210_pd_data *data = rdev_get_drvdata(dev); + int ret; + + ret = s5pv210_pd_ctrl(data->ctrlbit, 0); + if (ret < 0) { + printk(KERN_ERR "faild to disable power domain\n"); + return ret; + } + + return 0; +} + +static int s5pv210_pd_enable_time(struct regulator_dev *dev) +{ + struct s5pv210_pd_data *data = rdev_get_drvdata(dev); + + return data->startup_delay; +} + +static int s5pv210_pd_get_voltage(struct regulator_dev *dev) +{ + struct s5pv210_pd_data *data = rdev_get_drvdata(dev); + + return data->microvolts; +} + +static int s5pv210_pd_list_voltage(struct regulator_dev *dev, + unsigned selector) +{ + struct s5pv210_pd_data *data = rdev_get_drvdata(dev); + + if (selector != 0) + return -EINVAL; + + return data->microvolts; +} + +static struct regulator_ops s5pv210_pd_ops = { + .is_enabled = s5pv210_pd_is_enabled, + .enable = s5pv210_pd_enable, + .disable = s5pv210_pd_disable, + .enable_time = s5pv210_pd_enable_time, + .get_voltage = s5pv210_pd_get_voltage, + .list_voltage = s5pv210_pd_list_voltage, +}; + +static int __devinit reg_s5pv210_pd_probe(struct platform_device *pdev) +{ + struct s5pv210_pd_config *config = pdev->dev.platform_data; + struct s5pv210_pd_data *drvdata; + int ret; + + drvdata = kzalloc(sizeof(struct s5pv210_pd_data), GFP_KERNEL); + if (drvdata == NULL) { + dev_err(&pdev->dev, "Failed to allocate device data\n"); + ret = -ENOMEM; + goto err; + } + + drvdata->desc.name = kstrdup(config->supply_name, GFP_KERNEL); + if (drvdata->desc.name == NULL) { + dev_err(&pdev->dev, "Failed to allocate supply name\n"); + ret = -ENOMEM; + goto err; + } + + drvdata->desc.type = REGULATOR_VOLTAGE; + drvdata->desc.owner = THIS_MODULE; + drvdata->desc.ops = &s5pv210_pd_ops; + drvdata->desc.n_voltages = 1; + + drvdata->microvolts = config->microvolts; + drvdata->startup_delay = config->startup_delay; + + drvdata->clk_run = config->clk_run; + drvdata->ctrlbit = config->ctrlbit; + spin_lock_init(&pd_lock); + + drvdata->dev = regulator_register(&drvdata->desc, &pdev->dev, + config->init_data, drvdata); + if (IS_ERR(drvdata->dev)) { + ret = PTR_ERR(drvdata->dev); + dev_err(&pdev->dev, "Failed to register regulator: %d\n", ret); + goto err_name; + } + + platform_set_drvdata(pdev, drvdata); + + dev_dbg(&pdev->dev, "%s supplying %duV\n", drvdata->desc.name, + drvdata->microvolts); + + return 0; + +err_name: + kfree(drvdata->desc.name); +err: + kfree(drvdata); + return ret; +} + +static int __devexit reg_s5pv210_pd_remove(struct platform_device *pdev) +{ + struct s5pv210_pd_data *drvdata = platform_get_drvdata(pdev); + + regulator_unregister(drvdata->dev); + kfree(drvdata->desc.name); + kfree(drvdata); + + return 0; +} + +static struct platform_driver regulator_s5pv210_pd_driver = { + .probe = reg_s5pv210_pd_probe, + .remove = __devexit_p(reg_s5pv210_pd_remove), + .driver = { + .name = "reg-s5pv210-pd", + .owner = THIS_MODULE, + }, +}; + +static int __init regulator_s5pv210_pd_init(void) +{ + return platform_driver_register(®ulator_s5pv210_pd_driver); +} +subsys_initcall(regulator_s5pv210_pd_init); + +static void __exit regulator_s5pv210_pd_exit(void) +{ + platform_driver_unregister(®ulator_s5pv210_pd_driver); +} +module_exit(regulator_s5pv210_pd_exit); + +MODULE_AUTHOR("HuiSung Kang <hs1218.kang@samsung.com>"); +MODULE_DESCRIPTION("S5PV210 power domain regulator"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:reg-s5pv210-pd"); |