diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/clocksource/Kconfig | 20 | ||||
-rw-r--r-- | drivers/clocksource/Makefile | 3 | ||||
-rw-r--r-- | drivers/clocksource/clksrc-dbx500-prcmu.c | 23 | ||||
-rw-r--r-- | drivers/clocksource/db5500-mtimer.c | 67 | ||||
-rw-r--r-- | drivers/cpufreq/Makefile | 3 | ||||
-rw-r--r-- | drivers/cpufreq/cpufreq.c | 21 | ||||
-rw-r--r-- | drivers/cpufreq/cpufreq_ondemand.c | 167 | ||||
-rw-r--r-- | drivers/cpufreq/db8500-cpufreq.c | 170 | ||||
-rw-r--r-- | drivers/cpufreq/dbx500-cpufreq.c | 340 | ||||
-rw-r--r-- | drivers/mfd/db5500-prcmu-regs.h | 141 | ||||
-rw-r--r-- | drivers/mfd/db5500-prcmu.c | 2055 | ||||
-rw-r--r-- | drivers/mfd/db8500-prcmu.c | 141 | ||||
-rw-r--r-- | drivers/mfd/dbx500-prcmu-regs.h | 17 | ||||
-rw-r--r-- | drivers/regulator/Kconfig | 8 | ||||
-rw-r--r-- | drivers/regulator/Makefile | 1 | ||||
-rw-r--r-- | drivers/regulator/ab5500.c | 650 | ||||
-rw-r--r-- | drivers/regulator/ab8500-debug.c | 2083 | ||||
-rw-r--r-- | drivers/regulator/ab8500-debug.h | 80 | ||||
-rw-r--r-- | drivers/regulator/ab8500-ext.c | 451 | ||||
-rw-r--r-- | drivers/regulator/ab8500.c | 1052 | ||||
-rw-r--r-- | drivers/regulator/core.c | 56 | ||||
-rw-r--r-- | drivers/regulator/db5500-prcmu.c | 334 | ||||
-rw-r--r-- | drivers/regulator/dbx500-prcmu.c | 167 |
23 files changed, 7646 insertions, 404 deletions
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 5138927a416..6f86f8ca9b0 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -19,13 +19,27 @@ config DW_APB_TIMER config CLKSRC_DBX500_PRCMU bool "Clocksource PRCMU Timer" depends on UX500_SOC_DB5500 || UX500_SOC_DB8500 - default y + default y if UX500_SOC_DB8500 help Use the always on PRCMU Timer as clocksource config CLKSRC_DBX500_PRCMU_SCHED_CLOCK - bool "Clocksource PRCMU Timer sched_clock" - depends on (CLKSRC_DBX500_PRCMU && !NOMADIK_MTU_SCHED_CLOCK) + bool + depends on CLKSRC_DBX500_PRCMU + select HAVE_SCHED_CLOCK + help + Use the always on PRCMU Timer as sched_clock + +config CLKSRC_DB5500_MTIMER + bool "Clocksource MTIMER" + depends on UX500_SOC_DB5500 default y help + Use the always on MTIMER as clocksource + +config CLKSRC_DB5500_MTIMER_SCHED_CLOCK + bool + depends on CLKSRC_DB5500_MTIMER + select HAVE_SCHED_CLOCK + help Use the always on PRCMU Timer as sched_clock diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 8d81a1d3265..9b10f6b7536 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -9,4 +9,5 @@ obj-$(CONFIG_SH_TIMER_TMU) += sh_tmu.o obj-$(CONFIG_CLKBLD_I8253) += i8253.o obj-$(CONFIG_CLKSRC_MMIO) += mmio.o obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o -obj-$(CONFIG_CLKSRC_DBX500_PRCMU) += clksrc-dbx500-prcmu.o
\ No newline at end of file +obj-$(CONFIG_CLKSRC_DBX500_PRCMU) += clksrc-dbx500-prcmu.o +obj-$(CONFIG_CLKSRC_DB5500_MTIMER) += db5500-mtimer.o diff --git a/drivers/clocksource/clksrc-dbx500-prcmu.c b/drivers/clocksource/clksrc-dbx500-prcmu.c index c26c369eb9e..dc71e432dc5 100644 --- a/drivers/clocksource/clksrc-dbx500-prcmu.c +++ b/drivers/clocksource/clksrc-dbx500-prcmu.c @@ -14,6 +14,9 @@ */ #include <linux/clockchips.h> #include <linux/clksrc-dbx500-prcmu.h> +#ifdef CONFIG_BOOTTIME +#include <linux/boottime.h> +#endif #include <asm/sched_clock.h> @@ -68,6 +71,23 @@ static u32 notrace dbx500_prcmu_sched_clock_read(void) #endif +#ifdef CONFIG_BOOTTIME +static unsigned long __init boottime_get_time(void) +{ + return div_s64(clocksource_cyc2ns(clocksource_dbx500_prcmu.read( + &clocksource_dbx500_prcmu), + clocksource_dbx500_prcmu.mult, + clocksource_dbx500_prcmu.shift), + 1000); +} + +static struct boottime_timer __initdata boottime_timer = { + .init = NULL, + .get_time = boottime_get_time, + .finalize = NULL, +}; +#endif + void __init clksrc_dbx500_prcmu_init(void __iomem *base) { clksrc_dbx500_timer_base = base; @@ -90,4 +110,7 @@ void __init clksrc_dbx500_prcmu_init(void __iomem *base) 32, RATE_32K); #endif clocksource_register_hz(&clocksource_dbx500_prcmu, RATE_32K); +#ifdef CONFIG_BOOTTIME + boottime_activate(&boottime_timer); +#endif } diff --git a/drivers/clocksource/db5500-mtimer.c b/drivers/clocksource/db5500-mtimer.c new file mode 100644 index 00000000000..5e64da19e66 --- /dev/null +++ b/drivers/clocksource/db5500-mtimer.c @@ -0,0 +1,67 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + */ + +#include <linux/io.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/clockchips.h> +#include <linux/clksrc-db5500-mtimer.h> +#include <linux/boottime.h> + +#include <asm/sched_clock.h> + +#define MTIMER_PRIMARY_COUNTER 0x18 + +static void __iomem *db5500_mtimer_base; + +#ifdef CONFIG_CLKSRC_DB5500_MTIMER_SCHED_CLOCK +static DEFINE_CLOCK_DATA(cd); + +unsigned long long notrace sched_clock(void) +{ + u32 cyc; + + if (unlikely(!db5500_mtimer_base)) + return 0; + + cyc = readl_relaxed(db5500_mtimer_base + MTIMER_PRIMARY_COUNTER); + + return cyc_to_sched_clock(&cd, cyc, (u32)~0); +} + +static void notrace db5500_mtimer_update_sched_clock(void) +{ + u32 cyc = readl_relaxed(db5500_mtimer_base + MTIMER_PRIMARY_COUNTER); + update_sched_clock(&cd, cyc, (u32)~0); +} +#endif + +#ifdef CONFIG_BOOTTIME +static unsigned long __init boottime_get_time(void) +{ + return sched_clock(); +} + +static struct boottime_timer __initdata boottime_timer = { + .init = NULL, + .get_time = boottime_get_time, + .finalize = NULL, +}; +#endif + +void __init db5500_mtimer_init(void __iomem *base) +{ + db5500_mtimer_base = base; + + clocksource_mmio_init(base + MTIMER_PRIMARY_COUNTER, "mtimer", 32768, + 400, 32, clocksource_mmio_readl_up); + +#ifdef CONFIG_CLKSRC_DB5500_MTIMER_SCHED_CLOCK + init_sched_clock(&cd, db5500_mtimer_update_sched_clock, + 32, 32768); +#endif + boottime_activate(&boottime_timer); +} diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 9531fc2eda2..44aa7093235 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -39,7 +39,8 @@ obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o ################################################################################## # ARM SoC drivers -obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o +obj-$(CONFIG_UX500_SOC_DB8500) += dbx500-cpufreq.o +obj-$(CONFIG_UX500_SOC_DB5500) += dbx500-cpufreq.o obj-$(CONFIG_ARM_S3C2416_CPUFREQ) += s3c2416-cpufreq.o obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 7f2f149ae40..4de1fff790f 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -376,6 +376,27 @@ show_one(scaling_cur_freq, cur); static int __cpufreq_set_policy(struct cpufreq_policy *data, struct cpufreq_policy *policy); +int cpufreq_update_freq(int cpu, unsigned int min, unsigned int max) +{ + int ret; + struct cpufreq_policy new_policy; + struct cpufreq_policy *policy = cpufreq_cpu_get(cpu); + + ret = cpufreq_get_policy(&new_policy, cpu); + if (ret) + return -EINVAL; + + new_policy.min = min; + new_policy.max = max; + + ret = __cpufreq_set_policy(policy, &new_policy); + policy->user_policy.min = policy->min; + policy->user_policy.max = policy->max; + + return ret; +} +EXPORT_SYMBOL(cpufreq_update_freq); + /** * cpufreq_per_cpu_attr_write() / store_##file_name() - sysfs write access */ diff --git a/drivers/cpufreq/cpufreq_ondemand.c b/drivers/cpufreq/cpufreq_ondemand.c index 836e9b062e5..765256e4904 100644 --- a/drivers/cpufreq/cpufreq_ondemand.c +++ b/drivers/cpufreq/cpufreq_ondemand.c @@ -79,7 +79,6 @@ struct cpu_dbs_info_s { cputime64_t prev_cpu_wall; cputime64_t prev_cpu_nice; struct cpufreq_policy *cur_policy; - struct delayed_work work; struct cpufreq_frequency_table *freq_table; unsigned int freq_lo; unsigned int freq_lo_jiffies; @@ -95,8 +94,10 @@ struct cpu_dbs_info_s { struct mutex timer_mutex; }; static DEFINE_PER_CPU(struct cpu_dbs_info_s, od_cpu_dbs_info); +static DEFINE_PER_CPU(struct delayed_work, ondemand_work); static unsigned int dbs_enable; /* number of CPUs using this policy */ +static ktime_t time_stamp; /* * dbs_mutex protects dbs_enable in governor start/stop. @@ -290,22 +291,23 @@ static void update_sampling_rate(unsigned int new_rate) mutex_lock(&dbs_info->timer_mutex); - if (!delayed_work_pending(&dbs_info->work)) { + if (!delayed_work_pending(&per_cpu(ondemand_work, cpu))) { mutex_unlock(&dbs_info->timer_mutex); continue; } next_sampling = jiffies + usecs_to_jiffies(new_rate); - appointed_at = dbs_info->work.timer.expires; + appointed_at = per_cpu(ondemand_work, cpu).timer.expires; if (time_before(next_sampling, appointed_at)) { mutex_unlock(&dbs_info->timer_mutex); - cancel_delayed_work_sync(&dbs_info->work); + cancel_delayed_work_sync(&per_cpu(ondemand_work, cpu)); mutex_lock(&dbs_info->timer_mutex); - schedule_delayed_work_on(dbs_info->cpu, &dbs_info->work, + schedule_delayed_work_on(dbs_info->cpu, + &per_cpu(ondemand_work, cpu), usecs_to_jiffies(new_rate)); } @@ -449,6 +451,26 @@ static struct attribute_group dbs_attr_group = { /************************** sysfs end ************************/ +static bool dbs_sw_coordinated_cpus(void) +{ + struct cpu_dbs_info_s *dbs_info; + struct cpufreq_policy *policy; + int i = 0; + int j; + + dbs_info = &per_cpu(od_cpu_dbs_info, 0); + policy = dbs_info->cur_policy; + + for_each_cpu(j, policy->cpus) { + i++; + } + + if (i > 1) + return true; /* Dependant CPUs */ + else + return false; +} + static void dbs_freq_increase(struct cpufreq_policy *p, unsigned int freq) { if (dbs_tuners_ins.powersave_bias) @@ -463,7 +485,6 @@ static void dbs_freq_increase(struct cpufreq_policy *p, unsigned int freq) static void dbs_check_cpu(struct cpu_dbs_info_s *this_dbs_info) { unsigned int max_load_freq; - struct cpufreq_policy *policy; unsigned int j; @@ -598,20 +619,42 @@ static void dbs_check_cpu(struct cpu_dbs_info_s *this_dbs_info) static void do_dbs_timer(struct work_struct *work) { - struct cpu_dbs_info_s *dbs_info = - container_of(work, struct cpu_dbs_info_s, work.work); - unsigned int cpu = dbs_info->cpu; - int sample_type = dbs_info->sample_type; - + struct cpu_dbs_info_s *dbs_info; + unsigned int cpu = smp_processor_id(); + int sample_type; int delay; + bool sample = true; + + /* If SW dependant CPUs, use CPU 0 as leader */ + if (dbs_sw_coordinated_cpus()) { + + ktime_t time_now; + s64 delta_us; - mutex_lock(&dbs_info->timer_mutex); + dbs_info = &per_cpu(od_cpu_dbs_info, 0); + mutex_lock(&dbs_info->timer_mutex); + + time_now = ktime_get(); + delta_us = ktime_us_delta(time_now, time_stamp); + + /* Do nothing if we recently have sampled */ + if (delta_us < (s64)(dbs_tuners_ins.sampling_rate / 2)) + sample = false; + else + time_stamp = time_now; + } else { + dbs_info = &per_cpu(od_cpu_dbs_info, cpu); + mutex_lock(&dbs_info->timer_mutex); + } + + sample_type = dbs_info->sample_type; /* Common NORMAL_SAMPLE setup */ dbs_info->sample_type = DBS_NORMAL_SAMPLE; if (!dbs_tuners_ins.powersave_bias || sample_type == DBS_NORMAL_SAMPLE) { - dbs_check_cpu(dbs_info); + if (sample) + dbs_check_cpu(dbs_info); if (dbs_info->freq_lo) { /* Setup timer for SUB_SAMPLE */ dbs_info->sample_type = DBS_SUB_SAMPLE; @@ -627,15 +670,17 @@ static void do_dbs_timer(struct work_struct *work) delay -= jiffies % delay; } } else { - __cpufreq_driver_target(dbs_info->cur_policy, - dbs_info->freq_lo, CPUFREQ_RELATION_H); + if (sample) + __cpufreq_driver_target(dbs_info->cur_policy, + dbs_info->freq_lo, + CPUFREQ_RELATION_H); delay = dbs_info->freq_lo_jiffies; } - schedule_delayed_work_on(cpu, &dbs_info->work, delay); + schedule_delayed_work_on(cpu, &per_cpu(ondemand_work, cpu), delay); mutex_unlock(&dbs_info->timer_mutex); } -static inline void dbs_timer_init(struct cpu_dbs_info_s *dbs_info) +static inline void dbs_timer_init(struct cpu_dbs_info_s *dbs_info, int cpu) { /* We want all CPUs to do sampling nearly on same jiffy */ int delay = usecs_to_jiffies(dbs_tuners_ins.sampling_rate); @@ -644,13 +689,18 @@ static inline void dbs_timer_init(struct cpu_dbs_info_s *dbs_info) delay -= jiffies % delay; dbs_info->sample_type = DBS_NORMAL_SAMPLE; - INIT_DELAYED_WORK_DEFERRABLE(&dbs_info->work, do_dbs_timer); - schedule_delayed_work_on(dbs_info->cpu, &dbs_info->work, delay); + cancel_delayed_work_sync(&per_cpu(ondemand_work, cpu)); + schedule_delayed_work_on(cpu, &per_cpu(ondemand_work, cpu), delay); +} + +static inline void dbs_timer_exit(int cpu) +{ + cancel_delayed_work_sync(&per_cpu(ondemand_work, cpu)); } -static inline void dbs_timer_exit(struct cpu_dbs_info_s *dbs_info) +static void dbs_timer_exit_per_cpu(struct work_struct *dummy) { - cancel_delayed_work_sync(&dbs_info->work); + dbs_timer_exit(smp_processor_id()); } /* @@ -676,6 +726,42 @@ static int should_io_be_busy(void) return 0; } +static int __cpuinit cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + unsigned int cpu = (unsigned long)hcpu; + struct device *cpu_dev; + struct cpu_dbs_info_s *dbs_info; + + if (dbs_sw_coordinated_cpus()) + dbs_info = &per_cpu(od_cpu_dbs_info, 0); + else + dbs_info = &per_cpu(od_cpu_dbs_info, cpu); + + cpu_dev = get_cpu_device(cpu); + if (cpu_dev) { + switch (action) { + case CPU_ONLINE: + case CPU_ONLINE_FROZEN: + dbs_timer_init(dbs_info, cpu); + break; + case CPU_DOWN_PREPARE: + case CPU_DOWN_PREPARE_FROZEN: + dbs_timer_exit(cpu); + break; + case CPU_DOWN_FAILED: + case CPU_DOWN_FAILED_FROZEN: + dbs_timer_init(dbs_info, cpu); + break; + } + } + return NOTIFY_OK; +} + +static struct notifier_block __refdata ondemand_cpu_notifier = { + .notifier_call = cpu_callback, +}; + static int cpufreq_governor_dbs(struct cpufreq_policy *policy, unsigned int event) { @@ -704,9 +790,13 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy, if (dbs_tuners_ins.ignore_nice) j_dbs_info->prev_cpu_nice = kcpustat_cpu(j).cpustat[CPUTIME_NICE]; + mutex_init(&j_dbs_info->timer_mutex); + INIT_DELAYED_WORK_DEFERRABLE(&per_cpu(ondemand_work, j), + do_dbs_timer); + + j_dbs_info->rate_mult = 1; } this_dbs_info->cpu = cpu; - this_dbs_info->rate_mult = 1; ondemand_powersave_bias_init_cpu(cpu); /* * Start the timerschedule work, when this governor @@ -736,21 +826,46 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy, } mutex_unlock(&dbs_mutex); - mutex_init(&this_dbs_info->timer_mutex); - dbs_timer_init(this_dbs_info); + /* If SW coordinated CPUs then register notifier */ + if (dbs_sw_coordinated_cpus()) { + register_hotcpu_notifier(&ondemand_cpu_notifier); + + for_each_cpu(j, policy->cpus) { + struct cpu_dbs_info_s *j_dbs_info; + + j_dbs_info = &per_cpu(od_cpu_dbs_info, 0); + dbs_timer_init(j_dbs_info, j); + } + + /* Initiate timer time stamp */ + time_stamp = ktime_get(); + + + } else + dbs_timer_init(this_dbs_info, cpu); break; case CPUFREQ_GOV_STOP: - dbs_timer_exit(this_dbs_info); + + dbs_timer_exit(cpu); mutex_lock(&dbs_mutex); mutex_destroy(&this_dbs_info->timer_mutex); dbs_enable--; mutex_unlock(&dbs_mutex); - if (!dbs_enable) + if (!dbs_enable) { sysfs_remove_group(cpufreq_global_kobject, &dbs_attr_group); + if (dbs_sw_coordinated_cpus()) { + /* + * Make sure all pending timers/works are + * stopped. + */ + schedule_on_each_cpu(dbs_timer_exit_per_cpu); + unregister_hotcpu_notifier(&ondemand_cpu_notifier); + } + } break; case CPUFREQ_GOV_LIMITS: diff --git a/drivers/cpufreq/db8500-cpufreq.c b/drivers/cpufreq/db8500-cpufreq.c deleted file mode 100644 index 0bf1b8910ee..00000000000 --- a/drivers/cpufreq/db8500-cpufreq.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) STMicroelectronics 2009 - * Copyright (C) ST-Ericsson SA 2010 - * - * License Terms: GNU General Public License v2 - * Author: Sundar Iyer <sundar.iyer@stericsson.com> - * Author: Martin Persson <martin.persson@stericsson.com> - * Author: Jonas Aaberg <jonas.aberg@stericsson.com> - * - */ -#include <linux/kernel.h> -#include <linux/cpufreq.h> -#include <linux/delay.h> -#include <linux/slab.h> -#include <linux/mfd/dbx500-prcmu.h> -#include <mach/id.h> - -static struct cpufreq_frequency_table freq_table[] = { - [0] = { - .index = 0, - .frequency = 200000, - }, - [1] = { - .index = 1, - .frequency = 400000, - }, - [2] = { - .index = 2, - .frequency = 800000, - }, - [3] = { - /* Used for MAX_OPP, if available */ - .index = 3, - .frequency = CPUFREQ_TABLE_END, - }, - [4] = { - .index = 4, - .frequency = CPUFREQ_TABLE_END, - }, -}; - -static enum arm_opp idx2opp[] = { - ARM_EXTCLK, - ARM_50_OPP, - ARM_100_OPP, - ARM_MAX_OPP -}; - -static struct freq_attr *db8500_cpufreq_attr[] = { - &cpufreq_freq_attr_scaling_available_freqs, - NULL, -}; - -static int db8500_cpufreq_verify_speed(struct cpufreq_policy *policy) -{ - return cpufreq_frequency_table_verify(policy, freq_table); -} - -static int db8500_cpufreq_target(struct cpufreq_policy *policy, - unsigned int target_freq, - unsigned int relation) -{ - struct cpufreq_freqs freqs; - unsigned int idx; - - /* scale the target frequency to one of the extremes supported */ - if (target_freq < policy->cpuinfo.min_freq) - target_freq = policy->cpuinfo.min_freq; - if (target_freq > policy->cpuinfo.max_freq) - target_freq = policy->cpuinfo.max_freq; - - /* Lookup the next frequency */ - if (cpufreq_frequency_table_target - (policy, freq_table, target_freq, relation, &idx)) { - return -EINVAL; - } - - freqs.old = policy->cur; - freqs.new = freq_table[idx].frequency; - - if (freqs.old == freqs.new) - return 0; - - /* pre-change notification */ - for_each_cpu(freqs.cpu, policy->cpus) - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - - /* request the PRCM unit for opp change */ - if (prcmu_set_arm_opp(idx2opp[idx])) { - pr_err("db8500-cpufreq: Failed to set OPP level\n"); - return -EINVAL; - } - - /* post change notification */ - for_each_cpu(freqs.cpu, policy->cpus) - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - return 0; -} - -static unsigned int db8500_cpufreq_getspeed(unsigned int cpu) -{ - int i; - /* request the prcm to get the current ARM opp */ - for (i = 0; prcmu_get_arm_opp() != idx2opp[i]; i++) - ; - return freq_table[i].frequency; -} - -static int __cpuinit db8500_cpufreq_init(struct cpufreq_policy *policy) -{ - int i, res; - - BUILD_BUG_ON(ARRAY_SIZE(idx2opp) + 1 != ARRAY_SIZE(freq_table)); - - if (prcmu_has_arm_maxopp()) - freq_table[3].frequency = 1000000; - - pr_info("db8500-cpufreq : Available frequencies:\n"); - for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) - pr_info(" %d Mhz\n", freq_table[i].frequency/1000); - - /* get policy fields based on the table */ - res = cpufreq_frequency_table_cpuinfo(policy, freq_table); - if (!res) - cpufreq_frequency_table_get_attr(freq_table, policy->cpu); - else { - pr_err("db8500-cpufreq : Failed to read policy table\n"); - return res; - } - - policy->min = policy->cpuinfo.min_freq; - policy->max = policy->cpuinfo.max_freq; - policy->cur = db8500_cpufreq_getspeed(policy->cpu); - policy->governor = CPUFREQ_DEFAULT_GOVERNOR; - - /* - * FIXME : Need to take time measurement across the target() - * function with no/some/all drivers in the notification - * list. - */ - policy->cpuinfo.transition_latency = 20 * 1000; /* in ns */ - - /* policy sharing between dual CPUs */ - cpumask_copy(policy->cpus, cpu_present_mask); - - policy->shared_type = CPUFREQ_SHARED_TYPE_ALL; - - return 0; -} - -static struct cpufreq_driver db8500_cpufreq_driver = { - .flags = CPUFREQ_STICKY, - .verify = db8500_cpufreq_verify_speed, - .target = db8500_cpufreq_target, - .get = db8500_cpufreq_getspeed, - .init = db8500_cpufreq_init, - .name = "DB8500", - .attr = db8500_cpufreq_attr, -}; - -static int __init db8500_cpufreq_register(void) -{ - if (!cpu_is_u8500v20_or_later()) - return -ENODEV; - - pr_info("cpufreq for DB8500 started\n"); - return cpufreq_register_driver(&db8500_cpufreq_driver); -} -device_initcall(db8500_cpufreq_register); diff --git a/drivers/cpufreq/dbx500-cpufreq.c b/drivers/cpufreq/dbx500-cpufreq.c new file mode 100644 index 00000000000..a6f991e2fb6 --- /dev/null +++ b/drivers/cpufreq/dbx500-cpufreq.c @@ -0,0 +1,340 @@ +/* + * Copyright (C) STMicroelectronics 2009 + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * License Terms: GNU General Public License v2 + * Author: Sundar Iyer <sundar.iyer@stericsson.com> + * Author: Martin Persson <martin.persson@stericsson.com> + * Author: Jonas Aaberg <jonas.aberg@stericsson.com> + */ + +#include <linux/kernel.h> +#include <linux/cpufreq.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/mfd/dbx500-prcmu.h> +#include <mach/id.h> + +static struct cpufreq_frequency_table db8500_freq_table[] = { + [0] = { + .index = 0, + .frequency = 200000, + }, + [1] = { + .index = 1, + .frequency = 400000, + }, + [2] = { + .index = 2, + .frequency = 800000, + }, + [3] = { + /* Used for MAX_OPP, if available */ + .index = 3, + .frequency = CPUFREQ_TABLE_END, + }, + [4] = { + .index = 4, + .frequency = CPUFREQ_TABLE_END, + }, +}; + +static struct cpufreq_frequency_table db5500_freq_table[] = { + [0] = { + .index = 0, + .frequency = 200000, + }, + [1] = { + .index = 1, + .frequency = 396500, + }, + [2] = { + .index = 2, + .frequency = 793000, + }, + [3] = { + .index = 3, + .frequency = CPUFREQ_TABLE_END, + }, +}; + +static struct cpufreq_frequency_table *freq_table; +static int freq_table_len; + +static enum arm_opp db8500_idx2opp[] = { + ARM_EXTCLK, + ARM_50_OPP, + ARM_100_OPP, + ARM_MAX_OPP +}; + +static enum arm_opp db5500_idx2opp[] = { + ARM_EXTCLK, + ARM_50_OPP, + ARM_100_OPP, +}; + +static enum arm_opp *idx2opp; + +static struct freq_attr *dbx500_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static int dbx500_cpufreq_verify_speed(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, freq_table); +} + +static int dbx500_cpufreq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + struct cpufreq_freqs freqs; + unsigned int idx; + + /* scale the target frequency to one of the extremes supported */ + if (target_freq < policy->cpuinfo.min_freq) + target_freq = policy->cpuinfo.min_freq; + if (target_freq > policy->cpuinfo.max_freq) + target_freq = policy->cpuinfo.max_freq; + + /* Lookup the next frequency */ + if (cpufreq_frequency_table_target + (policy, freq_table, target_freq, relation, &idx)) { + return -EINVAL; + } + + freqs.old = policy->cur; + freqs.new = freq_table[idx].frequency; + + if (freqs.old == freqs.new) + return 0; + + /* pre-change notification */ + for_each_cpu(freqs.cpu, policy->cpus) + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + BUG_ON(idx >= freq_table_len); + + /* request the PRCM unit for opp change */ + if (prcmu_set_arm_opp(idx2opp[idx])) { + pr_err("ux500-cpufreq: Failed to set OPP level\n"); + return -EINVAL; + } + + /* post change notification */ + for_each_cpu(freqs.cpu, policy->cpus) + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +static unsigned int dbx500_cpufreq_getspeed(unsigned int cpu) +{ + int i; + enum arm_opp current_opp; + + current_opp = prcmu_get_arm_opp(); + + /* request the prcm to get the current ARM opp */ + for (i = 0; i < freq_table_len; i++) { + if (current_opp == idx2opp[i]) + return freq_table[i].frequency; + } + + pr_err("cpufreq: ERROR: unknown opp %d given from prcmufw!\n", + current_opp); + BUG_ON(1); + + /* + * Better to return something that might be correct than + * errno or zero, since clk_get_rate() won't do well with an errno. + */ + return freq_table[0].frequency; +} + +static void __init dbx500_cpufreq_init_maxopp_freq(void) +{ + struct prcmu_fw_version *fw_version = prcmu_get_fw_version(); + + if ((fw_version == NULL) || !prcmu_has_arm_maxopp()) + return; + + switch (fw_version->project) { + case PRCMU_FW_PROJECT_U8500: + case PRCMU_FW_PROJECT_U9500: + case PRCMU_FW_PROJECT_U8420: + freq_table[3].frequency = 1000000; + break; + case PRCMU_FW_PROJECT_U8500_C2: + case PRCMU_FW_PROJECT_U9500_C2: + case PRCMU_FW_PROJECT_U8520: + freq_table[3].frequency = 1150000; + break; + default: + break; + } +} + +static bool initialized; + +static void __init dbx500_cpufreq_early_init(void) +{ + if (cpu_is_u5500()) { + freq_table = db5500_freq_table; + idx2opp = db5500_idx2opp; + freq_table_len = ARRAY_SIZE(db5500_freq_table); + } else if (cpu_is_u8500()) { + freq_table = db8500_freq_table; + idx2opp = db8500_idx2opp; + dbx500_cpufreq_init_maxopp_freq(); + freq_table_len = ARRAY_SIZE(db8500_freq_table); + if (!prcmu_has_arm_maxopp()) + freq_table_len--; + } else { + ux500_unknown_soc(); + } + initialized = true; +} + +/* + * This is called from localtimer initialization, via the clk_get_rate() for + * the smp_twd clock. This is way before cpufreq is initialized. + */ +unsigned long dbx500_cpufreq_getfreq(void) +{ + if (!initialized) + dbx500_cpufreq_early_init(); + + return dbx500_cpufreq_getspeed(0) * 1000; +} + +int dbx500_cpufreq_percent2freq(int percent) +{ + int op; + int i; + + switch (percent) { + case 0: + /* Fall through */ + case 25: + op = ARM_EXTCLK; + break; + case 50: + op = ARM_50_OPP; + break; + case 100: + op = ARM_100_OPP; + break; + case 125: + if (cpu_is_u8500() && prcmu_has_arm_maxopp()) + op = ARM_MAX_OPP; + else + op = ARM_100_OPP; + break; + default: + pr_err("cpufreq-dbx500: Incorrect arm target value (%d).\n", + percent);; + return -EINVAL; + break; + } + + for (i = 0; idx2opp[i] != op && i < freq_table_len; i++) + ; + + if (freq_table[i].frequency == CPUFREQ_TABLE_END) { + pr_err("cpufreq-dbx500: Matching frequency does not exist!\n"); + return -EINVAL; + } + + return freq_table[i].frequency; +} + +int dbx500_cpufreq_get_limits(int cpu, int r, + unsigned int *min, unsigned int *max) +{ + int freq; + int ret; + static int old_freq; + struct cpufreq_policy p; + + freq = dbx500_cpufreq_percent2freq(r); + + if (freq < 0) + return -EINVAL; + + if (freq != old_freq) + pr_debug("cpufreq-dbx500: set min arm freq to %d\n", + freq); + + (*min) = freq; + + ret = cpufreq_get_policy(&p, cpu); + if (ret) { + pr_err("cpufreq-dbx500: Failed to get policy.\n"); + return -EINVAL; + } + + (*max) = p.max; + return 0; +} + +static int __cpuinit dbx500_cpufreq_init(struct cpufreq_policy *policy) +{ + int res; + + /* get policy fields based on the table */ + res = cpufreq_frequency_table_cpuinfo(policy, freq_table); + if (!res) + cpufreq_frequency_table_get_attr(freq_table, policy->cpu); + else { + pr_err("dbx500-cpufreq : Failed to read policy table\n"); + return res; + } + + policy->min = policy->cpuinfo.min_freq; + policy->max = policy->cpuinfo.max_freq; + policy->cur = dbx500_cpufreq_getspeed(policy->cpu); + policy->governor = CPUFREQ_DEFAULT_GOVERNOR; + + /* + * FIXME : Need to take time measurement across the target() + * function with no/some/all drivers in the notification + * list. + */ + policy->cpuinfo.transition_latency = 20 * 1000; /* in ns */ + + /* policy sharing between dual CPUs */ + cpumask_copy(policy->cpus, cpu_present_mask); + + policy->shared_type = CPUFREQ_SHARED_TYPE_ALL; + + return 0; +} + +static struct cpufreq_driver dbx500_cpufreq_driver = { + .flags = CPUFREQ_STICKY, + .verify = dbx500_cpufreq_verify_speed, + .target = dbx500_cpufreq_target, + .get = dbx500_cpufreq_getspeed, + .init = dbx500_cpufreq_init, + .name = "DBX500", + .attr = dbx500_cpufreq_attr, +}; + +static int __init dbx500_cpufreq_register(void) +{ + int i; + + if (!initialized) + dbx500_cpufreq_early_init(); + + pr_info("dbx500-cpufreq : Available frequencies:\n"); + + for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) + pr_info(" %d Mhz\n", freq_table[i].frequency / 1000); + + return cpufreq_register_driver(&dbx500_cpufreq_driver); +} +device_initcall(dbx500_cpufreq_register); diff --git a/drivers/mfd/db5500-prcmu-regs.h b/drivers/mfd/db5500-prcmu-regs.h new file mode 100644 index 00000000000..0428b5e95ae --- /dev/null +++ b/drivers/mfd/db5500-prcmu-regs.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + */ + +#ifndef __MACH_PRCMU_REGS_DB5500_H +#define __MACH_PRCMU_REGS_DB5500_H + +#define BITS(_start, _end) ((BIT(_end) - BIT(_start)) + BIT(_end)) + +#define PRCM_TCR 0x1C8 +#define PRCM_TCR_TENSEL_MASK BITS(0, 7) +#define PRCM_TCR_STOP_TIMERS BIT(16) +#define PRCM_TCR_DOZE_MODE BIT(17) + +/* PRCMU HW semaphore */ +#define PRCM_SEM 0x400 +#define PRCM_SEM_PRCM_SEM BIT(0) + +#define DB5500_PRCM_ACLK_MGT 0x004 +#define DB5500_PRCM_SVACLK_MGT 0x008 +#define DB5500_PRCM_SIACLK_MGT 0x00C +#define DB5500_PRCM_SGACLK_MGT 0x014 +#define DB5500_PRCM_UARTCLK_MGT 0x018 +#define DB5500_PRCM_MSP02CLK_MGT 0x01C +#define DB5500_PRCM_I2CCLK_MGT 0x020 +#define DB5500_PRCM_SDMMCCLK_MGT 0x024 +#define DB5500_PRCM_PER1CLK_MGT 0x02C +#define DB5500_PRCM_PER2CLK_MGT 0x030 +#define DB5500_PRCM_PER3CLK_MGT 0x034 +#define DB5500_PRCM_PER5CLK_MGT 0x038 +#define DB5500_PRCM_PER6CLK_MGT 0x03C +#define DB5500_PRCM_IRDACLK_MGT 0x040 +#define DB5500_PRCM_PWMCLK_MGT 0x044 +#define DB5500_PRCM_SPARE1CLK_MGT 0x048 +#define DB5500_PRCM_IRRCCLK_MGT 0x04C +#define DB5500_PRCM_HDMICLK_MGT 0x058 +#define DB5500_PRCM_APEATCLK_MGT 0x05C +#define DB5500_PRCM_APETRACECLK_MGT 0x060 +#define DB5500_PRCM_MCDECLK_MGT 0x064 +#define DB5500_PRCM_DSIALTCLK_MGT 0x06C +#define DB5500_PRCM_DMACLK_MGT 0x074 +#define DB5500_PRCM_B2R2CLK_MGT 0x078 +#define DB5500_PRCM_TVCLK_MGT 0x07C +#define DB5500_PRCM_RNGCLK_MGT 0x284 + +#define PRCM_CLK_MGT_CLKPLLDIV_MASK BITS(0, 4) +#define PRCM_CLK_MGT_CLKPLLDIV_SHIFT 0 +#define PRCM_CLK_MGT_CLKPLLSW_MASK BITS(5, 7) +#define PRCM_CLK_MGT_CLKEN BIT(8) + +#define PRCM_ARM_IT1_CLEAR 0x48C +#define PRCM_ARM_IT1_VAL 0x494 + +/* CPU mailbox registers */ +#define PRCM_MBOX_CPU_VAL 0x0FC +#define PRCM_MBOX_CPU_SET 0x100 + +/* System reset register */ +#define PRCM_APE_SOFTRST 0x228 + +/* PRCMU clock/PLL/reset registers */ +#define PRCM_PLLDSI_FREQ 0x500 +#define PRCM_PLLDSI_ENABLE 0x504 +#define PRCM_PLLDSI_LOCKP 0x508 +#define PRCM_DSI_PLLOUT_SEL 0x530 +#define PRCM_DSITVCLK_DIV 0x52C +#define PRCM_APE_RESETN_SET 0x1E4 +#define PRCM_APE_RESETN_CLR 0x1E8 + +/* CLKOUTx SEL0 settings */ +#define CLKOUT_SEL0_REF_CLK 0x01 /* 0b 0001 */ +#define CLKOUT_SEL0_RTC_CLK0 0x02 /* 0b 0010 */ +#define CLKOUT_SEL0_ULP_CLK 0x04 /* 0b 0100 */ +#define CLKOUT_SEL0_SEL_CLK 0x08 /* 0b 1000 */ + +/* CLKOUTx SEL settings */ +#define CLKOUT_SEL_STATIC0 0x0001 /* 0b 00 0000 0001 */ +#define CLKOUT_SEL_REFCLK 0x0002 /* 0b 00 0000 0010 */ +#define CLKOUT_SEL_ULPCLK 0x0004 /* 0b 00 0000 0100 */ +#define CLKOUT_SEL_ARMCLK 0x0008 /* 0b 00 0000 1000 */ +#define CLKOUT_SEL_SYSACC0CLK 0x0010 /* 0b 00 0001 0000 */ +#define CLKOUT_SEL_SOC0PLLCLK 0x0020 /* 0b 00 0010 0000 */ +#define CLKOUT_SEL_SOC1PLLCLK 0x0040 /* 0b 00 0100 0000 */ +#define CLKOUT_SEL_DDRPLLCLK 0x0080 /* 0b 00 1000 0000 */ +#define CLKOUT_SEL_TVCLK 0x0100 /* 0b 01 0000 0000 */ +#define CLKOUT_SEL_IRDACLK 0x0200 /* 0b 10 0000 0000 */ + +/* CLKOUTx dividers */ +#define CLKOUT_DIV_2 0x00 /* 0b 000 */ +#define CLKOUT_DIV_4 0x01 /* 0b 001 */ +#define CLKOUT_DIV_8 0x02 /* 0b 010 */ +#define CLKOUT_DIV_16 0x03 /* 0b 011 */ +#define CLKOUT_DIV_32 0x04 /* 0b 100 */ +#define CLKOUT_DIV_64 0x05 /* 0b 101 */ +/* Values 0x06 and 0x07 will also set the CLKOUTx divider to 64. */ + +/* PRCM_CLKOCR CLKOUTx Control registers */ +#define PRCM_CLKOCR 0x1CC +#define PRCM_CLKOCR_CLKOUT0_SEL0_SHIFT 0 +#define PRCM_CLKOCR_CLKOUT0_SEL0_MASK BITS(0, 3) +#define PRCM_CLKOCR_CLKOUT0_SEL_SHIFT 4 +#define PRCM_CLKOCR_CLKOUT0_SEL_MASK BITS(4, 13) +#define PRCM_CLKOCR_CLKOUT1_SEL0_SHIFT 16 +#define PRCM_CLKOCR_CLKOUT1_SEL0_MASK BITS(16, 19) +#define PRCM_CLKOCR_CLKOUT1_SEL_SHIFT 20 +#define PRCM_CLKOCR_CLKOUT1_SEL_MASK BITS(20, 29) + +/* PRCM_CLKODIV CLKOUTx Dividers */ +#define PRCM_CLKODIV 0x188 +#define PRCM_CLKODIV_CLKOUT0_DIV_SHIFT 0 +#define PRCM_CLKODIV_CLKOUT0_DIV_MASK BITS(0, 2) +#define PRCM_CLKODIV_CLKOUT1_DIV_SHIFT 16 +#define PRCM_CLKODIV_CLKOUT1_DIV_MASK BITS(16, 18) + +#define PRCM_MMIP_LS_CLAMP_SET 0x420 +#define PRCM_MMIP_LS_CLAMP_CLR 0x424 +#define PRCM_DDR_SUBSYS_APE_MINBW 0x438 + +/* Miscellaneous unit registers */ +#define PRCM_DSI_SW_RESET 0x324 +#define PRCM_RESOUTN_SET_OFFSET 0x214 +#define PRCM_RESOUTN_CLR_OFFSET 0x218 + +/* APE - Modem Registers */ +#define PRCM_HOSTACCESS_REQ 0x334 +/* APE - Modem register bit maipulation */ +#define PRCM_HOSTACCESS_REQ_BIT BIT(0) +#define PRCM_APE_ACK 0x49c +#define PRCM_APE_ACK_BIT 0x01 + +/* Watchdog - mtimer registers */ +#define PRCM_TIMER0_RTOS_COMP1_OFFSET 0x4C +#define PRCM_TIMER0_RTOS_COUNTER_OFFSET 0x40 +#define PRCM_TIMER0_IRQ_EN_SET_OFFSET 0x70 +#define PRCM_TIMER0_IRQ_EN_CLR_OFFSET 0x6C +#define PRCM_TIMER0_IRQ_RTOS1_SET 0x08 +#define PRCM_TIMER0_IRQ_RTOS1_CLR 0x08 + +#endif diff --git a/drivers/mfd/db5500-prcmu.c b/drivers/mfd/db5500-prcmu.c index bb115b2f04e..b106632d03c 100644 --- a/drivers/mfd/db5500-prcmu.c +++ b/drivers/mfd/db5500-prcmu.c @@ -19,12 +19,21 @@ #include <linux/irq.h> #include <linux/jiffies.h> #include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/mfd/core.h> +#include <linux/regulator/db5500-prcmu.h> +#include <linux/regulator/machine.h> #include <linux/interrupt.h> #include <linux/mfd/dbx500-prcmu.h> #include <mach/hardware.h> #include <mach/irqs.h> #include <mach/db5500-regs.h> -#include "dbx500-prcmu-regs.h" +#include <mach/prcmu-debug.h> + +#include "db5500-prcmu-regs.h" + +#define PRCMU_FW_VERSION_OFFSET 0xA4 +#define PRCM_SW_RST_REASON (tcdm_base + 0xFF8) /* 2 bytes */ #define _PRCM_MB_HEADER (tcdm_base + 0xFE8) #define PRCM_REQ_MB0_HEADER (_PRCM_MB_HEADER + 0x0) @@ -64,6 +73,52 @@ #define PRCM_ACK_MB6 (tcdm_base + 0xF0C) #define PRCM_ACK_MB7 (tcdm_base + 0xF08) +/* Share info */ +#define PRCM_SHARE_INFO (tcdm_base + 0xEC8) + +#define PRCM_SHARE_INFO_HOTDOG (PRCM_SHARE_INFO + 62) + +/* Mailbox 0 REQs */ +#define PRCM_REQ_MB0_AP_POWER_STATE (PRCM_REQ_MB0 + 0x0) +#define PRCM_REQ_MB0_ULP_CLOCK_STATE (PRCM_REQ_MB0 + 0x1) +#define PRCM_REQ_MB0_AP_PLL_STATE (PRCM_REQ_MB0 + 0x2) +#define PRCM_REQ_MB0_DDR_STATE (PRCM_REQ_MB0 + 0x3) +#define PRCM_REQ_MB0_ESRAM0_STATE (PRCM_REQ_MB0 + 0x4) +#define PRCM_REQ_MB0_WAKEUP_DBB (PRCM_REQ_MB0 + 0x8) +#define PRCM_REQ_MB0_WAKEUP_ABB (PRCM_REQ_MB0 + 0xC) + +/* Mailbox 0 ACKs */ +#define PRCM_ACK_MB0_AP_PWRSTTR_STATUS (PRCM_ACK_MB0 + 0x0) +#define PRCM_ACK_MB0_READ_POINTER (PRCM_ACK_MB0 + 0x1) +#define PRCM_ACK_MB0_WAKEUP_0_DBB (PRCM_ACK_MB0 + 0x4) +#define PRCM_ACK_MB0_WAKEUP_0_ABB (PRCM_ACK_MB0 + 0x8) +#define PRCM_ACK_MB0_WAKEUP_1_DBB (PRCM_ACK_MB0 + 0x28) +#define PRCM_ACK_MB0_WAKEUP_1_ABB (PRCM_ACK_MB0 + 0x2C) +#define PRCM_ACK_MB0_EVENT_ABB_NUMBERS 20 + +/* Request mailbox 1 fields. */ +#define PRCM_REQ_MB1_ARM_OPP (PRCM_REQ_MB1 + 0x0) +#define PRCM_REQ_MB1_APE_OPP (PRCM_REQ_MB1 + 0x1) + +/* Mailbox 1 ACKs */ +#define PRCM_ACK_MB1_CURRENT_ARM_OPP (PRCM_ACK_MB1 + 0x0) +#define PRCM_ACK_MB1_CURRENT_APE_OPP (PRCM_ACK_MB1 + 0x1) +#define PRCM_ACK_MB1_ARM_VOLT_STATUS (PRCM_ACK_MB1 + 0x2) +#define PRCM_ACK_MB1_APE_VOLT_STATUS (PRCM_ACK_MB1 + 0x3) + +/* Mailbox 2 REQs */ +#define PRCM_REQ_MB2_EPOD_CLIENT (PRCM_REQ_MB2 + 0x0) +#define PRCM_REQ_MB2_EPOD_STATE (PRCM_REQ_MB2 + 0x1) +#define PRCM_REQ_MB2_CLK_CLIENT (PRCM_REQ_MB2 + 0x2) +#define PRCM_REQ_MB2_CLK_STATE (PRCM_REQ_MB2 + 0x3) +#define PRCM_REQ_MB2_PLL_CLIENT (PRCM_REQ_MB2 + 0x4) +#define PRCM_REQ_MB2_PLL_STATE (PRCM_REQ_MB2 + 0x5) + +/* Mailbox 2 ACKs */ +#define PRCM_ACK_MB2_EPOD_STATUS (PRCM_ACK_MB2 + 0x2) +#define PRCM_ACK_MB2_CLK_STATUS (PRCM_ACK_MB2 + 0x6) +#define PRCM_ACK_MB2_PLL_STATUS (PRCM_ACK_MB2 + 0xA) + enum mb_return_code { RC_SUCCESS, RC_FAIL, @@ -71,12 +126,58 @@ enum mb_return_code { /* Mailbox 0 headers. */ enum mb0_header { - /* request */ - RMB0H_PWR_STATE_TRANS = 1, - RMB0H_WAKE_UP_CFG, - RMB0H_RD_WAKE_UP_ACK, /* acknowledge */ - AMB0H_WAKE_UP = 1, + MB0H_WAKE_UP = 0, + /* request */ + MB0H_PWR_STATE_TRANS, + MB0H_WAKE_UP_CFG, + MB0H_RD_WAKE_UP_ACK, +}; + +/* Mailbox 1 headers.*/ +enum mb1_header { + MB1H_ARM_OPP = 1, + MB1H_APE_OPP, + MB1H_ARM_APE_OPP, +}; + +/* Mailbox 2 headers. */ +enum mb2_header { + MB2H_EPOD_REQUEST = 1, + MB2H_CLK_REQUEST, + MB2H_PLL_REQUEST, +}; + +/* Mailbox 3 headers. */ +enum mb3_header { + MB3H_REFCLK_REQUEST = 1, +}; + +enum sysclk_state { + SYSCLK_OFF, + SYSCLK_ON, +}; + +/* Mailbox 4 headers */ +enum mb4_header { + MB4H_CFG_HOTDOG = 7, + MB4H_CFG_HOTMON = 8, + MB4H_CFG_HOTPERIOD = 10, + MB4H_CGF_MODEM_RESET = 13, + MB4H_CGF_A9WDOG_EN_PREBARK = 14, + MB4H_CGF_A9WDOG_EN_NOPREBARK = 15, + MB4H_CGF_A9WDOG_DIS = 16, +}; + +/* Mailbox 4 ACK headers */ +enum mb4_ack_header { + MB4H_ACK_CFG_HOTDOG = 5, + MB4H_ACK_CFG_HOTMON = 6, + MB4H_ACK_CFG_HOTPERIOD = 8, + MB4H_ACK_CFG_MODEM_RESET = 11, + MB4H_ACK_CGF_A9WDOG_EN_PREBARK = 12, + MB4H_ACK_CGF_A9WDOG_EN_NOPREBARK = 13, + MB4H_ACK_CGF_A9WDOG_DIS = 14, }; /* Mailbox 5 headers. */ @@ -85,6 +186,69 @@ enum mb5_header { MB5H_I2C_READ, }; +enum db5500_arm_opp { + DB5500_ARM_100_OPP = 1, + DB5500_ARM_50_OPP, + DB5500_ARM_EXT_OPP, +}; + +enum db5500_ape_opp { + DB5500_APE_100_OPP = 1, + DB5500_APE_50_OPP +}; + +enum epod_state { + EPOD_OFF, + EPOD_ON, +}; +enum epod_onoffret_state { + EPOD_OOR_OFF, + EPOD_OOR_RET, + EPOD_OOR_ON, +}; +enum db5500_prcmu_pll { + DB5500_PLL_SOC0, + DB5500_PLL_SOC1, + DB5500_PLL_DDR, + DB5500_NUM_PLL_ID, +}; + +enum db5500_prcmu_clk { + DB5500_MSP1CLK, + DB5500_CDCLK, + DB5500_IRDACLK, + DB5500_TVCLK, + DB5500_NUM_CLK_CLIENTS, +}; + +enum on_off_ret { + OFF_ST, + RET_ST, + ON_ST, +}; + +enum db5500_ap_pwr_state { + DB5500_AP_SLEEP = 2, + DB5500_AP_DEEP_SLEEP, + DB5500_AP_IDLE, +}; + +/* Request mailbox 3 fields */ +#define PRCM_REQ_MB3_REFCLK_MGT (PRCM_REQ_MB3 + 0x0) + +/* Ack. mailbox 3 fields */ +#define PRCM_ACK_MB3_REFCLK_REQ (PRCM_ACK_MB3 + 0x0) + + +/* Request mailbox 4 fields */ +#define PRCM_REQ_MB4_HOTDOG_THRESHOLD (PRCM_REQ_MB4 + 32) +#define PRCM_REQ_MB4_HOT_PERIOD (PRCM_REQ_MB4 + 34) +#define PRCM_REQ_MB4_HOTMON_LOW (PRCM_REQ_MB4 + 36) +#define PRCM_REQ_MB4_HOTMON_HIGH (PRCM_REQ_MB4 + 38) + +/* Ack. mailbox 4 field */ +#define PRCM_ACK_MB4_REQUESTS (PRCM_ACK_MB4 + 0x0) + /* Request mailbox 5 fields. */ #define PRCM_REQ_MB5_I2C_SLAVE (PRCM_REQ_MB5 + 0) #define PRCM_REQ_MB5_I2C_REG (PRCM_REQ_MB5 + 1) @@ -105,11 +269,12 @@ enum mb5_header { #define PRCMU_RESET_DSIPLL 0x00004000 #define PRCMU_UNCLAMP_DSIPLL 0x00400800 -/* HDMI CLK MGT PLLSW=001 (PLLSOC0), PLLDIV=0x8, = 50 Mhz*/ -#define PRCMU_DSI_CLOCK_SETTING 0x00000128 +/* HDMI CLK MGT PLLSW=001 (PLLSOC0), PLLDIV=0xC, = 33.33 Mhz*/ +#define PRCMU_DSI_CLOCK_SETTING 0x0000012C /* TVCLK_MGT PLLSW=001 (PLLSOC0) PLLDIV=0x13, = 19.05 MHZ */ #define PRCMU_DSI_LP_CLOCK_SETTING 0x00000135 -#define PRCMU_PLLDSI_FREQ_SETTING 0x00020121 +/* PRCM_PLLDSI_FREQ R=4, N=1, D= 0x65 */ +#define PRCMU_PLLDSI_FREQ_SETTING 0x00040165 #define PRCMU_DSI_PLLOUT_SEL_SETTING 0x00000002 #define PRCMU_ENABLE_ESCAPE_CLOCK_DIV 0x03000201 #define PRCMU_DISABLE_ESCAPE_CLOCK_DIV 0x00000101 @@ -125,13 +290,176 @@ enum mb5_header { #define PRCMU_PLLDSI_LOCKP_LOCKED 0x3 /* + * Wakeups/IRQs + */ + +#define WAKEUP_BIT_RTC BIT(0) +#define WAKEUP_BIT_RTT0 BIT(1) +#define WAKEUP_BIT_RTT1 BIT(2) +#define WAKEUP_BIT_CD_IRQ BIT(3) +#define WAKEUP_BIT_SRP_TIM BIT(4) +#define WAKEUP_BIT_APE_REQ BIT(5) +#define WAKEUP_BIT_USB BIT(6) +#define WAKEUP_BIT_ABB BIT(7) +#define WAKEUP_BIT_LOW_POWER_AUDIO BIT(8) +#define WAKEUP_BIT_TEMP_SENSOR_LOW BIT(9) +#define WAKEUP_BIT_ARM BIT(10) +#define WAKEUP_BIT_AC_WAKE_ACK BIT(11) +#define WAKEUP_BIT_TEMP_SENSOR_HIGH BIT(12) +#define WAKEUP_BIT_MODEM_SW_RESET_REQ BIT(20) +#define WAKEUP_BIT_GPIO0 BIT(23) +#define WAKEUP_BIT_GPIO1 BIT(24) +#define WAKEUP_BIT_GPIO2 BIT(25) +#define WAKEUP_BIT_GPIO3 BIT(26) +#define WAKEUP_BIT_GPIO4 BIT(27) +#define WAKEUP_BIT_GPIO5 BIT(28) +#define WAKEUP_BIT_GPIO6 BIT(29) +#define WAKEUP_BIT_GPIO7 BIT(30) +#define WAKEUP_BIT_AC_REL_ACK BIT(30) + +/* + * This vector maps irq numbers to the bits in the bit field used in + * communication with the PRCMU firmware. + * + * The reason for having this is to keep the irq numbers contiguous even though + * the bits in the bit field are not. (The bits also have a tendency to move + * around, to further complicate matters.) + */ +#define IRQ_INDEX(_name) ((IRQ_DB5500_PRCMU_##_name) - IRQ_DB5500_PRCMU_BASE) +#define IRQ_ENTRY(_name)[IRQ_INDEX(_name)] = (WAKEUP_BIT_##_name) +static u32 prcmu_irq_bit[NUM_DB5500_PRCMU_WAKEUPS] = { + IRQ_ENTRY(RTC), + IRQ_ENTRY(RTT0), + IRQ_ENTRY(RTT1), + IRQ_ENTRY(CD_IRQ), + IRQ_ENTRY(SRP_TIM), + IRQ_ENTRY(APE_REQ), + IRQ_ENTRY(USB), + IRQ_ENTRY(ABB), + IRQ_ENTRY(LOW_POWER_AUDIO), + IRQ_ENTRY(TEMP_SENSOR_LOW), + IRQ_ENTRY(TEMP_SENSOR_HIGH), + IRQ_ENTRY(ARM), + IRQ_ENTRY(AC_WAKE_ACK), + IRQ_ENTRY(MODEM_SW_RESET_REQ), + IRQ_ENTRY(GPIO0), + IRQ_ENTRY(GPIO1), + IRQ_ENTRY(GPIO2), + IRQ_ENTRY(GPIO3), + IRQ_ENTRY(GPIO4), + IRQ_ENTRY(GPIO5), + IRQ_ENTRY(GPIO6), + IRQ_ENTRY(GPIO7), + IRQ_ENTRY(AC_REL_ACK), +}; + +#define VALID_WAKEUPS (BIT(NUM_PRCMU_WAKEUP_INDICES) - 1) +#define WAKEUP_ENTRY(_name)[PRCMU_WAKEUP_INDEX_##_name] = (WAKEUP_BIT_##_name) +static u32 prcmu_wakeup_bit[NUM_PRCMU_WAKEUP_INDICES] = { + WAKEUP_ENTRY(RTC), + WAKEUP_ENTRY(RTT0), + WAKEUP_ENTRY(RTT1), + WAKEUP_ENTRY(CD_IRQ), + WAKEUP_ENTRY(USB), + WAKEUP_ENTRY(ABB), + WAKEUP_ENTRY(ARM) +}; + +/* * mb0_transfer - state needed for mailbox 0 communication. - * @lock: The transaction lock. + * @lock The transaction lock. + * @dbb_irqs_lock lock used for (un)masking DBB wakeup interrupts + * @mask_work: Work structure used for (un)masking wakeup interrupts. + * @ac_wake_lock: mutex to lock modem_req and modem_rel + * @req: Request data that need to persist between requests. */ static struct { spinlock_t lock; + spinlock_t dbb_irqs_lock; + struct work_struct mask_work; + struct mutex ac_wake_lock; + struct { + u32 dbb_irqs; + u32 dbb_wakeups; + u32 abb_events; + } req; } mb0_transfer; + +/* + * mb1_transfer - state needed for mailbox 1 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @req_arm_opp Requested arm opp + * @req_ape_opp Requested ape opp + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + u8 req_arm_opp; + u8 req_ape_opp; + struct { + u8 header; + u8 arm_opp; + u8 ape_opp; + u8 arm_voltage_st; + u8 ape_voltage_st; + } ack; +} mb1_transfer; + +/* + * mb2_transfer - state needed for mailbox 2 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @req: Request data that need to persist between requests. + * @ack: Reply ("acknowledge") data. + */ +static struct { + struct mutex lock; + struct completion work; + struct { + u8 epod_st[DB5500_NUM_EPOD_ID]; + u8 pll_st[DB5500_NUM_PLL_ID]; + } req; + struct { + u8 header; + u8 status; + } ack; +} mb2_transfer; + +/* + * mb3_transfer - state needed for mailbox 3 communication. + * @sysclk_lock: A lock used to handle concurrent sysclk requests. + * @sysclk_work: Work structure used for sysclk requests. + * @req_st: Requested clock state. + * @ack: Acknowledgement data + */ +static struct { + struct mutex sysclk_lock; + struct completion sysclk_work; + enum sysclk_state req_st; + struct { + u8 header; + u8 status; + } ack; +} mb3_transfer; + +/* + * mb4_transfer - state needed for mailbox 4 communication. + * @lock: The transaction lock. + * @work: The transaction completion structure. + * @ack: Acknowledgement data + */ +static struct { + struct mutex lock; + struct completion work; + struct { + u8 header; + u8 status; + } ack; +} mb4_transfer; + /* * mb5_transfer - state needed for mailbox 5 communication. * @lock: The transaction lock. @@ -148,9 +476,825 @@ static struct { } ack; } mb5_transfer; -/* PRCMU TCDM base IO address. */ +/* Spinlocks */ +static DEFINE_SPINLOCK(clkout_lock); + +/* PRCMU TCDM base IO address */ static __iomem void *tcdm_base; +/* PRCMU MTIMER base IO address */ +static __iomem void *mtimer_base; + +struct clk_mgt { + unsigned int offset; + u32 pllsw; + u32 div; + bool scalable; + bool force50; +}; + +/* PRCMU Firmware Details */ +static struct { + u16 board; + u8 fw_version; + u8 api_version; +} prcmu_version; + +static struct { + u32 timeout; + bool enabled; +} a9wdog_timer; + +static DEFINE_SPINLOCK(clk_mgt_lock); + +#define CLK_MGT_ENTRY(_name, _scalable)[PRCMU_##_name] = { \ + .offset = DB5500_PRCM_##_name##_MGT, \ + .scalable = _scalable, \ +} + +static struct clk_mgt clk_mgt[PRCMU_NUM_REG_CLOCKS] = { + CLK_MGT_ENTRY(SGACLK, true), + CLK_MGT_ENTRY(UARTCLK, false), + CLK_MGT_ENTRY(MSP02CLK, false), + CLK_MGT_ENTRY(I2CCLK, false), + [PRCMU_SDMMCCLK] { + .offset = DB5500_PRCM_SDMMCCLK_MGT, + .force50 = true, + .scalable = false, + + }, + [PRCMU_SPARE1CLK] { + .offset = DB5500_PRCM_SPARE1CLK_MGT, + .force50 = true, + .scalable = false, + + }, + CLK_MGT_ENTRY(PER1CLK, false), + CLK_MGT_ENTRY(PER2CLK, true), + CLK_MGT_ENTRY(PER3CLK, true), + CLK_MGT_ENTRY(PER5CLK, false), /* used for SPI */ + CLK_MGT_ENTRY(PER6CLK, true), + CLK_MGT_ENTRY(PWMCLK, false), + CLK_MGT_ENTRY(IRDACLK, false), + CLK_MGT_ENTRY(IRRCCLK, false), + CLK_MGT_ENTRY(HDMICLK, false), + CLK_MGT_ENTRY(APEATCLK, false), + CLK_MGT_ENTRY(APETRACECLK, true), + CLK_MGT_ENTRY(MCDECLK, true), + CLK_MGT_ENTRY(DSIALTCLK, false), + CLK_MGT_ENTRY(DMACLK, true), + CLK_MGT_ENTRY(B2R2CLK, true), + CLK_MGT_ENTRY(TVCLK, false), + CLK_MGT_ENTRY(RNGCLK, false), + CLK_MGT_ENTRY(SIACLK, false), + CLK_MGT_ENTRY(SVACLK, false), + CLK_MGT_ENTRY(ACLK, true), +}; + +static atomic_t modem_req_state = ATOMIC_INIT(0); + +bool db5500_prcmu_is_modem_requested(void) +{ + return (atomic_read(&modem_req_state) != 0); +} + +/** + * prcmu_modem_req - APE requests Modem to wake up + * + * Whenever APE wants to send message to the modem, it will have to call this + * function to make sure that modem is awake. + */ +void prcmu_modem_req(void) +{ + u32 val; + + mutex_lock(&mb0_transfer.ac_wake_lock); + + val = readl(_PRCMU_BASE + PRCM_HOSTACCESS_REQ); + if (val & PRCM_HOSTACCESS_REQ_BIT) + goto unlock_and_return; + + writel((val | PRCM_HOSTACCESS_REQ_BIT), + (_PRCMU_BASE + PRCM_HOSTACCESS_REQ)); + atomic_set(&modem_req_state, 1); + +unlock_and_return: + mutex_unlock(&mb0_transfer.ac_wake_lock); + +} + +/** + * prcmu_modem_rel - APE has no more messages to send and hence releases modem. + * + * APE to Modem communication is initiated by modem_req and once the + * communication is completed, APE sends modem_rel to complete the protocol. + */ +void prcmu_modem_rel(void) +{ + u32 val; + + mutex_lock(&mb0_transfer.ac_wake_lock); + + val = readl(_PRCMU_BASE + PRCM_HOSTACCESS_REQ); + if (!(val & PRCM_HOSTACCESS_REQ_BIT)) + goto unlock_and_return; + + writel((val & ~PRCM_HOSTACCESS_REQ_BIT), + (_PRCMU_BASE + PRCM_HOSTACCESS_REQ)); + + atomic_set(&modem_req_state, 0); + +unlock_and_return: + mutex_unlock(&mb0_transfer.ac_wake_lock); +} + +/** + * prcm_ape_ack - send an acknowledgement to modem + * + * On ape receiving ape_req, APE will have to acknowledge for the interrupt + * received. This function will send the acknowledgement by writing to the + * prcmu register and an interrupt is trigerred to modem. + */ +void prcmu_ape_ack(void) +{ + writel(PRCM_APE_ACK_BIT, (_PRCMU_BASE + PRCM_APE_ACK)); +} + +/** + * db5500_prcmu_modem_reset - Assert a Reset on modem + * + * This function will assert a reset request to the modem. Prior to that + * PRCM_HOSTACCESS_REQ must be '0'. + */ +void db5500_prcmu_modem_reset(void) +{ + mutex_lock(&mb4_transfer.lock); + + /* PRCM_HOSTACCESS_REQ = 0, before asserting a reset */ + prcmu_modem_rel(); + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writeb(MB4H_CGF_MODEM_RESET, PRCM_REQ_MB4_HEADER); + writel(MBOX_BIT(4), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + wait_for_completion(&mb4_transfer.work); + if (mb4_transfer.ack.status != RC_SUCCESS || + mb4_transfer.ack.header != MB4H_CGF_MODEM_RESET) + printk(KERN_ERR, + "ACK not received for modem reset interrupt\n"); + mutex_unlock(&mb4_transfer.lock); +} + +/** + * prcmu_config_clkout - Configure one of the programmable clock outputs. + * @clkout: The CLKOUT number (0 or 1). + * @source: Clock source. + * @div: The divider to be applied. + * + * Configures one of the programmable clock outputs (CLKOUTs). + */ +int prcmu_config_clkout(u8 clkout, u8 source, u8 div) +{ + static bool configured[2] = {false, false}; + int r = 0; + unsigned long flags; + u32 sel_val; + u32 div_val; + u32 sel_bits; + u32 div_bits; + u32 sel_mask; + u32 div_mask; + u8 sel0 = CLKOUT_SEL0_SEL_CLK; + u16 sel = 0; + + BUG_ON(clkout > DB5500_CLKOUT1); + BUG_ON(source > DB5500_CLKOUT_IRDACLK); + BUG_ON(div > 7); + + switch (source) { + case DB5500_CLKOUT_REF_CLK_SEL0: + sel0 = CLKOUT_SEL0_REF_CLK; + break; + case DB5500_CLKOUT_RTC_CLK0_SEL0: + sel0 = CLKOUT_SEL0_RTC_CLK0; + break; + case DB5500_CLKOUT_ULP_CLK_SEL0: + sel0 = CLKOUT_SEL0_ULP_CLK; + break; + case DB5500_CLKOUT_STATIC0: + sel = CLKOUT_SEL_STATIC0; + break; + case DB5500_CLKOUT_REFCLK: + sel = CLKOUT_SEL_REFCLK; + break; + case DB5500_CLKOUT_ULPCLK: + sel = CLKOUT_SEL_ULPCLK; + break; + case DB5500_CLKOUT_ARMCLK: + sel = CLKOUT_SEL_ARMCLK; + break; + case DB5500_CLKOUT_SYSACC0CLK: + sel = CLKOUT_SEL_SYSACC0CLK; + break; + case DB5500_CLKOUT_SOC0PLLCLK: + sel = CLKOUT_SEL_SOC0PLLCLK; + break; + case DB5500_CLKOUT_SOC1PLLCLK: + sel = CLKOUT_SEL_SOC1PLLCLK; + break; + case DB5500_CLKOUT_DDRPLLCLK: + sel = CLKOUT_SEL_DDRPLLCLK; + break; + case DB5500_CLKOUT_TVCLK: + sel = CLKOUT_SEL_TVCLK; + break; + case DB5500_CLKOUT_IRDACLK: + sel = CLKOUT_SEL_IRDACLK; + break; + } + + switch (clkout) { + case DB5500_CLKOUT0: + sel_mask = PRCM_CLKOCR_CLKOUT0_SEL0_MASK | + PRCM_CLKOCR_CLKOUT0_SEL_MASK; + sel_bits = ((sel0 << PRCM_CLKOCR_CLKOUT0_SEL0_SHIFT) | + (sel << PRCM_CLKOCR_CLKOUT0_SEL_SHIFT)); + div_mask = PRCM_CLKODIV_CLKOUT0_DIV_MASK; + div_bits = div << PRCM_CLKODIV_CLKOUT0_DIV_SHIFT; + break; + case DB5500_CLKOUT1: + sel_mask = PRCM_CLKOCR_CLKOUT1_SEL0_MASK | + PRCM_CLKOCR_CLKOUT1_SEL_MASK; + sel_bits = ((sel0 << PRCM_CLKOCR_CLKOUT1_SEL0_SHIFT) | + (sel << PRCM_CLKOCR_CLKOUT1_SEL_SHIFT)); + div_mask = PRCM_CLKODIV_CLKOUT1_DIV_MASK; + div_bits = div << PRCM_CLKODIV_CLKOUT1_DIV_SHIFT; + break; + } + + spin_lock_irqsave(&clkout_lock, flags); + + if (configured[clkout]) { + r = -EINVAL; + goto unlock_and_return; + } + + sel_val = readl(_PRCMU_BASE + PRCM_CLKOCR); + writel((sel_bits | (sel_val & ~sel_mask)), + (_PRCMU_BASE + PRCM_CLKOCR)); + + div_val = readl(_PRCMU_BASE + PRCM_CLKODIV); + writel((div_bits | (div_val & ~div_mask)), + (_PRCMU_BASE + PRCM_CLKODIV)); + + configured[clkout] = true; + +unlock_and_return: + spin_unlock_irqrestore(&clkout_lock, flags); + + return r; +} + +static int request_sysclk(bool enable) +{ + int r; + + r = 0; + mutex_lock(&mb3_transfer.sysclk_lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(3)) + cpu_relax(); + + if (enable) + mb3_transfer.req_st = SYSCLK_ON; + else + mb3_transfer.req_st = SYSCLK_OFF; + + writeb(mb3_transfer.req_st, (PRCM_REQ_MB3_REFCLK_MGT)); + + writeb(MB3H_REFCLK_REQUEST, (PRCM_REQ_MB3_HEADER)); + writel(MBOX_BIT(3), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + /* + * The firmware only sends an ACK if we want to enable the + * SysClk, and it succeeds. + */ + if (!wait_for_completion_timeout(&mb3_transfer.sysclk_work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", + __func__); + r = -EIO; + WARN(1, "Failed to set sysclk"); + goto unlock_and_return; + } + + if ((mb3_transfer.ack.header != MB3H_REFCLK_REQUEST) || + (mb3_transfer.ack.status != mb3_transfer.req_st)) { + r = -EIO; + } + +unlock_and_return: + mutex_unlock(&mb3_transfer.sysclk_lock); + + return r; +} + +static int request_timclk(bool enable) +{ + u32 val = (PRCM_TCR_DOZE_MODE | PRCM_TCR_TENSEL_MASK); + + if (!enable) + val |= PRCM_TCR_STOP_TIMERS; + writel(val, _PRCMU_BASE + PRCM_TCR); + + return 0; +} + +static int request_clk(u8 clock, bool enable) +{ + int r = 0; + + BUG_ON(clock >= DB5500_NUM_CLK_CLIENTS); + + mutex_lock(&mb2_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + cpu_relax(); + + /* fill in mailbox */ + writeb(clock, PRCM_REQ_MB2_CLK_CLIENT); + writeb(enable, PRCM_REQ_MB2_CLK_STATE); + + writeb(MB2H_CLK_REQUEST, PRCM_REQ_MB2_HEADER); + + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: request_clk() failed.\n"); + r = -EIO; + WARN(1, "Failed in request_clk"); + goto unlock_and_return; + } + if (mb2_transfer.ack.status != RC_SUCCESS || + mb2_transfer.ack.header != MB2H_CLK_REQUEST) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + return r; +} + +static int request_reg_clock(u8 clock, bool enable) +{ + u32 val; + unsigned long flags; + + WARN_ON(!clk_mgt[clock].offset); + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + val = readl(_PRCMU_BASE + clk_mgt[clock].offset); + if (enable) { + val |= (PRCM_CLK_MGT_CLKEN | clk_mgt[clock].pllsw); + } else { + clk_mgt[clock].pllsw = (val & PRCM_CLK_MGT_CLKPLLSW_MASK); + val &= ~(PRCM_CLK_MGT_CLKEN | PRCM_CLK_MGT_CLKPLLSW_MASK); + } + writel(val, (_PRCMU_BASE + clk_mgt[clock].offset)); + + /* Release the HW semaphore. */ + writel(0, _PRCMU_BASE + PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + + return 0; +} + +/* + * request_pll() - Request for a pll to be enabled or disabled. + * @pll: The pll for which the request is made. + * @enable: Whether the clock should be enabled (true) or disabled (false). + * + * This function should only be used by the clock implementation. + * Do not use it from any other place! + */ +static int request_pll(u8 pll, bool enable) +{ + int r = 0; + + BUG_ON(pll >= DB5500_NUM_PLL_ID); + mutex_lock(&mb2_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + cpu_relax(); + + mb2_transfer.req.pll_st[pll] = enable; + + /* fill in mailbox */ + writeb(pll, PRCM_REQ_MB2_PLL_CLIENT); + writeb(mb2_transfer.req.pll_st[pll], PRCM_REQ_MB2_PLL_STATE); + + writeb(MB2H_PLL_REQUEST, PRCM_REQ_MB2_HEADER); + + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: set_pll() failed.\n"); + r = -EIO; + WARN(1, "Failed to set pll"); + goto unlock_and_return; + } + if (mb2_transfer.ack.status != RC_SUCCESS || + mb2_transfer.ack.header != MB2H_PLL_REQUEST) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + + return r; +} + +/** + * db5500_prcmu_request_clock() - Request for a clock to be enabled or disabled. + * @clock: The clock for which the request is made. + * @enable: Whether the clock should be enabled (true) or disabled (false). + * + * This function should only be used by the clock implementation. + * Do not use it from any other place! + */ +int db5500_prcmu_request_clock(u8 clock, bool enable) +{ + /* MSP1 & CD clocks are handled by FW */ + if (clock == PRCMU_MSP1CLK) + return request_clk(DB5500_MSP1CLK, enable); + else if (clock == PRCMU_CDCLK) + return request_clk(DB5500_CDCLK, enable); + else if (clock == PRCMU_IRDACLK) + return request_clk(DB5500_IRDACLK, enable); + else if (clock < PRCMU_NUM_REG_CLOCKS) + return request_reg_clock(clock, enable); + else if (clock == PRCMU_TIMCLK) + return request_timclk(enable); + else if (clock == PRCMU_PLLSOC0) + return request_pll(DB5500_PLL_SOC0, enable); + else if (clock == PRCMU_PLLSOC1) + return request_pll(DB5500_PLL_SOC1, enable); + else if (clock == PRCMU_PLLDDR) + return request_pll(DB5500_PLL_DDR, enable); + else if (clock == PRCMU_SYSCLK) + return request_sysclk(enable); + else + return -EINVAL; +} + +/* This function should only be called while mb0_transfer.lock is held. */ +static void config_wakeups(void) +{ + static u32 last_dbb_events; + static u32 last_abb_events; + u32 dbb_events; + u32 abb_events; + + dbb_events = mb0_transfer.req.dbb_irqs | mb0_transfer.req.dbb_wakeups; + + abb_events = mb0_transfer.req.abb_events; + + if ((dbb_events == last_dbb_events) && (abb_events == last_abb_events)) + return; + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + + writel(dbb_events, PRCM_REQ_MB0_WAKEUP_DBB); + writel(abb_events, PRCM_REQ_MB0_WAKEUP_ABB); + writeb(MB0H_WAKE_UP_CFG, PRCM_REQ_MB0_HEADER); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + last_dbb_events = dbb_events; + last_abb_events = abb_events; +} + +int db5500_prcmu_config_esram0_deep_sleep(u8 state) +{ + unsigned long flags; + + if ((state > ESRAM0_DEEP_SLEEP_STATE_RET) || + (state < ESRAM0_DEEP_SLEEP_STATE_OFF)) + return -EINVAL; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + if (state == ESRAM0_DEEP_SLEEP_STATE_RET) + writeb(RET_ST, PRCM_REQ_MB0_ESRAM0_STATE); + else + writeb(OFF_ST, PRCM_REQ_MB0_ESRAM0_STATE); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); + + return 0; +} + +int db5500_prcmu_set_power_state(u8 state, bool keep_ulp_clk, bool keep_ap_pll) +{ + int r = 0; + unsigned long flags; + + /* Deep Idle is not supported in DB5500 */ + BUG_ON((state < PRCMU_AP_SLEEP) || (state >= PRCMU_AP_DEEP_IDLE)); + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + cpu_relax(); + + switch (state) { + case PRCMU_AP_IDLE: + writeb(DB5500_AP_IDLE, PRCM_REQ_MB0_AP_POWER_STATE); + /* TODO: Can be high latency */ + writeb(DDR_PWR_STATE_UNCHANGED, PRCM_REQ_MB0_DDR_STATE); + break; + case PRCMU_AP_SLEEP: + writeb(DB5500_AP_SLEEP, PRCM_REQ_MB0_AP_POWER_STATE); + break; + case PRCMU_AP_DEEP_SLEEP: + writeb(DB5500_AP_DEEP_SLEEP, PRCM_REQ_MB0_AP_POWER_STATE); + break; + default: + r = -EINVAL; + goto unlock_return; + } + writeb((keep_ap_pll ? 1 : 0), PRCM_REQ_MB0_AP_PLL_STATE); + writeb((keep_ulp_clk ? 1 : 0), PRCM_REQ_MB0_ULP_CLOCK_STATE); + + writeb(MB0H_PWR_STATE_TRANS, PRCM_REQ_MB0_HEADER); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + +unlock_return: + spin_unlock_irqrestore(&mb0_transfer.lock, flags); + + return r; +} + +u8 db5500_prcmu_get_power_state_result(void) +{ + u8 status = readb_relaxed(PRCM_ACK_MB0_AP_PWRSTTR_STATUS); + + /* + * Callers expect all the status values to match 8500. Adjust for + * PendingReq_Er (0x2b). + */ + if (status == 0x2b) + status = PRCMU_PRCMU2ARMPENDINGIT_ER; + + return status; +} + +void db5500_prcmu_enable_wakeups(u32 wakeups) +{ + unsigned long flags; + u32 bits; + int i; + + BUG_ON(wakeups != (wakeups & VALID_WAKEUPS)); + + for (i = 0, bits = 0; i < NUM_PRCMU_WAKEUP_INDICES; i++) { + if (wakeups & BIT(i)) { + if (prcmu_wakeup_bit[i] == 0) + WARN(1, "WAKEUP NOT SUPPORTED"); + else + bits |= prcmu_wakeup_bit[i]; + } + } + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + mb0_transfer.req.dbb_wakeups = bits; + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +void db5500_prcmu_config_abb_event_readout(u32 abb_events) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + mb0_transfer.req.abb_events = abb_events; + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +void db5500_prcmu_get_abb_event_buffer(void __iomem **buf) +{ + if (readb(PRCM_ACK_MB0_READ_POINTER) & 1) + *buf = (PRCM_ACK_MB0_WAKEUP_1_ABB); + else + *buf = (PRCM_ACK_MB0_WAKEUP_0_ABB); +} + +/* This function should be called with lock */ +static int mailbox4_request(u8 mb4_request, u8 ack_request) +{ + int ret = 0; + + writeb(mb4_request, PRCM_REQ_MB4_HEADER); + writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + + if (!wait_for_completion_timeout(&mb4_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: MB4 request %d failed", mb4_request); + ret = -EIO; + WARN(1, "prcmu: failed mb4 request"); + goto failed; + } + + if (mb4_transfer.ack.header != ack_request || + mb4_transfer.ack.status != RC_SUCCESS) + ret = -EIO; +failed: + return ret; +} + +int db5500_prcmu_get_hotdog(void) +{ + return readw(PRCM_SHARE_INFO_HOTDOG); +} + +int db5500_prcmu_config_hotdog(u8 threshold) +{ + int r = 0; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writew(threshold, PRCM_REQ_MB4_HOTDOG_THRESHOLD); + r = mailbox4_request(MB4H_CFG_HOTDOG, MB4H_ACK_CFG_HOTDOG); + + mutex_unlock(&mb4_transfer.lock); + + return r; +} + +int db5500_prcmu_config_hotmon(u8 low, u8 high) +{ + int r = 0; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writew(low, PRCM_REQ_MB4_HOTMON_LOW); + writew(high, PRCM_REQ_MB4_HOTMON_HIGH); + + r = mailbox4_request(MB4H_CFG_HOTMON, MB4H_ACK_CFG_HOTMON); + + mutex_unlock(&mb4_transfer.lock); + + return r; +} + +static int config_hot_period(u16 val) +{ + int r = 0; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + writew(val, PRCM_REQ_MB4_HOT_PERIOD); + r = mailbox4_request(MB4H_CFG_HOTPERIOD, MB4H_ACK_CFG_HOTPERIOD); + + mutex_unlock(&mb4_transfer.lock); + + return r; +} + +/* + * period in milli seconds + */ +int db5500_prcmu_start_temp_sense(u16 period) +{ + if (period == 0xFFFF) + return -EINVAL; + + return config_hot_period(period); +} + +int db5500_prcmu_stop_temp_sense(void) +{ + return config_hot_period(0xFFFF); +} + +static int prcmu_a9wdog(u8 req, u8 ack) +{ + int r = 0; + + mutex_lock(&mb4_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(4)) + cpu_relax(); + + r = mailbox4_request(req, ack); + + mutex_unlock(&mb4_transfer.lock); + + return r; +} + +static void prcmu_a9wdog_set_interrupt(bool enable) +{ + if (enable) { + writel(PRCM_TIMER0_IRQ_RTOS1_SET, + (mtimer_base + PRCM_TIMER0_IRQ_EN_SET_OFFSET)); + } else { + writel(PRCM_TIMER0_IRQ_RTOS1_CLR, + (mtimer_base + PRCM_TIMER0_IRQ_EN_CLR_OFFSET)); + } +} + +static void prcmu_a9wdog_set_timeout(u32 timeout) +{ + u32 comp_timeout; + + comp_timeout = readl(mtimer_base + PRCM_TIMER0_RTOS_COUNTER_OFFSET) + + timeout; + writel(comp_timeout, mtimer_base + PRCM_TIMER0_RTOS_COMP1_OFFSET); +} + +int db5500_prcmu_config_a9wdog(u8 num, bool sleep_auto_off) +{ + /* + * Sleep auto off feature is not supported. Resume and + * suspend will be handled by watchdog driver. + */ + return 0; +} + +int db5500_prcmu_enable_a9wdog(u8 id) +{ + int r = 0; + + if (a9wdog_timer.enabled) + return -EPERM; + + prcmu_a9wdog_set_interrupt(true); + + r = prcmu_a9wdog(MB4H_CGF_A9WDOG_EN_PREBARK, + MB4H_ACK_CGF_A9WDOG_EN_PREBARK); + if (!r) + a9wdog_timer.enabled = true; + else + prcmu_a9wdog_set_interrupt(false); + + return r; +} + +int db5500_prcmu_disable_a9wdog(u8 id) +{ + if (!a9wdog_timer.enabled) + return -EPERM; + + prcmu_a9wdog_set_interrupt(false); + + a9wdog_timer.enabled = false; + + return prcmu_a9wdog(MB4H_CGF_A9WDOG_DIS, + MB4H_ACK_CGF_A9WDOG_DIS); +} + +int db5500_prcmu_kick_a9wdog(u8 id) +{ + int r = 0; + + if (a9wdog_timer.enabled) + prcmu_a9wdog_set_timeout(a9wdog_timer.timeout); + else + r = -EPERM; + + return r; +} + +int db5500_prcmu_load_a9wdog(u8 id, u32 timeout) +{ + if (a9wdog_timer.enabled) + return -EPERM; + + prcmu_a9wdog_set_timeout(timeout); + a9wdog_timer.timeout = timeout; + + return 0; +} + /** * db5500_prcmu_abb_read() - Read register value(s) from the ABB. * @slave: The I2C slave address. @@ -170,14 +1314,14 @@ int db5500_prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) mutex_lock(&mb5_transfer.lock); - while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) cpu_relax(); writeb(slave, PRCM_REQ_MB5_I2C_SLAVE); writeb(reg, PRCM_REQ_MB5_I2C_REG); writeb(size, PRCM_REQ_MB5_I2C_SIZE); writeb(MB5H_I2C_READ, PRCM_REQ_MB5_HEADER); - writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); + writel(MBOX_BIT(5), _PRCMU_BASE + PRCM_MBOX_CPU_SET); wait_for_completion(&mb5_transfer.work); r = 0; @@ -211,7 +1355,7 @@ int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) mutex_lock(&mb5_transfer.lock); - while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(5)) cpu_relax(); writeb(slave, PRCM_REQ_MB5_I2C_SLAVE); writeb(reg, PRCM_REQ_MB5_I2C_REG); @@ -219,7 +1363,7 @@ int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) memcpy_toio(PRCM_REQ_MB5_I2C_DATA, value, size); writeb(MB5H_I2C_WRITE, PRCM_REQ_MB5_HEADER); - writel(MBOX_BIT(5), PRCM_MBOX_CPU_SET); + writel(MBOX_BIT(5), _PRCMU_BASE + PRCM_MBOX_CPU_SET); wait_for_completion(&mb5_transfer.work); if ((mb5_transfer.ack.header == MB5H_I2C_WRITE) && @@ -233,42 +1377,385 @@ int db5500_prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size) return r; } +/** + * db5500_prcmu_set_arm_opp - set the appropriate ARM OPP + * @opp: The new ARM operating point to which transition is to be made + * Returns: 0 on success, non-zero on failure + * + * This function sets the the operating point of the ARM. + */ +int db5500_prcmu_set_arm_opp(u8 opp) +{ + int r; + u8 db5500_opp; + + r = 0; + + switch (opp) { + case ARM_EXTCLK: + db5500_opp = DB5500_ARM_EXT_OPP; + break; + case ARM_50_OPP: + db5500_opp = DB5500_ARM_50_OPP; + break; + case ARM_100_OPP: + db5500_opp = DB5500_ARM_100_OPP; + break; + default: + pr_err("prcmu: %s() received wrong opp value: %d\n", + __func__, opp); + r = -EINVAL; + goto bailout; + } + + mutex_lock(&mb1_transfer.lock); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_ARM_OPP, PRCM_REQ_MB1_HEADER); + + writeb(db5500_opp, PRCM_REQ_MB1_ARM_OPP); + writel(MBOX_BIT(1), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + if (!wait_for_completion_timeout(&mb1_transfer.work, + msecs_to_jiffies(20000))) { + r = -EIO; + WARN(1, "prcmu: failed to set arm opp"); + goto unlock_and_return; + } + + if (mb1_transfer.ack.header != MB1H_ARM_OPP || + (mb1_transfer.ack.arm_opp != db5500_opp) || + (mb1_transfer.ack.arm_voltage_st != RC_SUCCESS)) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb1_transfer.lock); +bailout: + if (!r) + prcmu_debug_arm_opp_log(opp); + return r; +} + +static void __init prcmu_ape_clocks_init(void) +{ + u8 opp = db5500_prcmu_get_ape_opp(); + unsigned long flags; + int i; + + WARN(opp != APE_100_OPP, "%s: Initial APE OPP (%u) not 100%%?\n", + __func__, opp); + + for (i = 0; i < PRCMU_NUM_REG_CLOCKS; i++) { + struct clk_mgt *clkmgt = &clk_mgt[i]; + u32 clkval; + u32 div; + + if (!clkmgt->scalable && !clkmgt->force50) + continue; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + clkval = readl(_PRCMU_BASE + clkmgt->offset); + div = clkval & PRCM_CLK_MGT_CLKPLLDIV_MASK; + div >>= PRCM_CLK_MGT_CLKPLLDIV_SHIFT; + + if (clkmgt->force50) { + div *= 2; + + clkval &= ~PRCM_CLK_MGT_CLKPLLDIV_MASK; + clkval |= div << PRCM_CLK_MGT_CLKPLLDIV_SHIFT; + writel(clkval, _PRCMU_BASE + clkmgt->offset); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + continue; + } + + spin_unlock_irqrestore(&clk_mgt_lock, flags); + + clkmgt->div = div; + if (!div) + pr_err("%s: scalable clock at offset %#x has zero divisor\n", + __func__, clkmgt->offset); + } +} + +static void prcmu_ape_clocks_scale(u8 opp) +{ + unsigned long irqflags; + unsigned int i; + u32 clkval; + + /* + * Note: calling printk() under the following lock can cause lock + * recursion via clk_enable() for the console UART! + */ + spin_lock_irqsave(&clk_mgt_lock, irqflags); + + /* take a lock on HW (HWSEM)*/ + while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + for (i = 0; i < PRCMU_NUM_REG_CLOCKS; i++) { + u32 divval; + + if (!clk_mgt[i].scalable) + continue; + + clkval = readl(_PRCMU_BASE + clk_mgt[i].offset); + divval = clk_mgt[i].div; + + pr_debug("PRCMU: reg %#x prev clk = 0x%x stored div = 0x%x\n", + clk_mgt[i].offset, clkval, divval); + + if (opp == DB5500_APE_50_OPP) + divval *= 2; + + clkval &= ~PRCM_CLK_MGT_CLKPLLDIV_MASK; + clkval |= divval << PRCM_CLK_MGT_CLKPLLDIV_SHIFT; + + pr_debug("PRCMU: wr 0x%x in reg 0x%x\n", + clkval, clk_mgt[i].offset); + + writel(clkval, _PRCMU_BASE + clk_mgt[i].offset); + } + + /* release lock */ + writel(0, (_PRCMU_BASE + PRCM_SEM)); + + spin_unlock_irqrestore(&clk_mgt_lock, irqflags); +} +/* Divide the frequency of certain clocks by 2 for APE_50_PARTLY_25_OPP. */ +static void request_even_slower_clocks(bool enable) +{ + void __iomem *clock_reg[] = { + (_PRCMU_BASE + DB5500_PRCM_ACLK_MGT), + (_PRCMU_BASE + DB5500_PRCM_DMACLK_MGT) + }; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&clk_mgt_lock, flags); + + /* Grab the HW semaphore. */ + while ((readl(_PRCMU_BASE + PRCM_SEM) & PRCM_SEM_PRCM_SEM) != 0) + cpu_relax(); + + for (i = 0; i < ARRAY_SIZE(clock_reg); i++) { + u32 val; + u32 div; + + val = readl(clock_reg[i]); + div = (val & PRCM_CLK_MGT_CLKPLLDIV_MASK); + if (enable) { + if ((div <= 1) || (div > 15)) { + pr_err("prcmu: Bad clock divider %d in %s\n", + div, __func__); + goto unlock_and_return; + } + div <<= 1; + } else { + if (div <= 2) + goto unlock_and_return; + div >>= 1; + } + val = ((val & ~PRCM_CLK_MGT_CLKPLLDIV_MASK) | + (div & PRCM_CLK_MGT_CLKPLLDIV_MASK)); + writel(val, clock_reg[i]); + } + +unlock_and_return: + /* Release the HW semaphore. */ + writel(0, _PRCMU_BASE + PRCM_SEM); + + spin_unlock_irqrestore(&clk_mgt_lock, flags); +} +int db5500_prcmu_set_ape_opp(u8 opp) +{ + int ret = 0; + u8 db5500_opp; + if (opp == mb1_transfer.req_ape_opp) + return 0; + + switch (opp) { + case APE_100_OPP: + db5500_opp = DB5500_APE_100_OPP; + break; + case APE_50_OPP: + case APE_50_PARTLY_25_OPP: + db5500_opp = DB5500_APE_50_OPP; + break; + default: + pr_err("prcmu: %s() received wrong opp value: %d\n", + __func__, opp); + ret = -EINVAL; + goto bailout; + } + + mutex_lock(&mb1_transfer.lock); + if (mb1_transfer.req_ape_opp == APE_50_PARTLY_25_OPP) + request_even_slower_clocks(false); + if ((opp != APE_100_OPP) && (mb1_transfer.req_ape_opp != APE_100_OPP)) + goto skip_message; + + prcmu_ape_clocks_scale(db5500_opp); + + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) + cpu_relax(); + + writeb(MB1H_APE_OPP, PRCM_REQ_MB1_HEADER); + writeb(db5500_opp, PRCM_REQ_MB1_APE_OPP); + writel(MBOX_BIT(1), (_PRCMU_BASE + PRCM_MBOX_CPU_SET)); + + if (!wait_for_completion_timeout(&mb1_transfer.work, + msecs_to_jiffies(20000))) { + ret = -EIO; + WARN(1, "prcmu: failed to set ape opp to %u", opp); + goto unlock_and_return; + } + + if (mb1_transfer.ack.header != MB1H_APE_OPP || + (mb1_transfer.ack.ape_opp != db5500_opp) || + (mb1_transfer.ack.arm_voltage_st != RC_SUCCESS)) + ret = -EIO; + +skip_message: + if ((!ret && (opp == APE_50_PARTLY_25_OPP)) || + (ret && (mb1_transfer.req_ape_opp == APE_50_PARTLY_25_OPP))) + request_even_slower_clocks(true); + if (!ret) + mb1_transfer.req_ape_opp = opp; +unlock_and_return: + mutex_unlock(&mb1_transfer.lock); +bailout: + return ret; +} + +int db5500_prcmu_get_ape_opp(void) +{ + u8 opp = readb(PRCM_ACK_MB1_CURRENT_APE_OPP); + + switch (opp) { + case DB5500_APE_100_OPP: + return APE_100_OPP; + case DB5500_APE_50_OPP: + return APE_50_OPP; + default: + pr_err("prcmu: %s() read unknown opp value: %d\n", + __func__, opp); + return APE_100_OPP; + } +} + +int db5500_prcmu_get_ddr_opp(void) +{ + return readb(_PRCMU_BASE + PRCM_DDR_SUBSYS_APE_MINBW); +} + +int db5500_prcmu_set_ddr_opp(u8 opp) +{ + if (opp != DDR_100_OPP && opp != DDR_50_OPP) + return -EINVAL; + + writeb(opp, _PRCMU_BASE + PRCM_DDR_SUBSYS_APE_MINBW); + + return 0; +} + +/** + * db5500_prcmu_get_arm_opp - get the current ARM OPP + * + * Returns: the current ARM OPP + */ +int db5500_prcmu_get_arm_opp(void) +{ + u8 opp = readb(PRCM_ACK_MB1_CURRENT_ARM_OPP); + + switch (opp) { + case DB5500_ARM_EXT_OPP: + return ARM_EXTCLK; + case DB5500_ARM_50_OPP: + return ARM_50_OPP; + case DB5500_ARM_100_OPP: + return ARM_100_OPP; + default: + pr_err("prcmu: %s() read unknown opp value: %d\n", + __func__, opp); + return ARM_100_OPP; + } +} + +int prcmu_resetout(u8 resoutn, u8 state) +{ + int offset; + int pin = -1; + + offset = state > 0 ? PRCM_RESOUTN_SET_OFFSET : PRCM_RESOUTN_CLR_OFFSET; + + switch (resoutn) { + case 0: + pin = PRCMU_RESOUTN0_PIN; + break; + case 1: + pin = PRCMU_RESOUTN1_PIN; + break; + case 2: + pin = PRCMU_RESOUTN2_PIN; + default: + break; + } + + if (pin > 0) + writel(pin, _PRCMU_BASE + offset); + else + return -EINVAL; + + return 0; +} + int db5500_prcmu_enable_dsipll(void) { int i; + int ret = 0; /* Enable DSIPLL_RESETN resets */ - writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_CLR); + writel(PRCMU_RESET_DSIPLL, _PRCMU_BASE + PRCM_APE_RESETN_CLR); /* Unclamp DSIPLL in/out */ - writel(PRCMU_UNCLAMP_DSIPLL, PRCM_MMIP_LS_CLAMP_CLR); + writel(PRCMU_UNCLAMP_DSIPLL, _PRCMU_BASE + PRCM_MMIP_LS_CLAMP_CLR); /* Set DSI PLL FREQ */ - writel(PRCMU_PLLDSI_FREQ_SETTING, PRCM_PLLDSI_FREQ); + writel(PRCMU_PLLDSI_FREQ_SETTING, _PRCMU_BASE + PRCM_PLLDSI_FREQ); writel(PRCMU_DSI_PLLOUT_SEL_SETTING, - PRCM_DSI_PLLOUT_SEL); + _PRCMU_BASE + PRCM_DSI_PLLOUT_SEL); /* Enable Escape clocks */ - writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); + writel(PRCMU_ENABLE_ESCAPE_CLOCK_DIV, _PRCMU_BASE + PRCM_DSITVCLK_DIV); /* Start DSI PLL */ - writel(PRCMU_ENABLE_PLLDSI, PRCM_PLLDSI_ENABLE); + writel(PRCMU_ENABLE_PLLDSI, _PRCMU_BASE + PRCM_PLLDSI_ENABLE); /* Reset DSI PLL */ - writel(PRCMU_DSI_RESET_SW, PRCM_DSI_SW_RESET); + writel(PRCMU_DSI_RESET_SW, _PRCMU_BASE + PRCM_DSI_SW_RESET); for (i = 0; i < 10; i++) { - if ((readl(PRCM_PLLDSI_LOCKP) & + if ((readl(_PRCMU_BASE + PRCM_PLLDSI_LOCKP) & PRCMU_PLLDSI_LOCKP_LOCKED) == PRCMU_PLLDSI_LOCKP_LOCKED) break; udelay(100); } + + if ((readl(_PRCMU_BASE + PRCM_PLLDSI_LOCKP) & + PRCMU_PLLDSI_LOCKP_LOCKED) + != PRCMU_PLLDSI_LOCKP_LOCKED) + ret = -EIO; /* Release DSIPLL_RESETN */ - writel(PRCMU_RESET_DSIPLL, PRCM_APE_RESETN_SET); - return 0; + writel(PRCMU_RESET_DSIPLL, _PRCMU_BASE + PRCM_APE_RESETN_SET); + return ret; } int db5500_prcmu_disable_dsipll(void) { /* Disable dsi pll */ - writel(PRCMU_DISABLE_PLLDSI, PRCM_PLLDSI_ENABLE); + writel(PRCMU_DISABLE_PLLDSI, _PRCMU_BASE + PRCM_PLLDSI_ENABLE); /* Disable escapeclock */ - writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, PRCM_DSITVCLK_DIV); + writel(PRCMU_DISABLE_ESCAPE_CLOCK_DIV, _PRCMU_BASE + PRCM_DSITVCLK_DIV); return 0; } @@ -276,27 +1763,150 @@ int db5500_prcmu_set_display_clocks(void) { /* HDMI and TVCLK Should be handled somewhere else */ /* PLLDIV=8, PLLSW=2, CLKEN=1 */ - writel(PRCMU_DSI_CLOCK_SETTING, PRCM_HDMICLK_MGT); + writel(PRCMU_DSI_CLOCK_SETTING, _PRCMU_BASE + DB5500_PRCM_HDMICLK_MGT); /* PLLDIV=14, PLLSW=2, CLKEN=1 */ - writel(PRCMU_DSI_LP_CLOCK_SETTING, PRCM_TVCLK_MGT); + writel(PRCMU_DSI_LP_CLOCK_SETTING, _PRCMU_BASE + DB5500_PRCM_TVCLK_MGT); return 0; } +u32 db5500_prcmu_read(unsigned int reg) +{ + return readl_relaxed(_PRCMU_BASE + reg); +} + +void db5500_prcmu_write(unsigned int reg, u32 value) +{ + writel_relaxed(value, _PRCMU_BASE + reg); +} + +void db5500_prcmu_write_masked(unsigned int reg, u32 mask, u32 value) +{ + u32 val; + + val = readl_relaxed(_PRCMU_BASE + reg); + val = (val & ~mask) | (value & mask); + writel_relaxed(val, _PRCMU_BASE + reg); +} + +/** + * db5500_prcmu_system_reset - System reset + * + * Saves the reset reason code and then sets the APE_SOFTRST register which + * fires an interrupt to fw + */ +void db5500_prcmu_system_reset(u16 reset_code) +{ + writew(reset_code, PRCM_SW_RST_REASON); + writel(1, _PRCMU_BASE + PRCM_APE_SOFTRST); +} + +/** + * db5500_prcmu_get_reset_code - Retrieve SW reset reason code + * + * Retrieves the reset reason code stored by prcmu_system_reset() before + * last restart. + */ +u16 db5500_prcmu_get_reset_code(void) +{ + return readw(PRCM_SW_RST_REASON); +} + static void ack_dbb_wakeup(void) { unsigned long flags; spin_lock_irqsave(&mb0_transfer.lock, flags); - while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(0)) cpu_relax(); - writeb(RMB0H_RD_WAKE_UP_ACK, PRCM_REQ_MB0_HEADER); - writel(MBOX_BIT(0), PRCM_MBOX_CPU_SET); + writeb(MB0H_RD_WAKE_UP_ACK, PRCM_REQ_MB0_HEADER); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_MBOX_CPU_SET); spin_unlock_irqrestore(&mb0_transfer.lock, flags); } +int db5500_prcmu_set_epod(u16 epod, u8 epod_state) +{ + int r = 0; + bool ram_retention = false; + + /* check argument */ + BUG_ON(epod < DB5500_EPOD_ID_BASE); + BUG_ON(epod_state > EPOD_STATE_ON); + BUG_ON((epod - DB5500_EPOD_ID_BASE) >= DB5500_NUM_EPOD_ID); + + if (epod == DB5500_EPOD_ID_ESRAM12) + ram_retention = true; + + /* check argument */ + BUG_ON(epod_state == EPOD_STATE_RAMRET && !ram_retention); + + /* get lock */ + mutex_lock(&mb2_transfer.lock); + + /* wait for mailbox */ + while (readl(_PRCMU_BASE + PRCM_MBOX_CPU_VAL) & MBOX_BIT(2)) + cpu_relax(); + + /* Retention is allowed only for ESRAM12 */ + if (epod == DB5500_EPOD_ID_ESRAM12) { + switch (epod_state) { + case EPOD_STATE_ON: + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OOR_ON; + break; + case EPOD_STATE_OFF: + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OOR_OFF; + break; + case EPOD_STATE_RAMRET: + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OOR_RET; + break; + default: + r = -EINVAL; + goto unlock_and_return; + break; + } + } else { + if (epod_state == EPOD_STATE_ON) + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_ON; + else if (epod_state == EPOD_STATE_OFF) + mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE] = + EPOD_OFF; + else { + r = -EINVAL; + goto unlock_and_return; + } + } + /* fill in mailbox */ + writeb((epod - DB5500_EPOD_ID_BASE), PRCM_REQ_MB2_EPOD_CLIENT); + writeb(mb2_transfer.req.epod_st[epod - DB5500_EPOD_ID_BASE], + PRCM_REQ_MB2_EPOD_STATE); + + writeb(MB2H_EPOD_REQUEST, PRCM_REQ_MB2_HEADER); + + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_MBOX_CPU_SET); + + if (!wait_for_completion_timeout(&mb2_transfer.work, + msecs_to_jiffies(20000))) { + pr_err("prcmu: set_epod() failed.\n"); + r = -EIO; + WARN(1, "Failed to set epod"); + goto unlock_and_return; + } + + if (mb2_transfer.ack.status != RC_SUCCESS || + mb2_transfer.ack.header != MB2H_EPOD_REQUEST) + r = -EIO; + +unlock_and_return: + mutex_unlock(&mb2_transfer.lock); + return r; +} + static inline void print_unknown_header_warning(u8 n, u8 header) { pr_warning("prcmu: Unknown message header (%d) in mailbox %d.\n", @@ -306,11 +1916,31 @@ static inline void print_unknown_header_warning(u8 n, u8 header) static bool read_mailbox_0(void) { bool r; + u32 ev; + unsigned int n; + u8 header; header = readb(PRCM_ACK_MB0_HEADER); switch (header) { - case AMB0H_WAKE_UP: + case MB0H_WAKE_UP: + if (readb(PRCM_ACK_MB0_READ_POINTER) & 1) + ev = readl(PRCM_ACK_MB0_WAKEUP_1_DBB); + else + ev = readl(PRCM_ACK_MB0_WAKEUP_0_DBB); + + prcmu_debug_register_mbox0_event(ev, + (mb0_transfer.req.dbb_irqs | + mb0_transfer.req.dbb_wakeups)); + + ev &= mb0_transfer.req.dbb_irqs; + + for (n = 0; n < NUM_DB5500_PRCMU_WAKEUPS; n++) { + if (ev & prcmu_irq_bit[n]) { + if (n != IRQ_INDEX(ABB)) + generic_handle_irq(IRQ_DB5500_PRCMU_BASE + n); + } + } r = true; break; default: @@ -318,31 +1948,123 @@ static bool read_mailbox_0(void) r = false; break; } - writel(MBOX_BIT(0), PRCM_ARM_IT1_CLR); + writel(MBOX_BIT(0), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return r; } static bool read_mailbox_1(void) { - writel(MBOX_BIT(1), PRCM_ARM_IT1_CLR); + u8 header; + bool do_complete = true; + + header = mb1_transfer.ack.header = readb(PRCM_ACK_MB1_HEADER); + + switch (header) { + case MB1H_ARM_OPP: + mb1_transfer.ack.arm_opp = readb(PRCM_ACK_MB1_CURRENT_ARM_OPP); + mb1_transfer.ack.arm_voltage_st = + readb(PRCM_ACK_MB1_ARM_VOLT_STATUS); + break; + case MB1H_APE_OPP: + mb1_transfer.ack.ape_opp = readb(PRCM_ACK_MB1_CURRENT_APE_OPP); + mb1_transfer.ack.ape_voltage_st = + readb(PRCM_ACK_MB1_APE_VOLT_STATUS); + break; + case MB1H_ARM_APE_OPP: + mb1_transfer.ack.ape_opp = readb(PRCM_ACK_MB1_CURRENT_APE_OPP); + mb1_transfer.ack.ape_voltage_st = + readb(PRCM_ACK_MB1_APE_VOLT_STATUS); + break; + default: + print_unknown_header_warning(1, header); + do_complete = false; + break; + } + + writel(MBOX_BIT(1), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + + if (do_complete) + complete(&mb1_transfer.work); + return false; } static bool read_mailbox_2(void) { - writel(MBOX_BIT(2), PRCM_ARM_IT1_CLR); + u8 header; + + header = readb(PRCM_ACK_MB2_HEADER); + mb2_transfer.ack.header = header; + switch (header) { + case MB2H_EPOD_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_EPOD_STATUS); + break; + case MB2H_CLK_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_CLK_STATUS); + break; + case MB2H_PLL_REQUEST: + mb2_transfer.ack.status = readb(PRCM_ACK_MB2_PLL_STATUS); + break; + default: + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + pr_err("prcmu: Wrong ACK received for MB2 request \n"); + return false; + break; + } + writel(MBOX_BIT(2), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + complete(&mb2_transfer.work); return false; } static bool read_mailbox_3(void) { - writel(MBOX_BIT(3), PRCM_ARM_IT1_CLR); + u8 header; + + header = readb(PRCM_ACK_MB3_HEADER); + mb3_transfer.ack.header = header; + switch (header) { + case MB3H_REFCLK_REQUEST: + mb3_transfer.ack.status = readb(PRCM_ACK_MB3_REFCLK_REQ); + writel(MBOX_BIT(3), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + complete(&mb3_transfer.sysclk_work); + break; + default: + writel(MBOX_BIT(3), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); + pr_err("prcmu: wrong MB3 header\n"); + break; + } + return false; } static bool read_mailbox_4(void) { - writel(MBOX_BIT(4), PRCM_ARM_IT1_CLR); + u8 header; + bool do_complete = true; + + header = readb(PRCM_ACK_MB4_HEADER); + mb4_transfer.ack.header = header; + switch (header) { + case MB4H_ACK_CFG_HOTDOG: + case MB4H_ACK_CFG_HOTMON: + case MB4H_ACK_CFG_HOTPERIOD: + case MB4H_ACK_CFG_MODEM_RESET: + case MB4H_ACK_CGF_A9WDOG_EN_PREBARK: + case MB4H_ACK_CGF_A9WDOG_EN_NOPREBARK: + case MB4H_ACK_CGF_A9WDOG_DIS: + mb4_transfer.ack.status = readb(PRCM_ACK_MB4_REQUESTS); + break; + default: + print_unknown_header_warning(4, header); + do_complete = false; + break; + } + + writel(MBOX_BIT(4), (_PRCMU_BASE + PRCM_ARM_IT1_CLEAR)); + + if (do_complete) + complete(&mb4_transfer.work); + return false; } @@ -363,19 +2085,19 @@ static bool read_mailbox_5(void) print_unknown_header_warning(5, header); break; } - writel(MBOX_BIT(5), PRCM_ARM_IT1_CLR); + writel(MBOX_BIT(5), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return false; } static bool read_mailbox_6(void) { - writel(MBOX_BIT(6), PRCM_ARM_IT1_CLR); + writel(MBOX_BIT(6), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return false; } static bool read_mailbox_7(void) { - writel(MBOX_BIT(7), PRCM_ARM_IT1_CLR); + writel(MBOX_BIT(7), _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); return false; } @@ -396,7 +2118,7 @@ static irqreturn_t prcmu_irq_handler(int irq, void *data) u8 n; irqreturn_t r; - bits = (readl(PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); + bits = (readl(_PRCMU_BASE + PRCM_ARM_IT1_VAL) & ALL_MBOX_BITS); if (unlikely(!bits)) return IRQ_NONE; @@ -406,6 +2128,7 @@ static irqreturn_t prcmu_irq_handler(int irq, void *data) bits -= MBOX_BIT(n); if (read_mailbox[n]()) r = IRQ_WAKE_THREAD; + prcmu_debug_register_interrupt(n); } } return r; @@ -413,39 +2136,271 @@ static irqreturn_t prcmu_irq_handler(int irq, void *data) static irqreturn_t prcmu_irq_thread_fn(int irq, void *data) { + u32 ev; + + /* + * ABB needs to be handled before the wakeup because + * the ping/pong buffers for ABB events could change + * after we acknowledge the wakeup. + */ + if (readb(PRCM_ACK_MB0_READ_POINTER) & 1) + ev = readl(PRCM_ACK_MB0_WAKEUP_1_DBB); + else + ev = readl(PRCM_ACK_MB0_WAKEUP_0_DBB); + + ev &= mb0_transfer.req.dbb_irqs; + if (ev & WAKEUP_BIT_ABB) + handle_nested_irq(IRQ_DB5500_PRCMU_ABB); + ack_dbb_wakeup(); + return IRQ_HANDLED; } +static void prcmu_mask_work(struct work_struct *work) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.lock, flags); + + config_wakeups(); + + spin_unlock_irqrestore(&mb0_transfer.lock, flags); +} + +static void prcmu_irq_mask(struct irq_data *d) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.dbb_irqs_lock, flags); + + mb0_transfer.req.dbb_irqs &= ~prcmu_irq_bit[d->irq - IRQ_DB5500_PRCMU_BASE]; + + spin_unlock_irqrestore(&mb0_transfer.dbb_irqs_lock, flags); + schedule_work(&mb0_transfer.mask_work); +} + +static void prcmu_irq_unmask(struct irq_data *d) +{ + unsigned long flags; + + spin_lock_irqsave(&mb0_transfer.dbb_irqs_lock, flags); + + mb0_transfer.req.dbb_irqs |= prcmu_irq_bit[d->irq - IRQ_DB5500_PRCMU_BASE]; + + spin_unlock_irqrestore(&mb0_transfer.dbb_irqs_lock, flags); + schedule_work(&mb0_transfer.mask_work); +} + +static void noop(struct irq_data *d) +{ +} + +static struct irq_chip prcmu_irq_chip = { + .name = "prcmu", + .irq_disable = prcmu_irq_mask, + .irq_ack = noop, + .irq_mask = prcmu_irq_mask, + .irq_unmask = prcmu_irq_unmask, +}; + void __init db5500_prcmu_early_init(void) { + unsigned int i; + void *tcpm_base = ioremap_nocache(U5500_PRCMU_TCPM_BASE, SZ_4K); + + if (tcpm_base != NULL) { + int version_high, version_low; + + version_high = readl(tcpm_base + PRCMU_FW_VERSION_OFFSET); + version_low = readl(tcpm_base + PRCMU_FW_VERSION_OFFSET + 4); + prcmu_version.board = (version_high >> 24) & 0xFF; + prcmu_version.fw_version = version_high & 0xFF; + prcmu_version.api_version = version_low & 0xFF; + + pr_info("PRCMU Firmware Version: 0x%x\n", + prcmu_version.fw_version); + pr_info("PRCMU API Version: 0x%x\n", + prcmu_version.api_version); + + iounmap(tcpm_base); + } + tcdm_base = __io_address(U5500_PRCMU_TCDM_BASE); + mtimer_base = __io_address(U5500_MTIMER_BASE); spin_lock_init(&mb0_transfer.lock); + spin_lock_init(&mb0_transfer.dbb_irqs_lock); + mutex_init(&mb0_transfer.ac_wake_lock); + mutex_init(&mb1_transfer.lock); + init_completion(&mb1_transfer.work); + mutex_init(&mb2_transfer.lock); + init_completion(&mb2_transfer.work); + mutex_init(&mb3_transfer.sysclk_lock); + init_completion(&mb3_transfer.sysclk_work); + mutex_init(&mb4_transfer.lock); + init_completion(&mb4_transfer.work); mutex_init(&mb5_transfer.lock); init_completion(&mb5_transfer.work); + + INIT_WORK(&mb0_transfer.mask_work, prcmu_mask_work); + + /* Initalize irqs. */ + for (i = 0; i < NUM_DB5500_PRCMU_WAKEUPS; i++) { + unsigned int irq; + + irq = IRQ_DB5500_PRCMU_BASE + i; + irq_set_chip_and_handler(irq, &prcmu_irq_chip, + handle_simple_irq); + if (irq == IRQ_DB5500_PRCMU_ABB) + irq_set_nested_thread(irq, true); + set_irq_flags(irq, IRQF_VALID); + } + prcmu_ape_clocks_init(); +} + +/* + * Power domain switches (ePODs) modeled as regulators for the DB5500 SoC + */ +static struct regulator_consumer_supply db5500_vape_consumers[] = { + REGULATOR_SUPPLY("v-ape", NULL), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.0"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.1"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.2"), + REGULATOR_SUPPLY("v-i2c", "nmk-i2c.3"), + REGULATOR_SUPPLY("vcore", "sdi0"), + REGULATOR_SUPPLY("vcore", "sdi1"), + REGULATOR_SUPPLY("vcore", "sdi2"), + REGULATOR_SUPPLY("vcore", "sdi3"), + REGULATOR_SUPPLY("vcore", "sdi4"), + REGULATOR_SUPPLY("v-uart", "uart0"), + REGULATOR_SUPPLY("v-uart", "uart1"), + REGULATOR_SUPPLY("v-uart", "uart2"), + REGULATOR_SUPPLY("v-uart", "uart3"), + REGULATOR_SUPPLY("v-ape", "db5500-keypad"), +}; + +static struct regulator_consumer_supply db5500_sga_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.0"), + REGULATOR_SUPPLY("v-mali", NULL), +}; + +static struct regulator_consumer_supply db5500_hva_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.1"), + REGULATOR_SUPPLY("v-hva", NULL), +}; + +static struct regulator_consumer_supply db5500_sia_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.2"), + REGULATOR_SUPPLY("v-sia", "mmio_camera"), +}; + +static struct regulator_consumer_supply db5500_disp_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.3"), + REGULATOR_SUPPLY("vsupply", "b2r2_bus"), + REGULATOR_SUPPLY("vsupply", "mcde"), +}; + +static struct regulator_consumer_supply db5500_esram12_consumers[] = { + REGULATOR_SUPPLY("debug", "reg-virt-consumer.4"), + REGULATOR_SUPPLY("v-esram12", "mcde"), + REGULATOR_SUPPLY("esram12", "hva"), +}; + +#define DB5500_REGULATOR_SWITCH(lower, upper) \ +[DB5500_REGULATOR_SWITCH_##upper] = { \ + .constraints = { \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + }, \ + .consumer_supplies = db5500_##lower##_consumers, \ + .num_consumer_supplies = ARRAY_SIZE(db5500_##lower##_consumers),\ } +#define DB5500_REGULATOR_SWITCH_VAPE(lower, upper) \ +[DB5500_REGULATOR_SWITCH_##upper] = { \ + .supply_regulator = "db5500-vape", \ + .constraints = { \ + .valid_ops_mask = REGULATOR_CHANGE_STATUS, \ + }, \ + .consumer_supplies = db5500_##lower##_consumers, \ + .num_consumer_supplies = ARRAY_SIZE(db5500_##lower##_consumers),\ +} \ + +static struct regulator_init_data db5500_regulators[DB5500_NUM_REGULATORS] = { + [DB5500_REGULATOR_VAPE] = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .consumer_supplies = db5500_vape_consumers, + .num_consumer_supplies = ARRAY_SIZE(db5500_vape_consumers), + }, + DB5500_REGULATOR_SWITCH_VAPE(sga, SGA), + DB5500_REGULATOR_SWITCH_VAPE(hva, HVA), + DB5500_REGULATOR_SWITCH_VAPE(sia, SIA), + DB5500_REGULATOR_SWITCH_VAPE(disp, DISP), + /* + * ESRAM12 is put in retention by the firmware when VAPE is + * turned off so there's no need to hold VAPE. + */ + DB5500_REGULATOR_SWITCH(esram12, ESRAM12), +}; + +static struct mfd_cell db5500_prcmu_devs[] = { + { + .name = "db5500-prcmu-regulators", + .platform_data = &db5500_regulators, + .pdata_size = sizeof(db5500_regulators), + }, + { + .name = "cpufreq-u5500", + }, +}; + /** * prcmu_fw_init - arch init call for the Linux PRCMU fw init logic * */ -int __init db5500_prcmu_init(void) +static int __init db5500_prcmu_probe(struct platform_device *pdev) { - int r = 0; + int err = 0; if (ux500_is_svp() || !cpu_is_u5500()) return -ENODEV; /* Clean up the mailbox interrupts after pre-kernel code. */ - writel(ALL_MBOX_BITS, PRCM_ARM_IT1_CLR); + writel(ALL_MBOX_BITS, _PRCMU_BASE + PRCM_ARM_IT1_CLEAR); - r = request_threaded_irq(IRQ_DB5500_PRCMU1, prcmu_irq_handler, - prcmu_irq_thread_fn, 0, "prcmu", NULL); - if (r < 0) { + err = request_threaded_irq(IRQ_DB5500_PRCMU1, prcmu_irq_handler, + prcmu_irq_thread_fn, IRQF_NO_SUSPEND, "prcmu", NULL); + if (err < 0) { pr_err("prcmu: Failed to allocate IRQ_DB5500_PRCMU1.\n"); - return -EBUSY; + err = -EBUSY; + goto no_irq_return; } - return 0; + + err = mfd_add_devices(&pdev->dev, 0, db5500_prcmu_devs, + ARRAY_SIZE(db5500_prcmu_devs), NULL, + 0); + + if (err) + pr_err("prcmu: Failed to add subdevices\n"); + else + pr_info("DB5500 PRCMU initialized\n"); + +no_irq_return: + return err; + +} + +static struct platform_driver db5500_prcmu_driver = { + .driver = { + .name = "db5500-prcmu", + .owner = THIS_MODULE, + }, +}; + +static int __init db5500_prcmu_init(void) +{ + return platform_driver_probe(&db5500_prcmu_driver, db5500_prcmu_probe); } arch_initcall(db5500_prcmu_init); diff --git a/drivers/mfd/db8500-prcmu.c b/drivers/mfd/db8500-prcmu.c index 5be32489714..7c26c41a7ef 100644 --- a/drivers/mfd/db8500-prcmu.c +++ b/drivers/mfd/db8500-prcmu.c @@ -30,11 +30,13 @@ #include <linux/mfd/dbx500-prcmu.h> #include <linux/regulator/db8500-prcmu.h> #include <linux/regulator/machine.h> +#include <linux/mfd/abx500.h> #include <asm/hardware/gic.h> #include <mach/hardware.h> #include <mach/irqs.h> #include <mach/db8500-regs.h> #include <mach/id.h> +#include <mach/prcmu-debug.h> #include "dbx500-prcmu-regs.h" /* Offset for the firmware version within the TCPM */ @@ -70,6 +72,8 @@ #define PRCM_SW_RST_REASON 0xFF8 /* 2 bytes */ +#define PRCM_TCDM_VOICE_CALL_FLAG 0xDD4 /* 4 bytes */ + #define _PRCM_MBOX_HEADER 0xFE8 /* 16 bytes */ #define PRCM_MBOX_HEADER_REQ_MB0 (_PRCM_MBOX_HEADER + 0x0) #define PRCM_MBOX_HEADER_REQ_MB1 (_PRCM_MBOX_HEADER + 0x1) @@ -214,10 +218,8 @@ #define PRCM_REQ_MB5_I2C_HW_BITS (PRCM_REQ_MB5 + 0x1) #define PRCM_REQ_MB5_I2C_REG (PRCM_REQ_MB5 + 0x2) #define PRCM_REQ_MB5_I2C_VAL (PRCM_REQ_MB5 + 0x3) -#define PRCMU_I2C_WRITE(slave) \ - (((slave) << 1) | (cpu_is_u8500v2() ? BIT(6) : 0)) -#define PRCMU_I2C_READ(slave) \ - (((slave) << 1) | BIT(0) | (cpu_is_u8500v2() ? BIT(6) : 0)) +#define PRCMU_I2C_WRITE(slave) (((slave) << 1) | BIT(6)) +#define PRCMU_I2C_READ(slave) (((slave) << 1) | BIT(0) | BIT(6)) #define PRCMU_I2C_STOP_EN BIT(3) /* Mailbox 5 ACKs */ @@ -424,6 +426,13 @@ static DEFINE_SPINLOCK(clkout_lock); /* Global var to runtime determine TCDM base for v2 or v1 */ static __iomem void *tcdm_base; +/* + * Copies of the startup values of the reset status register and the SW reset + * code. + */ +static u32 reset_status_copy; +static u16 reset_code_copy; + struct clk_mgt { void __iomem *reg; u32 pllsw; @@ -637,6 +646,26 @@ void db8500_prcmu_write_masked(unsigned int reg, u32 mask, u32 value) spin_unlock_irqrestore(&prcmu_lock, flags); } +/* + * Dump AB8500 registers, PRCMU registers and PRCMU data memory + * on critical errors. + */ +static void db8500_prcmu_debug_dump(const char *func, + bool dump_prcmu, bool dump_abb) +{ + printk(KERN_DEBUG"%s: timeout\n", func); + + /* Dump AB8500 registers */ + if (dump_abb) + abx500_dump_all_banks(); + + /* Dump prcmu registers and data memory */ + if (dump_prcmu) { + prcmu_debug_dump_regs(); + prcmu_debug_dump_data_mem(); + } +} + struct prcmu_fw_version *prcmu_get_fw_version(void) { return fw_info.valid ? &fw_info.version : NULL; @@ -648,6 +677,11 @@ bool prcmu_has_arm_maxopp(void) PRCM_AVS_ISMODEENABLE_MASK) == PRCM_AVS_ISMODEENABLE_MASK; } +void db8500_prcmu_vc(bool enable) +{ + writel((enable ? 0xF : 0), (tcdm_base + PRCM_TCDM_VOICE_CALL_FLAG)); +} + /** * prcmu_get_boot_status - PRCMU boot status checking * Returns: the current PRCMU boot status @@ -1049,7 +1083,7 @@ int db8500_prcmu_set_ddr_opp(u8 opp) if (opp < DDR_100_OPP || opp > DDR_25_OPP) return -EINVAL; /* Changing the DDR OPP can hang the hardware pre-v21 */ - if (cpu_is_u8500v20_or_later() && !cpu_is_u8500v20()) + if (!cpu_is_u8500v20()) writeb(opp, PRCM_DDR_SUBSYS_APE_MINBW); return 0; @@ -1111,12 +1145,14 @@ unlock_and_return: int db8500_prcmu_set_ape_opp(u8 opp) { int r = 0; + u8 prcmu_opp_req; if (opp == mb1_transfer.ape_opp) return 0; mutex_lock(&mb1_transfer.lock); + /* Exit APE_50_PARTLY_25_OPP */ if (mb1_transfer.ape_opp == APE_50_PARTLY_25_OPP) request_even_slower_clocks(false); @@ -1126,20 +1162,22 @@ int db8500_prcmu_set_ape_opp(u8 opp) while (readl(PRCM_MBOX_CPU_VAL) & MBOX_BIT(1)) cpu_relax(); + prcmu_opp_req = (opp == APE_50_PARTLY_25_OPP) ? APE_50_OPP : opp; + writeb(MB1H_ARM_APE_OPP, (tcdm_base + PRCM_MBOX_HEADER_REQ_MB1)); writeb(ARM_NO_CHANGE, (tcdm_base + PRCM_REQ_MB1_ARM_OPP)); - writeb(((opp == APE_50_PARTLY_25_OPP) ? APE_50_OPP : opp), - (tcdm_base + PRCM_REQ_MB1_APE_OPP)); + writeb(prcmu_opp_req, (tcdm_base + PRCM_REQ_MB1_APE_OPP)); writel(MBOX_BIT(1), PRCM_MBOX_CPU_SET); wait_for_completion(&mb1_transfer.work); if ((mb1_transfer.ack.header != MB1H_ARM_APE_OPP) || - (mb1_transfer.ack.ape_opp != opp)) + (mb1_transfer.ack.ape_opp != prcmu_opp_req)) r = -EIO; skip_message: if ((!r && (opp == APE_50_PARTLY_25_OPP)) || + /* Set APE_50_PARTLY_25_OPP back in case new opp failed */ (r && (mb1_transfer.ape_opp == APE_50_PARTLY_25_OPP))) request_even_slower_clocks(true); if (!r) @@ -1322,6 +1360,7 @@ int db8500_prcmu_set_epod(u16 epod_id, u8 epod_state) pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", __func__); r = -EIO; + db8500_prcmu_debug_dump(__func__, true, true); goto unlock_and_return; } @@ -1416,6 +1455,7 @@ static int request_sysclk(bool enable) pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", __func__); r = -EIO; + db8500_prcmu_debug_dump(__func__, true, true); } mutex_unlock(&mb3_transfer.sysclk_lock); @@ -2190,6 +2230,7 @@ int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size) pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", __func__); r = -EIO; + db8500_prcmu_debug_dump(__func__, true, false); } else { r = ((mb5_transfer.ack.status == I2C_RD_OK) ? 0 : -EIO); } @@ -2240,6 +2281,7 @@ int prcmu_abb_write_masked(u8 slave, u8 reg, u8 *value, u8 *mask, u8 size) pr_err("prcmu: %s timed out (20 s) waiting for a reply.\n", __func__); r = -EIO; + db8500_prcmu_debug_dump(__func__, true, false); } else { r = ((mb5_transfer.ack.status == I2C_WR_OK) ? 0 : -EIO); } @@ -2287,6 +2329,7 @@ retry: if (!wait_for_completion_timeout(&mb0_transfer.ac_wake_work, msecs_to_jiffies(5000))) { + db8500_prcmu_debug_dump(__func__, true, true); pr_crit("prcmu: %s timed out (5 s) waiting for a reply.\n", __func__); goto unlock_and_return; @@ -2309,6 +2352,7 @@ retry: if (wait_for_completion_timeout(&mb0_transfer.ac_wake_work, msecs_to_jiffies(5000))) goto retry; + db8500_prcmu_debug_dump(__func__, true, true); pr_crit("prcmu: %s timed out (5 s) waiting for AC_SLEEP_ACK.\n", __func__); } @@ -2335,6 +2379,7 @@ void prcmu_ac_sleep_req() if (!wait_for_completion_timeout(&mb0_transfer.ac_wake_work, msecs_to_jiffies(5000))) { + db8500_prcmu_debug_dump(__func__, true, true); pr_crit("prcmu: %s timed out (5 s) waiting for a reply.\n", __func__); } @@ -2370,7 +2415,17 @@ void db8500_prcmu_system_reset(u16 reset_code) */ u16 db8500_prcmu_get_reset_code(void) { - return readw(tcdm_base + PRCM_SW_RST_REASON); + return reset_code_copy; +} + +/** + * db8500_prcmu_get_reset_status - Retrieve reset status + * + * Retrieves the value of the reset status register as read at startup. + */ +u32 db8500_prcmu_get_reset_status(void) +{ + return reset_status_copy; } /** @@ -2437,6 +2492,13 @@ static bool read_mailbox_0(void) if (ev & WAKEUP_BIT_SYSCLK_OK) complete(&mb3_transfer.sysclk_work); + prcmu_debug_register_mbox0_event(ev, + (mb0_transfer.req.dbb_irqs | + mb0_transfer.req.dbb_wakeups | + WAKEUP_BIT_AC_WAKE_ACK | + WAKEUP_BIT_AC_SLEEP_ACK | + WAKEUP_BIT_SYSCLK_OK)); + ev &= mb0_transfer.req.dbb_irqs; for (n = 0; n < NUM_PRCMU_WAKEUPS; n++) { @@ -2561,6 +2623,7 @@ static irqreturn_t prcmu_irq_handler(int irq, void *data) bits -= MBOX_BIT(n); if (read_mailbox[n]()) r = IRQ_WAKE_THREAD; + prcmu_debug_register_interrupt(n); } } return r; @@ -2646,29 +2709,38 @@ static char *fw_project_name(u8 project) void __init db8500_prcmu_early_init(void) { unsigned int i; - if (cpu_is_u8500v2()) { - void *tcpm_base = ioremap_nocache(U8500_PRCMU_TCPM_BASE, SZ_4K); - - if (tcpm_base != NULL) { - u32 version; - version = readl(tcpm_base + PRCMU_FW_VERSION_OFFSET); - fw_info.version.project = version & 0xFF; - fw_info.version.api_version = (version >> 8) & 0xFF; - fw_info.version.func_version = (version >> 16) & 0xFF; - fw_info.version.errata = (version >> 24) & 0xFF; - fw_info.valid = true; - pr_info("PRCMU firmware: %s, version %d.%d.%d\n", - fw_project_name(fw_info.version.project), - (version >> 8) & 0xFF, (version >> 16) & 0xFF, - (version >> 24) & 0xFF); - iounmap(tcpm_base); - } + void *tcpm_base = ioremap_nocache(U8500_PRCMU_TCPM_BASE, SZ_4K); + void __iomem *sec_base; + + if (tcpm_base != NULL) { + u32 version; + version = readl(tcpm_base + PRCMU_FW_VERSION_OFFSET); + fw_info.version.project = version & 0xFF; + fw_info.version.api_version = (version >> 8) & 0xFF; + fw_info.version.func_version = (version >> 16) & 0xFF; + fw_info.version.errata = (version >> 24) & 0xFF; + fw_info.valid = true; + pr_info("PRCMU firmware: %s, version %d.%d.%d\n", + fw_project_name(fw_info.version.project), + (version >> 8) & 0xFF, (version >> 16) & 0xFF, + (version >> 24) & 0xFF); + iounmap(tcpm_base); + } - tcdm_base = __io_address(U8500_PRCMU_TCDM_BASE); - } else { - pr_err("prcmu: Unsupported chip version\n"); - BUG(); + tcdm_base = __io_address(U8500_PRCMU_TCDM_BASE); + + /* + * Copy the value of the reset status register and if needed also + * the software reset code. + */ + sec_base = ioremap_nocache(U8500_PRCMU_SEC_BASE, SZ_4K); + if (sec_base != NULL) { + reset_status_copy = readl(sec_base + + DB8500_SEC_PRCM_RESET_STATUS); + iounmap(sec_base); } + if (reset_status_copy & DB8500_SEC_PRCM_RESET_STATUS_APE_SOFTWARE_RESET) + reset_code_copy = readw(tcdm_base + PRCM_SW_RST_REASON); spin_lock_init(&mb0_transfer.lock); spin_lock_init(&mb0_transfer.dbb_irqs_lock); @@ -2734,6 +2806,7 @@ static struct regulator_consumer_supply db8500_vape_consumers[] = { REGULATOR_SUPPLY("vcore", "uart2"), REGULATOR_SUPPLY("v-ape", "nmk-ske-keypad.0"), REGULATOR_SUPPLY("v-hsi", "ste_hsi.0"), + REGULATOR_SUPPLY("vddvario", "smsc911x.0"), }; static struct regulator_consumer_supply db8500_vsmps2_consumers[] = { @@ -2743,7 +2816,7 @@ static struct regulator_consumer_supply db8500_vsmps2_consumers[] = { }; static struct regulator_consumer_supply db8500_b2r2_mcde_consumers[] = { - REGULATOR_SUPPLY("vsupply", "b2r2_bus"), + REGULATOR_SUPPLY("vsupply", "b2r2_core"), REGULATOR_SUPPLY("vsupply", "mcde"), }; @@ -2962,9 +3035,6 @@ static int __init db8500_prcmu_probe(struct platform_device *pdev) { int err = 0; - if (ux500_is_svp()) - return -ENODEV; - init_prcm_registers(); /* Clean up the mailbox interrupts after pre-kernel code. */ @@ -2978,8 +3048,7 @@ static int __init db8500_prcmu_probe(struct platform_device *pdev) goto no_irq_return; } - if (cpu_is_u8500v20_or_later()) - prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET); + prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET); err = mfd_add_devices(&pdev->dev, 0, db8500_prcmu_devs, ARRAY_SIZE(db8500_prcmu_devs), NULL, diff --git a/drivers/mfd/dbx500-prcmu-regs.h b/drivers/mfd/dbx500-prcmu-regs.h index 3a0bf91d778..0835a57dac1 100644 --- a/drivers/mfd/dbx500-prcmu-regs.h +++ b/drivers/mfd/dbx500-prcmu-regs.h @@ -34,6 +34,9 @@ #define PRCM_PER5CLK_MGT PRCM_CLK_MGT(0x038) #define PRCM_PER6CLK_MGT PRCM_CLK_MGT(0x03C) #define PRCM_PER7CLK_MGT PRCM_CLK_MGT(0x040) +#define PRCM_PWMCLK_MGT PRCM_CLK_MGT(0x044) /* for DB5500 */ +#define PRCM_IRDACLK_MGT PRCM_CLK_MGT(0x048) /* for DB5500 */ +#define PRCM_IRRCCLK_MGT PRCM_CLK_MGT(0x04C) /* for DB5500 */ #define PRCM_LCDCLK_MGT PRCM_CLK_MGT(0x044) #define PRCM_BMLCLK_MGT PRCM_CLK_MGT(0x04C) #define PRCM_HSITXCLK_MGT PRCM_CLK_MGT(0x050) @@ -124,7 +127,6 @@ #define PRCM_ITSTATUS4 (_PRCMU_BASE + 0x168) #define PRCM_ITSTATUS5 (_PRCMU_BASE + 0x484) #define PRCM_ITCLEAR5 (_PRCMU_BASE + 0x488) -#define PRCM_ARMIT_MASKXP70_IT (_PRCMU_BASE + 0x1018) /* System reset register */ #define PRCM_APE_SOFTRST (_PRCMU_BASE + 0x228) @@ -247,4 +249,17 @@ /* System reset register */ #define PRCM_APE_SOFTRST (_PRCMU_BASE + 0x228) +/* Secure read-only registers */ +#define DB8500_SEC_PRCM_RESET_STATUS 0x03C +#define DB8500_SEC_PRCM_RESET_STATUS_A9_CPU0_WATCHDOG_RESET BIT(0) +#define DB8500_SEC_PRCM_RESET_STATUS_A9_CPU1_WATCHDOG_RESET BIT(1) +#define DB8500_SEC_PRCM_RESET_STATUS_APE_SOFTWARE_RESET BIT(2) +#define DB8500_SEC_PRCM_RESET_STATUS_APE_RESET BIT(3) +#define DB8500_SEC_PRCM_RESET_STATUS_SECURE_WATCHDOG BIT(4) +#define DB8500_SEC_PRCM_RESET_STATUS_POWER_ON_RESET BIT(5) +#define DB8500_SEC_PRCM_RESET_STATUS_A9_RESTART BIT(6) +#define DB8500_SEC_PRCM_RESET_STATUS_APE_RESTART BIT(7) +#define DB8500_SEC_PRCM_RESET_STATUS_MODEM_SOFTWARE_RESET BIT(8) +#define DB8500_SEC_PRCM_RESET_STATUS_BOOT_ENGI BIT(16) + #endif /* __DB8500_PRCMU_REGS_H */ diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 36db5a441eb..7278f1e4141 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -247,6 +247,14 @@ config REGULATOR_AB8500 This driver supports the regulators found on the ST-Ericsson mixed signal AB8500 PMIC +config REGULATOR_AB8500_EXT + bool "ST-Ericsson AB8500 External Regulators" + depends on REGULATOR_AB8500 + default y if REGULATOR_AB8500 + help + This driver supports the external regulator controls found on the + ST-Ericsson mixed signal AB8500 PMIC + config REGULATOR_DBX500_PRCMU bool diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 94b52745e95..b5b90fb232b 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_REGULATOR_88PM8607) += 88pm8607.o obj-$(CONFIG_REGULATOR_AAT2870) += aat2870-regulator.o obj-$(CONFIG_REGULATOR_AB3100) += ab3100.o obj-$(CONFIG_REGULATOR_AB8500) += ab8500.o +obj-$(CONFIG_REGULATOR_AB8500_EXT) += ab8500-ext.o obj-$(CONFIG_REGULATOR_AD5398) += ad5398.o obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o obj-$(CONFIG_REGULATOR_DA903X) += da903x.o diff --git a/drivers/regulator/ab5500.c b/drivers/regulator/ab5500.c new file mode 100644 index 00000000000..99676f7ad8e --- /dev/null +++ b/drivers/regulator/ab5500.c @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2011 ST-Ericsson SA + * + * License terms: GNU General Public License (GPL) version 2 + * + * Based on ab3100.c. + * + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson + * Author: Rabin Vincent <rabin.vincent@stericsson.com> for ST-Ericsson + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab5500.h> +#include <linux/regulator/ab5500.h> + +#define AB5500_LDO_VDIGMIC_ST 0x50 + +#define AB5500_LDO_G_ST 0x78 +#define AB5500_LDO_G_PWR1 0x79 +#define AB5500_LDO_G_PWR0 0x7a + +#define AB5500_LDO_H_ST 0x7b +#define AB5500_LDO_H_PWR1 0x7c +#define AB5500_LDO_H_PWR0 0x7d + +#define AB5500_LDO_K_ST 0x7e +#define AB5500_LDO_K_PWR1 0x7f +#define AB5500_LDO_K_PWR0 0x80 + +#define AB5500_LDO_L_ST 0x81 +#define AB5500_LDO_L_PWR1 0x82 +#define AB5500_LDO_L_PWR0 0x83 + +/* In SIM bank */ +#define AB5500_SIM_SUP 0x14 + +#define AB5500_MBIAS1 0x00 +#define AB5500_MBIAS2 0x01 + +#define AB5500_LDO_MODE_MASK (0x3 << 4) +#define AB5500_LDO_MODE_FULLPOWER (0x3 << 4) +#define AB5500_LDO_MODE_PWRCTRL (0x2 << 4) +#define AB5500_LDO_MODE_LOWPOWER (0x1 << 4) +#define AB5500_LDO_MODE_OFF (0x0 << 4) +#define AB5500_LDO_VOLT_MASK 0x07 + +#define AB5500_MBIAS1_ENABLE (0x1 << 1) +#define AB5500_MBIAS1_MODE_MASK (0x1 << 1) +#define AB5500_MBIAS2_ENABLE (0x1 << 1) +#define AB5500_MBIAS2_VOLT_MASK (0x1 << 2) +#define AB5500_MBIAS2_MODE_MASK (0x1 << 1) + +struct ab5500_regulator { + struct regulator_desc desc; + const int *voltages; + int num_holes; + bool off_is_lowpower; + bool enabled; + int enable_time; + int load_lp_uA; + u8 bank; + u8 reg; + u8 mode; + u8 update_mask; + u8 update_val_idle; + u8 update_val_normal; + u8 voltage_mask; +}; + +struct ab5500_regulators { + struct device *dev; + struct ab5500_regulator *regulator[AB5500_NUM_REGULATORS]; + struct regulator_dev *rdev[AB5500_NUM_REGULATORS]; +}; + +static int ab5500_regulator_enable_time(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + + return r->enable_time; /* microseconds */ +} + +static int ab5500_regulator_enable(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + int ret; + + ret = abx500_mask_and_set(ab5500->dev, r->bank, r->reg, + r->update_mask, r->mode); + if (ret < 0) + return ret; + + r->enabled = true; + + return 0; +} + +static int ab5500_regulator_disable(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + u8 regval; + int ret; + + if (r->off_is_lowpower) + regval = AB5500_LDO_MODE_LOWPOWER; + else + regval = AB5500_LDO_MODE_OFF; + + ret = abx500_mask_and_set(ab5500->dev, r->bank, r->reg, + r->update_mask, regval); + if (ret < 0) + return ret; + + r->enabled = false; + + return 0; +} + +static unsigned int ab5500_regulator_get_mode(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + + if (r->mode == r->update_val_idle) + return REGULATOR_MODE_IDLE; + + return REGULATOR_MODE_NORMAL; +} + +static unsigned int +ab5500_regulator_get_optimum_mode(struct regulator_dev *rdev, + int input_uV, int output_uV, int load_uA) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + unsigned int mode; + + if (load_uA <= r->load_lp_uA) + mode = REGULATOR_MODE_IDLE; + else + mode = REGULATOR_MODE_NORMAL; + + return mode; +} + +static int ab5500_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + + switch (mode) { + case REGULATOR_MODE_NORMAL: + r->mode = r->update_val_normal; + break; + case REGULATOR_MODE_IDLE: + r->mode = r->update_val_idle; + break; + default: + return -EINVAL; + } + + if (r->enabled) + return ab5500_regulator_enable(rdev); + + return 0; +} + +static int ab5500_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + u8 regval; + int err; + + err = abx500_get_register_interruptible(ab5500->dev, + r->bank, r->reg, ®val); + if (err) { + dev_err(rdev_get_dev(rdev), "unable to get register 0x%x\n", + r->reg); + return err; + } + + switch (regval & r->update_mask) { + case AB5500_LDO_MODE_PWRCTRL: + case AB5500_LDO_MODE_OFF: + r->enabled = false; + break; + case AB5500_LDO_MODE_LOWPOWER: + if (r->off_is_lowpower) { + r->enabled = false; + break; + } + /* fall through */ + default: + r->enabled = true; + break; + } + + return r->enabled; +} + +static int +ab5500_regulator_list_voltage(struct regulator_dev *rdev, unsigned selector) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + unsigned n_voltages = r->desc.n_voltages; + int selindex; + int i; + + for (i = 0, selindex = 0; selindex < n_voltages; i++) { + int voltage = r->voltages[i]; + + if (!voltage) + continue; + + if (selindex == selector) + return voltage; + + selindex++; + } + + return -EINVAL; +} + +static int ab5500_regulator_fixed_get_voltage(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + + return r->voltages[0]; +} + +static int ab5500_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + u8 regval; + int ret; + + ret = abx500_get_register_interruptible(ab5500->dev, + r->bank, r->reg, ®val); + if (ret) { + dev_warn(rdev_get_dev(rdev), + "failed to get regulator value in register " + "%02x\n", r->reg); + return ret; + } + + regval &= r->voltage_mask; + if (regval >= r->desc.n_voltages + r->num_holes) + return -EINVAL; + + if (!r->voltages[regval]) + return -EINVAL; + + return r->voltages[regval]; +} + +static int ab5500_get_best_voltage_index(struct ab5500_regulator *r, + int min_uV, int max_uV) +{ + unsigned n_voltages = r->desc.n_voltages; + int bestmatch = INT_MAX; + int bestindex = -EINVAL; + int selindex; + int i; + + /* + * Locate the minimum voltage fitting the criteria on + * this regulator. The switchable voltages are not + * in strict falling order so we need to check them + * all for the best match. + */ + for (i = 0, selindex = 0; selindex < n_voltages; i++) { + int voltage = r->voltages[i]; + + if (!voltage) + continue; + + if (voltage <= max_uV && + voltage >= min_uV && + voltage < bestmatch) { + bestmatch = voltage; + bestindex = i; + } + + selindex++; + } + + return bestindex; +} + +static int ab5500_regulator_set_voltage(struct regulator_dev *rdev, + int min_uV, int max_uV, + unsigned *selector) +{ + struct ab5500_regulators *ab5500 = rdev_get_drvdata(rdev); + struct ab5500_regulator *r = ab5500->regulator[rdev_get_id(rdev)]; + int bestindex; + + bestindex = ab5500_get_best_voltage_index(r, min_uV, max_uV); + if (bestindex < 0) { + dev_warn(rdev_get_dev(rdev), + "requested %d<=x<=%d uV, out of range!\n", + min_uV, max_uV); + return bestindex; + } + + *selector = bestindex; + + return abx500_mask_and_set_register_interruptible(ab5500->dev, + r->bank, r->reg, r->voltage_mask, bestindex); + +} + +static struct regulator_ops ab5500_regulator_variable_ops = { + .enable = ab5500_regulator_enable, + .disable = ab5500_regulator_disable, + .is_enabled = ab5500_regulator_is_enabled, + .enable_time = ab5500_regulator_enable_time, + .get_voltage = ab5500_regulator_get_voltage, + .set_voltage = ab5500_regulator_set_voltage, + .list_voltage = ab5500_regulator_list_voltage, + .set_mode = ab5500_regulator_set_mode, + .get_mode = ab5500_regulator_get_mode, + .get_optimum_mode = ab5500_regulator_get_optimum_mode, +}; + +static struct regulator_ops ab5500_regulator_fixed_ops = { + .enable = ab5500_regulator_enable, + .disable = ab5500_regulator_disable, + .is_enabled = ab5500_regulator_is_enabled, + .enable_time = ab5500_regulator_enable_time, + .get_voltage = ab5500_regulator_fixed_get_voltage, + .list_voltage = ab5500_regulator_list_voltage, + .set_mode = ab5500_regulator_set_mode, + .get_mode = ab5500_regulator_get_mode, + .get_optimum_mode = ab5500_regulator_get_optimum_mode, +}; + +static const int ab5500_ldo_lg_voltages[] = { + [0x00] = 1200000, + [0x01] = 0, /* not used */ + [0x02] = 1500000, + [0x03] = 1800000, + [0x04] = 0, /* not used */ + [0x05] = 2500000, + [0x06] = 2730000, + [0x07] = 2910000, +}; + +static const int ab5500_ldo_kh_voltages[] = { + [0x00] = 1200000, + [0x01] = 1500000, + [0x02] = 1800000, + [0x03] = 2100000, + [0x04] = 2500000, + [0x05] = 2750000, + [0x06] = 2790000, + [0x07] = 2910000, +}; + +static const int ab5500_ldo_vdigmic_voltages[] = { + [0x00] = 2100000, +}; + +static const int ab5500_ldo_sim_voltages[] = { + [0x00] = 1875000, + [0x01] = 2800000, + [0x02] = 2900000, +}; + +static const int ab5500_bias2_voltages[] = { + [0x00] = 2000000, + [0x01] = 2200000, +}; + +static const int ab5500_bias1_voltages[] = { + [0x00] = 2000000, +}; + +static struct ab5500_regulator ab5500_regulators[] = { + [AB5500_LDO_L] = { + .desc = { + .name = "LDO_L", + .id = AB5500_LDO_L, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_ldo_lg_voltages) - + 2, + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_L_ST, + .voltages = ab5500_ldo_lg_voltages, + .num_holes = 2, /* 2 register values unused */ + .enable_time = 400, + .load_lp_uA = 20000, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_LDO_G] = { + .desc = { + .name = "LDO_G", + .id = AB5500_LDO_G, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_ldo_lg_voltages) - + 2, + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_G_ST, + .voltages = ab5500_ldo_lg_voltages, + .num_holes = 2, /* 2 register values unused */ + .enable_time = 400, + .load_lp_uA = 20000, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_LDO_K] = { + .desc = { + .name = "LDO_K", + .id = AB5500_LDO_K, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_ldo_kh_voltages), + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_K_ST, + .voltages = ab5500_ldo_kh_voltages, + .enable_time = 400, + .load_lp_uA = 20000, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_LDO_H] = { + .desc = { + .name = "LDO_H", + .id = AB5500_LDO_H, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_ldo_kh_voltages), + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_H_ST, + .voltages = ab5500_ldo_kh_voltages, + .enable_time = 400, + .load_lp_uA = 20000, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_LDO_VDIGMIC] = { + .desc = { + .name = "LDO_VDIGMIC", + .id = AB5500_LDO_VDIGMIC, + .ops = &ab5500_regulator_fixed_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = + ARRAY_SIZE(ab5500_ldo_vdigmic_voltages), + }, + .bank = AB5500_BANK_STARTUP, + .reg = AB5500_LDO_VDIGMIC_ST, + .voltages = ab5500_ldo_vdigmic_voltages, + .enable_time = 450, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_LDO_SIM] = { + .desc = { + .name = "LDO_SIM", + .id = AB5500_LDO_SIM, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_ldo_sim_voltages), + }, + .bank = AB5500_BANK_SIM_USBSIM, + .reg = AB5500_SIM_SUP, + .voltages = ab5500_ldo_sim_voltages, + .enable_time = 1000, + .mode = AB5500_LDO_MODE_FULLPOWER, + .update_mask = AB5500_LDO_MODE_MASK, + .update_val_normal = AB5500_LDO_MODE_FULLPOWER, + .update_val_idle = AB5500_LDO_MODE_LOWPOWER, + .voltage_mask = AB5500_LDO_VOLT_MASK, + }, + [AB5500_BIAS2] = { + .desc = { + .name = "MBIAS2", + .id = AB5500_BIAS2, + .ops = &ab5500_regulator_variable_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_bias2_voltages), + }, + .bank = AB5500_BANK_AUDIO_HEADSETUSB, + .reg = AB5500_MBIAS2, + .voltages = ab5500_bias2_voltages, + .enable_time = 1000, + .mode = AB5500_MBIAS2_ENABLE, + .update_mask = AB5500_MBIAS2_MODE_MASK, + .update_val_normal = AB5500_MBIAS2_ENABLE, + .update_val_idle = AB5500_MBIAS2_ENABLE, + .voltage_mask = AB5500_MBIAS2_VOLT_MASK, + }, + [AB5500_BIAS1] = { + .desc = { + .name = "MBIAS1", + .id = AB5500_BIAS1, + .ops = &ab5500_regulator_fixed_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ab5500_bias1_voltages), + }, + .bank = AB5500_BANK_AUDIO_HEADSETUSB, + .reg = AB5500_MBIAS1, + .voltages = ab5500_bias1_voltages, + .enable_time = 1000, + .mode = AB5500_MBIAS1_ENABLE, + .update_mask = AB5500_MBIAS1_MODE_MASK, + .update_val_normal = AB5500_MBIAS1_ENABLE, + .update_val_idle = AB5500_MBIAS1_ENABLE, + }, +}; + + +static int __devinit ab5500_regulator_probe(struct platform_device *pdev) +{ + struct ab5500_platform_data *ppdata = pdev->dev.parent->platform_data; + struct ab5500_regulator_platform_data *pdata = ppdata->regulator; + struct ab5500_regulator_data *regdata; + struct ab5500_regulators *ab5500; + int err = 0; + int i; + + if (!pdata || !pdata->regulator) + return -EINVAL; + + ab5500 = kzalloc(sizeof(*ab5500), GFP_KERNEL); + if (!ab5500) + return -ENOMEM; + + ab5500->dev = &pdev->dev; + regdata = pdata->data; + + platform_set_drvdata(pdev, ab5500); + + for (i = 0; i < AB5500_NUM_REGULATORS; i++) { + struct ab5500_regulator *regulator = &ab5500_regulators[i]; + struct regulator_dev *rdev; + + if (regdata) + regulator->off_is_lowpower = regdata[i].off_is_lowpower; + + ab5500->regulator[i] = regulator; + + rdev = regulator_register(®ulator->desc, &pdev->dev, + &pdata->regulator[i], ab5500); + if (IS_ERR(rdev)) { + err = PTR_ERR(rdev); + dev_err(&pdev->dev, "failed to register regulator %s err %d\n", + regulator->desc.name, err); + goto err_unregister; + } + + ab5500->rdev[i] = rdev; + } + + return 0; + +err_unregister: + /* remove the already registered regulators */ + while (--i >= 0) + regulator_unregister(ab5500->rdev[i]); + + platform_set_drvdata(pdev, NULL); + kfree(ab5500); + + return err; +} + +static int __devexit ab5500_regulators_remove(struct platform_device *pdev) +{ + struct ab5500_regulators *ab5500 = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < AB5500_NUM_REGULATORS; i++) + regulator_unregister(ab5500->rdev[i]); + + platform_set_drvdata(pdev, NULL); + kfree(ab5500); + + return 0; +} + +static struct platform_driver ab5500_regulator_driver = { + .driver = { + .name = "ab5500-regulator", + .owner = THIS_MODULE, + }, + .probe = ab5500_regulator_probe, + .remove = __devexit_p(ab5500_regulators_remove), +}; + +static __init int ab5500_regulator_init(void) +{ + return platform_driver_register(&ab5500_regulator_driver); +} + +static __exit void ab5500_regulator_exit(void) +{ + platform_driver_unregister(&ab5500_regulator_driver); +} + +subsys_initcall(ab5500_regulator_init); +module_exit(ab5500_regulator_exit); + +MODULE_DESCRIPTION("AB5500 Regulator Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:ab5500-regulator"); diff --git a/drivers/regulator/ab8500-debug.c b/drivers/regulator/ab8500-debug.c new file mode 100644 index 00000000000..f71cc26c135 --- /dev/null +++ b/drivers/regulator/ab8500-debug.c @@ -0,0 +1,2083 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson. + * + * License Terms: GNU General Public License v2 + */ + +#include <linux/module.h> +#include <linux/seq_file.h> +#include <linux/uaccess.h> +#include <linux/fs.h> +#include <linux/debugfs.h> +#include <linux/platform_device.h> +#include <linux/kobject.h> +#include <linux/slab.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/regulator/ab8500-debug.h> +#include <linux/io.h> + +#include <mach/db8500-regs.h> /* U8500_BACKUPRAM1_BASE */ +#include <mach/hardware.h> + +#include "ab8500-debug.h" + +/* board profile address - to determine if suspend-force is default */ +#define BOOT_INFO_BACKUPRAM1 (U8500_BACKUPRAM1_BASE + 0xffc) +#define BOARD_PROFILE_BACKUPRAM1 (0x3) + +/* board profile option */ +#define OPTION_BOARD_VERSION_V5X 50 + +/* for error prints */ +struct device *dev; +struct platform_device *pdev; + +/* setting for suspend force (disabled by default) */ +static bool setting_suspend_force; + +/* + * regulator states + */ +enum ab8500_regulator_state_id { + AB8500_REGULATOR_STATE_INIT, + AB8500_REGULATOR_STATE_SUSPEND, + AB8500_REGULATOR_STATE_SUSPEND_CORE, + AB8500_REGULATOR_STATE_RESUME_CORE, + AB8500_REGULATOR_STATE_RESUME, + AB8500_REGULATOR_STATE_CURRENT, + NUM_REGULATOR_STATE +}; + +static const char *regulator_state_name[NUM_REGULATOR_STATE] = { + [AB8500_REGULATOR_STATE_INIT] = "init", + [AB8500_REGULATOR_STATE_SUSPEND] = "suspend", + [AB8500_REGULATOR_STATE_SUSPEND_CORE] = "suspend-core", + [AB8500_REGULATOR_STATE_RESUME_CORE] = "resume-core", + [AB8500_REGULATOR_STATE_RESUME] = "resume", + [AB8500_REGULATOR_STATE_CURRENT] = "current", +}; + +/* + * regulator register definitions + */ +enum ab8500_register_id { + AB8500_REGU_NOUSE, /* if not defined */ + AB8500_REGU_REQUEST_CTRL1, + AB8500_REGU_REQUEST_CTRL2, + AB8500_REGU_REQUEST_CTRL3, + AB8500_REGU_REQUEST_CTRL4, + AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + AB8500_REGU_HW_HP_REQ1_VALID1, + AB8500_REGU_HW_HP_REQ1_VALID2, + AB8500_REGU_HW_HP_REQ2_VALID1, + AB8500_REGU_HW_HP_REQ2_VALID2, + AB8500_REGU_SW_HP_REQ_VALID1, + AB8500_REGU_SW_HP_REQ_VALID2, + AB8500_REGU_SYSCLK_REQ1_VALID, + AB8500_REGU_SYSCLK_REQ2_VALID, + AB9540_REGU_VAUX4_REQ_VALID, + AB8500_REGU_MISC1, + AB8500_REGU_OTG_SUPPLY_CTRL, + AB8500_REGU_VUSB_CTRL, + AB8500_REGU_VAUDIO_SUPPLY, + AB8500_REGU_CTRL1_VAMIC, + AB8500_REGU_ARM_REGU1, + AB8500_REGU_ARM_REGU2, + AB8500_REGU_VAPE_REGU, + AB8500_REGU_VSMPS1_REGU, + AB8500_REGU_VSMPS2_REGU, + AB8500_REGU_VSMPS3_REGU, + AB8500_REGU_VPLL_VANA_REGU, + AB8500_REGU_VREF_DDR, + AB8500_REGU_EXT_SUPPLY_REGU, + AB8500_REGU_VAUX12_REGU, + AB8500_REGU_VRF1_VAUX3_REGU, + AB8500_REGU_VARM_SEL1, + AB8500_REGU_VARM_SEL2, + AB8500_REGU_VARM_SEL3, + AB8500_REGU_VAPE_SEL1, + AB8500_REGU_VAPE_SEL2, + AB8500_REGU_VAPE_SEL3, + AB9540_REGU_VAUX4_REQ_CTRL, + AB9540_REGU_VAUX4_REGU, + AB9540_REGU_VAUX4_SEL, + AB8500_REGU_VBB_SEL1, + AB8500_REGU_VBB_SEL2, + AB8500_REGU_VSMPS1_SEL1, + AB8500_REGU_VSMPS1_SEL2, + AB8500_REGU_VSMPS1_SEL3, + AB8500_REGU_VSMPS2_SEL1, + AB8500_REGU_VSMPS2_SEL2, + AB8500_REGU_VSMPS2_SEL3, + AB8500_REGU_VSMPS3_SEL1, + AB8500_REGU_VSMPS3_SEL2, + AB8500_REGU_VSMPS3_SEL3, + AB8500_REGU_VAUX1_SEL, + AB8500_REGU_VAUX2_SEL, + AB8500_REGU_VRF1_VAUX3_SEL, + AB8500_REGU_CTRL_EXT_SUP, + AB8500_REGU_VMOD_REGU, + AB8500_REGU_VMOD_SEL1, + AB8500_REGU_VMOD_SEL2, + AB8500_REGU_CTRL_DISCH, + AB8500_REGU_CTRL_DISCH2, + AB9540_REGU_CTRL_DISCH3, + AB8500_OTHER_SYSCLK_CTRL, /* Other */ + AB8500_OTHER_VSIM_SYSCLK_CTRL, /* Other */ + AB8500_OTHER_SYSULPCLK_CTRL1, /* Other */ + NUM_AB8500_REGISTER +}; + +struct ab8500_register { + const char *name; + u8 bank; + u8 addr; + u8 unavailable; /* Used to flag when AB doesn't support a register */ +}; + +static struct ab8500_register + ab8500_register[NUM_AB8500_REGISTER] = { + [AB8500_REGU_REQUEST_CTRL1] = { + .name = "ReguRequestCtrl1", + .bank = 0x03, + .addr = 0x03, + }, + [AB8500_REGU_REQUEST_CTRL2] = { + .name = "ReguRequestCtrl2", + .bank = 0x03, + .addr = 0x04, + }, + [AB8500_REGU_REQUEST_CTRL3] = { + .name = "ReguRequestCtrl3", + .bank = 0x03, + .addr = 0x05, + }, + [AB8500_REGU_REQUEST_CTRL4] = { + .name = "ReguRequestCtrl4", + .bank = 0x03, + .addr = 0x06, + }, + [AB8500_REGU_SYSCLK_REQ1_HP_VALID1] = { + .name = "ReguSysClkReq1HPValid", + .bank = 0x03, + .addr = 0x07, + }, + [AB8500_REGU_SYSCLK_REQ1_HP_VALID2] = { + .name = "ReguSysClkReq1HPValid2", + .bank = 0x03, + .addr = 0x08, + }, + [AB8500_REGU_HW_HP_REQ1_VALID1] = { + .name = "ReguHwHPReq1Valid1", + .bank = 0x03, + .addr = 0x09, + }, + [AB8500_REGU_HW_HP_REQ1_VALID2] = { + .name = "ReguHwHPReq1Valid2", + .bank = 0x03, + .addr = 0x0a, + }, + [AB8500_REGU_HW_HP_REQ2_VALID1] = { + .name = "ReguHwHPReq2Valid1", + .bank = 0x03, + .addr = 0x0b, + }, + [AB8500_REGU_HW_HP_REQ2_VALID2] = { + .name = "ReguHwHPReq2Valid2", + .bank = 0x03, + .addr = 0x0c, + }, + [AB8500_REGU_SW_HP_REQ_VALID1] = { + .name = "ReguSwHPReqValid1", + .bank = 0x03, + .addr = 0x0d, + }, + [AB8500_REGU_SW_HP_REQ_VALID2] = { + .name = "ReguSwHPReqValid2", + .bank = 0x03, + .addr = 0x0e, + }, + [AB8500_REGU_SYSCLK_REQ1_VALID] = { + .name = "ReguSysClkReqValid1", + .bank = 0x03, + .addr = 0x0f, + }, + [AB8500_REGU_SYSCLK_REQ2_VALID] = { + .name = "ReguSysClkReqValid2", + .bank = 0x03, + .addr = 0x10, + }, + [AB9540_REGU_VAUX4_REQ_VALID] = { + .name = "ReguVaux4ReqValid", + .bank = 0x03, + .addr = 0x11, + .unavailable = true, /* ab9540 register */ + }, + [AB8500_REGU_MISC1] = { + .name = "ReguMisc1", + .bank = 0x03, + .addr = 0x80, + }, + [AB8500_REGU_OTG_SUPPLY_CTRL] = { + .name = "OTGSupplyCtrl", + .bank = 0x03, + .addr = 0x81, + }, + [AB8500_REGU_VUSB_CTRL] = { + .name = "VusbCtrl", + .bank = 0x03, + .addr = 0x82, + }, + [AB8500_REGU_VAUDIO_SUPPLY] = { + .name = "VaudioSupply", + .bank = 0x03, + .addr = 0x83, + }, + [AB8500_REGU_CTRL1_VAMIC] = { + .name = "ReguCtrl1VAmic", + .bank = 0x03, + .addr = 0x84, + }, + [AB8500_REGU_ARM_REGU1] = { + .name = "ArmRegu1", + .bank = 0x04, + .addr = 0x00, + }, + [AB8500_REGU_ARM_REGU2] = { + .name = "ArmRegu2", + .bank = 0x04, + .addr = 0x01, + }, + [AB8500_REGU_VAPE_REGU] = { + .name = "VapeRegu", + .bank = 0x04, + .addr = 0x02, + }, + [AB8500_REGU_VSMPS1_REGU] = { + .name = "Vsmps1Regu", + .bank = 0x04, + .addr = 0x03, + }, + [AB8500_REGU_VSMPS2_REGU] = { + .name = "Vsmps2Regu", + .bank = 0x04, + .addr = 0x04, + }, + [AB8500_REGU_VSMPS3_REGU] = { + .name = "Vsmps3Regu", + .bank = 0x04, + .addr = 0x05, + }, + [AB8500_REGU_VPLL_VANA_REGU] = { + .name = "VpllVanaRegu", + .bank = 0x04, + .addr = 0x06, + }, + [AB8500_REGU_VREF_DDR] = { + .name = "VrefDDR", + .bank = 0x04, + .addr = 0x07, + }, + [AB8500_REGU_EXT_SUPPLY_REGU] = { + .name = "ExtSupplyRegu", + .bank = 0x04, + .addr = 0x08, + }, + [AB8500_REGU_VAUX12_REGU] = { + .name = "Vaux12Regu", + .bank = 0x04, + .addr = 0x09, + }, + [AB8500_REGU_VRF1_VAUX3_REGU] = { + .name = "VRF1Vaux3Regu", + .bank = 0x04, + .addr = 0x0a, + }, + [AB8500_REGU_VARM_SEL1] = { + .name = "VarmSel1", + .bank = 0x04, + .addr = 0x0b, + }, + [AB8500_REGU_VARM_SEL2] = { + .name = "VarmSel2", + .bank = 0x04, + .addr = 0x0c, + }, + [AB8500_REGU_VARM_SEL3] = { + .name = "VarmSel3", + .bank = 0x04, + .addr = 0x0d, + }, + [AB8500_REGU_VAPE_SEL1] = { + .name = "VapeSel1", + .bank = 0x04, + .addr = 0x0e, + }, + [AB8500_REGU_VAPE_SEL2] = { + .name = "VapeSel2", + .bank = 0x04, + .addr = 0x0f, + }, + [AB8500_REGU_VAPE_SEL3] = { + .name = "VapeSel3", + .bank = 0x04, + .addr = 0x10, + }, + [AB9540_REGU_VAUX4_REQ_CTRL] = { + .name = "Vaux4ReqCtrl", + .bank = 0x04, + .addr = 0x2d, + .unavailable = true, /* ab9540 register */ + }, + [AB9540_REGU_VAUX4_REGU] = { + .name = "Vaux4Regu", + .bank = 0x04, + .addr = 0x2e, + .unavailable = true, /* ab9540 register */ + }, + [AB9540_REGU_VAUX4_SEL] = { + .name = "Vaux4Sel", + .bank = 0x04, + .addr = 0x2f, + .unavailable = true, /* ab9540 register */ + }, + [AB8500_REGU_VBB_SEL1] = { + .name = "VBBSel1", + .bank = 0x04, + .addr = 0x11, + }, + [AB8500_REGU_VBB_SEL2] = { + .name = "VBBSel2", + .bank = 0x04, + .addr = 0x12, + }, + [AB8500_REGU_VSMPS1_SEL1] = { + .name = "Vsmps1Sel1", + .bank = 0x04, + .addr = 0x13, + }, + [AB8500_REGU_VSMPS1_SEL2] = { + .name = "Vsmps1Sel2", + .bank = 0x04, + .addr = 0x14, + }, + [AB8500_REGU_VSMPS1_SEL3] = { + .name = "Vsmps1Sel3", + .bank = 0x04, + .addr = 0x15, + }, + [AB8500_REGU_VSMPS2_SEL1] = { + .name = "Vsmps2Sel1", + .bank = 0x04, + .addr = 0x17, + }, + [AB8500_REGU_VSMPS2_SEL2] = { + .name = "Vsmps2Sel2", + .bank = 0x04, + .addr = 0x18, + }, + [AB8500_REGU_VSMPS2_SEL3] = { + .name = "Vsmps2Sel3", + .bank = 0x04, + .addr = 0x19, + }, + [AB8500_REGU_VSMPS3_SEL1] = { + .name = "Vsmps3Sel1", + .bank = 0x04, + .addr = 0x1b, + }, + [AB8500_REGU_VSMPS3_SEL2] = { + .name = "Vsmps3Sel2", + .bank = 0x04, + .addr = 0x1c, + }, + [AB8500_REGU_VSMPS3_SEL3] = { + .name = "Vsmps3Sel3", + .bank = 0x04, + .addr = 0x1d, + }, + [AB8500_REGU_VAUX1_SEL] = { + .name = "Vaux1Sel", + .bank = 0x04, + .addr = 0x1f, + }, + [AB8500_REGU_VAUX2_SEL] = { + .name = "Vaux2Sel", + .bank = 0x04, + .addr = 0x20, + }, + [AB8500_REGU_VRF1_VAUX3_SEL] = { + .name = "VRF1Vaux3Sel", + .bank = 0x04, + .addr = 0x21, + }, + [AB8500_REGU_CTRL_EXT_SUP] = { + .name = "ReguCtrlExtSup", + .bank = 0x04, + .addr = 0x22, + }, + [AB8500_REGU_VMOD_REGU] = { + .name = "VmodRegu", + .bank = 0x04, + .addr = 0x40, + }, + [AB8500_REGU_VMOD_SEL1] = { + .name = "VmodSel1", + .bank = 0x04, + .addr = 0x41, + }, + [AB8500_REGU_VMOD_SEL2] = { + .name = "VmodSel2", + .bank = 0x04, + .addr = 0x42, + }, + [AB8500_REGU_CTRL_DISCH] = { + .name = "ReguCtrlDisch", + .bank = 0x04, + .addr = 0x43, + }, + [AB8500_REGU_CTRL_DISCH2] = { + .name = "ReguCtrlDisch2", + .bank = 0x04, + .addr = 0x44, + }, + [AB9540_REGU_CTRL_DISCH3] = { + .name = "ReguCtrlDisch3", + .bank = 0x04, + .addr = 0x48, + .unavailable = true, /* ab9540 register */ + }, + /* Outside regulator banks */ + [AB8500_OTHER_SYSCLK_CTRL] = { + .name = "SysClkCtrl", + .bank = 0x02, + .addr = 0x0c, + }, + [AB8500_OTHER_VSIM_SYSCLK_CTRL] = { + .name = "VsimSysClkCtrl", + .bank = 0x02, + .addr = 0x33, + }, + [AB8500_OTHER_SYSULPCLK_CTRL1] = { + .name = "SysUlpClkCtrl1", + .bank = 0x02, + .addr = 0x0b, + }, +}; + +struct ab9540_register_update { + /* Identity of register to be updated */ + u8 bank; + u8 addr; + /* New value for unavailable flag */ + u8 unavailable; +}; + +static const struct ab9540_register_update ab9540_update[] = { + /* AB8500 register which is unavailable to AB9540 */ + /* AB8500_REGU_VREF_DDR */ + { + .bank = 0x04, + .addr = 0x07, + .unavailable = true, + }, + + /* Registers which were not available to AB8500 but are on the + * AB9540. */ + /* AB9540_REGU_VAUX4_REQ_VALID */ + { + .bank = 0x03, + .addr = 0x11, + }, + /* AB9540_REGU_VAUX4_REQ_CTRL */ + { + .bank = 0x04, + .addr = 0x2d, + }, + /* AB9540_REGU_VAUX4_REGU */ + { + .bank = 0x04, + .addr = 0x2e, + }, + /* AB9540_REGU_VAUX4_SEL */ + { + .bank = 0x04, + .addr = 0x2f, + }, + /* AB9540_REGU_CTRL_DISCH3 */ + { + .bank = 0x04, + .addr = 0x48, + }, +}; + +static void ab9540_registers_update(void) +{ + int i; + int j; + + for (i = 0; i < NUM_AB8500_REGISTER; i++) + for (j = 0; j < ARRAY_SIZE(ab9540_update); j++) + if (ab8500_register[i].bank == ab9540_update[j].bank && + ab8500_register[i].addr == ab9540_update[j].addr) { + ab8500_register[i].unavailable = + ab9540_update[j].unavailable; + break; + } +} + +static u8 ab8500_register_state[NUM_REGULATOR_STATE][NUM_AB8500_REGISTER]; +static bool ab8500_register_state_saved[NUM_REGULATOR_STATE]; +static bool ab8500_register_state_save = true; + +static int ab8500_regulator_record_state(int state) +{ + u8 val; + int i; + int ret; + + /* check arguments */ + if ((state > NUM_REGULATOR_STATE) || (state < 0)) { + dev_err(dev, "Wrong state specified\n"); + return -EINVAL; + } + + /* record */ + if (!ab8500_register_state_save) + goto exit; + + ab8500_register_state_saved[state] = true; + + for (i = 1; i < NUM_AB8500_REGISTER; i++) { + if (ab8500_register[i].unavailable) + continue; + + ret = abx500_get_register_interruptible(dev, + ab8500_register[i].bank, + ab8500_register[i].addr, + &val); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + + ab8500_register_state[state][i] = val; + } +exit: + return 0; +} + +/* + * regulator register dump + */ +static int ab8500_regulator_dump_print(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + int state, reg_id, i; + int err; + + /* record current state */ + ab8500_regulator_record_state(AB8500_REGULATOR_STATE_CURRENT); + + /* print dump header */ + err = seq_printf(s, "ab8500-regulator dump:\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + + /* print states */ + for (state = NUM_REGULATOR_STATE - 1; state >= 0; state--) { + if (ab8500_register_state_saved[state]) + err = seq_printf(s, "%16s saved -------", + regulator_state_name[state]); + else + err = seq_printf(s, "%12s not saved -------", + regulator_state_name[state]); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + for (i = 0; i < NUM_REGULATOR_STATE; i++) { + if (i < state) + err = seq_printf(s, "-----"); + else if (i == state) + err = seq_printf(s, "----+"); + else + err = seq_printf(s, " |"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", + __LINE__); + } + err = seq_printf(s, "\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + } + + /* print labels */ + err = seq_printf(s, "\n addr\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + /* dump registers */ + for (reg_id = 1; reg_id < NUM_AB8500_REGISTER; reg_id++) { + if (ab8500_register[reg_id].unavailable) + continue; + + err = seq_printf(s, "%22s 0x%02x%02x:", + ab8500_register[reg_id].name, + ab8500_register[reg_id].bank, + ab8500_register[reg_id].addr); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + reg_id, __LINE__); + + for (state = 0; state < NUM_REGULATOR_STATE; state++) { + err = seq_printf(s, " 0x%02x", + ab8500_register_state[state][reg_id]); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + reg_id, __LINE__); + } + + err = seq_printf(s, "\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + reg_id, __LINE__); + } + + return 0; +} + +static int ab8500_regulator_dump_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_regulator_dump_print, inode->i_private); +} + +static const struct file_operations ab8500_regulator_dump_fops = { + .open = ab8500_regulator_dump_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +/* + * regulator_voltage + */ +struct regulator_volt { + u8 value; + int volt; +}; + +struct regulator_volt_range { + struct regulator_volt start; + struct regulator_volt step; + struct regulator_volt end; +}; + +/* + * ab8500_regulator + * @name + * @update_regid + * @update_mask + * @update_val[4] {off, on, hw, lp} + * @hw_mode_regid + * @hw_mode_mask + * @hw_mode_val[4] {hp/lp, hp/off, hp, hp} + * @hw_valid_regid[4] {sysclkreq1, hw1, hw2, sw} + * @hw_valid_mask[4] {sysclkreq1, hw1, hw2, sw} + * @vsel_sel_regid + * @vsel_sel_mask + * @vsel_val[333] {sel1, sel2, sel3, sel3} + * @vsel_regid + * @vsel_mask + * @vsel_range + * @vsel_range_len + * @unavailable {true/false depending on whether AB supports the regulator} + */ +struct ab8500_regulator { + const char *name; + int update_regid; + u8 update_mask; + u8 update_val[4]; + int hw_mode_regid; + u8 hw_mode_mask; + u8 hw_mode_val[4]; + int hw_valid_regid[4]; + u8 hw_valid_mask[4]; + int vsel_sel_regid; + u8 vsel_sel_mask; + u8 vsel_sel_val[4]; + int vsel_regid[3]; + u8 vsel_mask[3]; + struct regulator_volt_range const *vsel_range[3]; + int vsel_range_len[3]; + u8 unavailable; +}; + +static const char *update_val_name[] = { + "off", + "on ", + "hw ", + "lp ", + " - " /* undefined value */ +}; + +static const char *hw_mode_val_name[] = { + "hp/lp ", + "hp/off", + "hp ", + "hp ", + "-/- ", /* undefined value */ +}; + +/* voltage selection */ +/* AB8500 device - Varm_vsel in 12.5mV steps */ +#define AB8500_VARM_VSEL_MASK 0x3f +static const struct regulator_volt_range ab8500_varm_vsel[] = { + { {0x00, 700000}, {0x01, 12500}, {0x35, 1362500} }, + { {0x36, 1362500}, {0x01, 0}, {0x3f, 1362500} }, +}; + +/* AB9540 device - Varm_vsel in 6.25mV steps */ +#define AB9540_VARM_VSEL_MASK 0x7f +static const struct regulator_volt_range ab9540_varm_vsel[] = { + { {0x00, 600000}, {0x01, 6250}, {0x7f, 1393750} }, +}; + +static const struct regulator_volt_range vape_vmod_vsel[] = { + { {0x00, 700000}, {0x01, 12500}, {0x35, 1362500} }, + { {0x36, 1362500}, {0x01, 0}, {0x3f, 1362500} }, +}; + +/* AB8500 device - Vbbp_vsel and Vbbn_sel in 100mV steps */ +static const struct regulator_volt_range ab8500_vbbp_vsel[] = { + { {0x00, 0}, {0x10, 100000}, {0x40, 400000} }, + { {0x50, 400000}, {0x10, 0}, {0x70, 400000} }, + { {0x80, -400000}, {0x10, 0}, {0xb0, -400000} }, + { {0xc0, -400000}, {0x10, 100000}, {0xf0, -100000} }, +}; + +static const struct regulator_volt_range ab8500_vbbn_vsel[] = { + { {0x00, 0}, {0x01, -100000}, {0x04, -400000} }, + { {0x05, -400000}, {0x01, 0}, {0x07, -400000} }, + { {0x08, 0}, {0x01, 100000}, {0x0c, 400000} }, + { {0x0d, 400000}, {0x01, 0}, {0x0f, 400000} }, +}; + +/* AB9540 device - Vbbp_vsel and Vbbn_sel in 50mV steps */ +static const struct regulator_volt_range ab9540_vbbp_vsel[] = { + { {0x00, 0}, {0x10, -50000}, {0x70, -350000} }, + { {0x80, 50000}, {0x10, 50000}, {0xf0, 400000} }, +}; + +static const struct regulator_volt_range ab9540_vbbn_vsel[] = { + { {0x00, 0}, {0x01, -50000}, {0x07, -350000} }, + { {0x08, 50000}, {0x01, 50000}, {0x0f, 400000} }, +}; + +static const struct regulator_volt_range vsmps1_vsel[] = { + { {0x00, 1100000}, {0x01, 0}, {0x1f, 1100000} }, + { {0x20, 1100000}, {0x01, 12500}, {0x30, 1300000} }, + { {0x31, 1300000}, {0x01, 0}, {0x3f, 1300000} }, +}; + +static const struct regulator_volt_range vsmps2_vsel[] = { + { {0x00, 1800000}, {0x01, 0}, {0x38, 1800000} }, + { {0x39, 1800000}, {0x01, 12500}, {0x7f, 1875000} }, +}; + +static const struct regulator_volt_range vsmps3_vsel[] = { + { {0x00, 700000}, {0x01, 12500}, {0x35, 1363500} }, + { {0x36, 1363500}, {0x01, 0}, {0x7f, 1363500} }, +}; + +/* for Vaux1, Vaux2 and Vaux4 */ +static const struct regulator_volt_range vauxn_vsel[] = { + { {0x00, 1100000}, {0x01, 100000}, {0x04, 1500000} }, + { {0x05, 1800000}, {0x01, 50000}, {0x07, 1900000} }, + { {0x08, 2500000}, {0x01, 0}, {0x08, 2500000} }, + { {0x09, 2650000}, {0x01, 50000}, {0x0c, 2800000} }, + { {0x0d, 2900000}, {0x01, 100000}, {0x0e, 3000000} }, + { {0x0f, 3300000}, {0x01, 0}, {0x0f, 3300000} }, +}; + +static const struct regulator_volt_range vaux3_vsel[] = { + { {0x00, 1200000}, {0x01, 300000}, {0x03, 2100000} }, + { {0x04, 2500000}, {0x01, 250000}, {0x05, 2750000} }, + { {0x06, 2790000}, {0x01, 0}, {0x06, 2790000} }, + { {0x07, 2910000}, {0x01, 0}, {0x07, 2910000} }, +}; + +static const struct regulator_volt_range vrf1_vsel[] = { + { {0x00, 1800000}, {0x10, 200000}, {0x10, 2000000} }, + { {0x20, 2150000}, {0x10, 0}, {0x20, 2150000} }, + { {0x30, 2500000}, {0x10, 0}, {0x30, 2500000} }, +}; + +static const struct regulator_volt_range vintcore12_vsel[] = { + { {0x00, 1200000}, {0x08, 25000}, {0x30, 1350000} }, + { {0x38, 1350000}, {0x01, 0}, {0x38, 1350000} }, +}; + +/* regulators */ +static struct ab8500_regulator ab8500_regulator[AB8500_NUM_REGULATORS] = { + [AB8500_VARM] = { + .name = "Varm", + .update_regid = AB8500_REGU_ARM_REGU1, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x02, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x02, + .vsel_sel_regid = AB8500_REGU_ARM_REGU1, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VARM_SEL1, + .vsel_mask[0] = AB8500_VARM_VSEL_MASK, + .vsel_range[0] = ab8500_varm_vsel, + .vsel_range_len[0] = ARRAY_SIZE(ab8500_varm_vsel), + .vsel_regid[1] = AB8500_REGU_VARM_SEL2, + .vsel_mask[1] = AB8500_VARM_VSEL_MASK, + .vsel_range[1] = ab8500_varm_vsel, + .vsel_range_len[1] = ARRAY_SIZE(ab8500_varm_vsel), + .vsel_regid[2] = AB8500_REGU_VARM_SEL3, + .vsel_mask[2] = AB8500_VARM_VSEL_MASK, + .vsel_range[2] = ab8500_varm_vsel, + .vsel_range_len[2] = ARRAY_SIZE(ab8500_varm_vsel), + }, + [AB8500_VBBP] = { + .name = "Vbbp", + .update_regid = AB8500_REGU_ARM_REGU2, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x00}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x04, + .vsel_sel_regid = AB8500_REGU_ARM_REGU1, + .vsel_sel_mask = 0x10, + .vsel_sel_val = {0x00, 0x10, 0x00, 0x00}, + .vsel_regid[0] = AB8500_REGU_VBB_SEL1, + .vsel_mask[0] = 0xf0, + .vsel_range[0] = ab8500_vbbp_vsel, + .vsel_range_len[0] = ARRAY_SIZE(ab8500_vbbp_vsel), + .vsel_regid[1] = AB8500_REGU_VBB_SEL2, + .vsel_mask[1] = 0xf0, + .vsel_range[1] = ab8500_vbbp_vsel, + .vsel_range_len[1] = ARRAY_SIZE(ab8500_vbbp_vsel), + }, + [AB8500_VBBN] = { + .name = "Vbbn", + .update_regid = AB8500_REGU_ARM_REGU2, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x00}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x04, + .vsel_sel_regid = AB8500_REGU_ARM_REGU1, + .vsel_sel_mask = 0x20, + .vsel_sel_val = {0x00, 0x20, 0x00, 0x00}, + .vsel_regid[0] = AB8500_REGU_VBB_SEL1, + .vsel_mask[0] = 0x0f, + .vsel_range[0] = ab8500_vbbn_vsel, + .vsel_range_len[0] = ARRAY_SIZE(ab8500_vbbn_vsel), + .vsel_regid[1] = AB8500_REGU_VBB_SEL2, + .vsel_mask[1] = 0x0f, + .vsel_range[1] = ab8500_vbbn_vsel, + .vsel_range_len[1] = ARRAY_SIZE(ab8500_vbbn_vsel), + }, + [AB8500_VAPE] = { + .name = "Vape", + .update_regid = AB8500_REGU_VAPE_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0x0c, + .hw_mode_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x01, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x01, + .vsel_sel_regid = AB8500_REGU_VAPE_REGU, + .vsel_sel_mask = 0x24, + .vsel_sel_val = {0x00, 0x04, 0x20, 0x24}, + .vsel_regid[0] = AB8500_REGU_VAPE_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = vape_vmod_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vape_vmod_vsel), + .vsel_regid[1] = AB8500_REGU_VAPE_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = vape_vmod_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vape_vmod_vsel), + .vsel_regid[2] = AB8500_REGU_VAPE_SEL3, + .vsel_mask[2] = 0x3f, + .vsel_range[2] = vape_vmod_vsel, + .vsel_range_len[2] = ARRAY_SIZE(vape_vmod_vsel), + }, + [AB8500_VSMPS1] = { + .name = "Vsmps1", + .update_regid = AB8500_REGU_VSMPS1_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0x30, + .hw_mode_val = {0x00, 0x10, 0x20, 0x30}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x01, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x01, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x01, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x04, + .vsel_sel_regid = AB8500_REGU_VSMPS1_REGU, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VSMPS1_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = vsmps1_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vsmps1_vsel), + .vsel_regid[1] = AB8500_REGU_VSMPS1_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = vsmps1_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vsmps1_vsel), + .vsel_regid[2] = AB8500_REGU_VSMPS1_SEL3, + .vsel_mask[2] = 0x3f, + .vsel_range[2] = vsmps1_vsel, + .vsel_range_len[2] = ARRAY_SIZE(vsmps1_vsel), + }, + [AB8500_VSMPS2] = { + .name = "Vsmps2", + .update_regid = AB8500_REGU_VSMPS2_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL1, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x02, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x02, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x02, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x08, + .vsel_sel_regid = AB8500_REGU_VSMPS2_REGU, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VSMPS2_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = vsmps2_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vsmps2_vsel), + .vsel_regid[1] = AB8500_REGU_VSMPS2_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = vsmps2_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vsmps2_vsel), + .vsel_regid[2] = AB8500_REGU_VSMPS2_SEL3, + .vsel_mask[2] = 0x3f, + .vsel_range[2] = vsmps2_vsel, + .vsel_range_len[2] = ARRAY_SIZE(vsmps2_vsel), + }, + [AB8500_VSMPS3] = { + .name = "Vsmps3", + .update_regid = AB8500_REGU_VSMPS3_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x04, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x04, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x04, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x10, + .vsel_sel_regid = AB8500_REGU_VSMPS3_REGU, + .vsel_sel_mask = 0x0c, + .vsel_sel_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VSMPS3_SEL1, + .vsel_mask[0] = 0x7f, + .vsel_range[0] = vsmps3_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vsmps3_vsel), + .vsel_regid[1] = AB8500_REGU_VSMPS3_SEL2, + .vsel_mask[1] = 0x7f, + .vsel_range[1] = vsmps3_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vsmps3_vsel), + .vsel_regid[2] = AB8500_REGU_VSMPS3_SEL3, + .vsel_mask[2] = 0x7f, + .vsel_range[2] = vsmps3_vsel, + .vsel_range_len[2] = ARRAY_SIZE(vsmps3_vsel), + }, + [AB8500_VPLL] = { + .name = "Vpll", + .update_regid = AB8500_REGU_VPLL_VANA_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0x0c, + .hw_mode_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x10, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x10, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x10, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x40, + }, + [AB8500_VREFDDR] = { + .name = "VrefDDR", + .update_regid = AB8500_REGU_VREF_DDR, + .update_mask = 0x01, + .update_val = {0x00, 0x01, 0x00, 0x00}, + }, + [AB8500_VMOD] = { + .name = "Vmod", + .update_regid = AB8500_REGU_VMOD_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_VMOD_REGU, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x08, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x08, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x08, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x20, + .vsel_sel_regid = AB8500_REGU_VMOD_REGU, + .vsel_sel_mask = 0x04, + .vsel_sel_val = {0x00, 0x04, 0x00, 0x00}, + .vsel_regid[0] = AB8500_REGU_VMOD_SEL1, + .vsel_mask[0] = 0x3f, + .vsel_range[0] = vape_vmod_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vape_vmod_vsel), + .vsel_regid[1] = AB8500_REGU_VMOD_SEL2, + .vsel_mask[1] = 0x3f, + .vsel_range[1] = vape_vmod_vsel, + .vsel_range_len[1] = ARRAY_SIZE(vape_vmod_vsel), + }, + [AB8500_VEXTSUPPLY1] = { + .name = "Vextsupply1", + .update_regid = AB8500_REGU_EXT_SUPPLY_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x10, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x01, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x01, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x04, + }, + [AB8500_VEXTSUPPLY2] = { + .name = "VextSupply2", + .update_regid = AB8500_REGU_EXT_SUPPLY_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x20, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x02, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x02, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x08, + }, + [AB8500_VEXTSUPPLY3] = { + .name = "VextSupply3", + .update_regid = AB8500_REGU_EXT_SUPPLY_REGU, + .update_mask = 0x30, + .update_val = {0x00, 0x10, 0x20, 0x30}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0x0c, + .hw_mode_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID2, + .hw_valid_mask[0] = 0x40, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID2, + .hw_valid_mask[1] = 0x04, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID2, + .hw_valid_mask[2] = 0x04, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x10, + }, + [AB8500_VRF1] = { + .name = "Vrf1", + .update_regid = AB8500_REGU_VRF1_VAUX3_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .vsel_regid[0] = AB8500_REGU_VRF1_VAUX3_SEL, + .vsel_mask[0] = 0x30, + .vsel_range[0] = vrf1_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vrf1_vsel), + }, + [AB8500_VANA] = { + .name = "Vana", + .update_regid = AB8500_REGU_VPLL_VANA_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL2, + .hw_mode_mask = 0x30, + .hw_mode_val = {0x00, 0x10, 0x20, 0x30}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x08, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x08, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x08, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x20, + }, + [AB8500_VAUX1] = { + .name = "Vaux1", + .update_regid = AB8500_REGU_VAUX12_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0x30, + .hw_mode_val = {0x00, 0x10, 0x20, 0x30}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x20, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x20, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x20, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID1, + .hw_valid_mask[3] = 0x80, + .vsel_regid[0] = AB8500_REGU_VAUX1_SEL, + .vsel_mask[0] = 0x0f, + .vsel_range[0] = vauxn_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vauxn_vsel), + }, + [AB8500_VAUX2] = { + .name = "Vaux2", + .update_regid = AB8500_REGU_VAUX12_REGU, + .update_mask = 0x0c, + .update_val = {0x00, 0x04, 0x08, 0x0c}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL3, + .hw_mode_mask = 0xc0, + .hw_mode_val = {0x00, 0x40, 0x80, 0xc0}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x40, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x40, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x40, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x01, + .vsel_regid[0] = AB8500_REGU_VAUX2_SEL, + .vsel_mask[0] = 0x0f, + .vsel_range[0] = vauxn_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vauxn_vsel), + }, + [AB8500_VAUX3] = { + .name = "Vaux3", + .update_regid = AB8500_REGU_VRF1_VAUX3_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB8500_REGU_REQUEST_CTRL4, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB8500_REGU_SYSCLK_REQ1_HP_VALID1, + .hw_valid_mask[0] = 0x80, + .hw_valid_regid[1] = AB8500_REGU_HW_HP_REQ1_VALID1, + .hw_valid_mask[1] = 0x80, + .hw_valid_regid[2] = AB8500_REGU_HW_HP_REQ2_VALID1, + .hw_valid_mask[2] = 0x80, + .hw_valid_regid[3] = AB8500_REGU_SW_HP_REQ_VALID2, + .hw_valid_mask[3] = 0x02, + .vsel_regid[0] = AB8500_REGU_VRF1_VAUX3_SEL, + .vsel_mask[0] = 0x07, + .vsel_range[0] = vaux3_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vaux3_vsel), + }, + [AB9540_VAUX4] = { + .name = "Vaux4", + .update_regid = AB9540_REGU_VAUX4_REGU, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x02, 0x03}, + .hw_mode_regid = AB9540_REGU_VAUX4_REQ_CTRL, + .hw_mode_mask = 0x03, + .hw_mode_val = {0x00, 0x01, 0x02, 0x03}, + .hw_valid_regid[0] = AB9540_REGU_VAUX4_REQ_VALID, + .hw_valid_mask[0] = 0x08, + .hw_valid_regid[1] = AB9540_REGU_VAUX4_REQ_VALID, + .hw_valid_mask[1] = 0x04, + .hw_valid_regid[2] = AB9540_REGU_VAUX4_REQ_VALID, + .hw_valid_mask[2] = 0x02, + .hw_valid_regid[3] = AB9540_REGU_VAUX4_REQ_VALID, + .hw_valid_mask[3] = 0x01, + .vsel_regid[0] = AB9540_REGU_VAUX4_SEL, + .vsel_mask[0] = 0x0f, + .vsel_range[0] = vauxn_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vauxn_vsel), + .unavailable = true, /* AB9540 regulator */ + }, + [AB8500_VINTCORE] = { + .name = "VintCore12", + .update_regid = AB8500_REGU_MISC1, + .update_mask = 0x44, + .update_val = {0x00, 0x04, 0x00, 0x44}, + .vsel_regid[0] = AB8500_REGU_MISC1, + .vsel_mask[0] = 0x38, + .vsel_range[0] = vintcore12_vsel, + .vsel_range_len[0] = ARRAY_SIZE(vintcore12_vsel), + }, + [AB8500_VTVOUT] = { + .name = "VTVout", + .update_regid = AB8500_REGU_MISC1, + .update_mask = 0x82, + .update_val = {0x00, 0x02, 0x00, 0x82}, + }, + [AB8500_VAUDIO] = { + .name = "Vaudio", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x02, + .update_val = {0x00, 0x02, 0x00, 0x00}, + }, + [AB8500_VANAMIC1] = { + .name = "Vanamic1", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x08, + .update_val = {0x00, 0x08, 0x00, 0x00}, + }, + [AB8500_VANAMIC2] = { + .name = "Vanamic2", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x10, + .update_val = {0x00, 0x10, 0x00, 0x00}, + }, + [AB8500_VDMIC] = { + .name = "Vdmic", + .update_regid = AB8500_REGU_VAUDIO_SUPPLY, + .update_mask = 0x04, + .update_val = {0x00, 0x04, 0x00, 0x00}, + }, + [AB8500_VUSB] = { + .name = "Vusb", + .update_regid = AB8500_REGU_VUSB_CTRL, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x00, 0x03}, + }, + [AB8500_VOTG] = { + .name = "VOTG", + .update_regid = AB8500_REGU_OTG_SUPPLY_CTRL, + .update_mask = 0x03, + .update_val = {0x00, 0x01, 0x00, 0x03}, + }, + [AB8500_VBUSBIS] = { + .name = "Vbusbis", + .update_regid = AB8500_REGU_OTG_SUPPLY_CTRL, + .update_mask = 0x08, + .update_val = {0x00, 0x08, 0x00, 0x00}, + }, +}; + +static void ab9540_regulators_update(void) +{ + /* Update unavailable regulators */ + ab8500_regulator[AB8500_VREFDDR].unavailable = true; + ab8500_regulator[AB9540_VAUX4].unavailable = false; + + /* Update regulator characteristics for AB9540 */ + ab8500_regulator[AB8500_VARM].vsel_mask[0] = AB9540_VARM_VSEL_MASK; + ab8500_regulator[AB8500_VARM].vsel_range[0] = ab9540_varm_vsel; + ab8500_regulator[AB8500_VARM].vsel_range_len[0] = + ARRAY_SIZE(ab9540_varm_vsel); + ab8500_regulator[AB8500_VARM].vsel_mask[1] = AB9540_VARM_VSEL_MASK; + ab8500_regulator[AB8500_VARM].vsel_range[1] = ab9540_varm_vsel; + ab8500_regulator[AB8500_VARM].vsel_range_len[1] = + ARRAY_SIZE(ab9540_varm_vsel); + ab8500_regulator[AB8500_VARM].vsel_mask[2] = AB9540_VARM_VSEL_MASK; + ab8500_regulator[AB8500_VARM].vsel_range[2] = ab9540_varm_vsel; + ab8500_regulator[AB8500_VARM].vsel_range_len[2] = + ARRAY_SIZE(ab9540_varm_vsel); + + ab8500_regulator[AB8500_VBBP].vsel_range[0] = ab9540_vbbp_vsel; + ab8500_regulator[AB8500_VBBP].vsel_range_len[0] = + ARRAY_SIZE(ab9540_vbbp_vsel); + ab8500_regulator[AB8500_VBBP].vsel_range[1] = ab9540_vbbp_vsel; + ab8500_regulator[AB8500_VBBP].vsel_range_len[1] = + ARRAY_SIZE(ab9540_vbbp_vsel); + + ab8500_regulator[AB8500_VBBN].vsel_range[0] = ab9540_vbbn_vsel; + ab8500_regulator[AB8500_VBBN].vsel_range_len[0] = + ARRAY_SIZE(ab9540_vbbn_vsel); + ab8500_regulator[AB8500_VBBN].vsel_range[1] = ab9540_vbbn_vsel; + ab8500_regulator[AB8500_VBBN].vsel_range_len[1] = + ARRAY_SIZE(ab9540_vbbn_vsel); +} + +static int status_state = AB8500_REGULATOR_STATE_CURRENT; + +static int _get_voltage(struct regulator_volt_range const *volt_range, + u8 value, int *volt) +{ + u8 start = volt_range->start.value; + u8 end = volt_range->end.value; + u8 step = volt_range->step.value; + + /* Check if witin range */ + if (step == 0) { + if (value == start) { + *volt = volt_range->start.volt; + return 1; + } + } else { + if ((start <= value) && (value <= end)) { + if ((value - start) % step != 0) + return -EINVAL; /* invalid setting */ + *volt = volt_range->start.volt + + volt_range->step.volt + *((value - start) / step); + return 1; + } + } + + return 0; +} + +static int get_voltage(struct regulator_volt_range const *volt_range, + int volt_range_len, + u8 value) +{ + int volt; + int i, ret; + + for (i = 0; i < volt_range_len; i++) { + ret = _get_voltage(&volt_range[i], value, &volt); + if (ret < 0) + break; /* invalid setting */ + if (ret == 1) + return volt; /* successful */ + } + + return -EINVAL; +} + +static bool get_reg_and_mask(int regid, u8 mask, u8 *val) +{ + int ret; + u8 t; + + if (!regid) + return false; + + ret = abx500_get_register_interruptible(dev, + ab8500_register[regid].bank, + ab8500_register[regid].addr, + &t); + if (ret < 0) + return false; + + (*val) = t & mask; + + return true; +} + +/* Convert regulator register value to index */ +static bool val2idx(u8 val, u8 *v, int len, int *idx) +{ + int i; + + for (i = 0; i < len && v[i] != val; i++); + + if (i == len) + return false; + + (*idx) = i; + return true; +} + +int ab8500_regulator_debug_read(enum ab8500_regulator_id id, + struct ab8500_debug_regulator_status *s) +{ + int i; + u8 val; + bool found; + int idx = 0; + + if (id >= AB8500_NUM_REGULATORS) + return -EINVAL; + + s->name = (char *)ab8500_regulator[id].name; + + /* read mode */ + (void) get_reg_and_mask(ab8500_regulator[id].update_regid, + ab8500_regulator[id].update_mask, + &val); + + (void) val2idx(val, ab8500_regulator[id].update_val, + 4, &idx); + + s->mode = (u8) idx; + + /* read hw mode */ + found = get_reg_and_mask(ab8500_regulator[id].hw_mode_regid, + ab8500_regulator[id].hw_mode_mask, + &val); + + if (found) + found = val2idx(val, ab8500_regulator[id].hw_mode_val, 4, &idx); + + if (found) + /* +1 since 0 = HWMODE_NONE */ + s->hwmode = idx + 1; + else + s->hwmode = AB8500_HWMODE_NONE; + + for (i = 0; i < 4 && found; i++) { + + bool f = get_reg_and_mask(ab8500_regulator[id].hw_valid_regid[i], + ab8500_regulator[id].hw_valid_mask[i], + &val); + if (f) + s->hwmode_auto[i] = !!val; + else + s->hwmode_auto[i] = HWM_INVAL; + } + + /* read voltage */ + found = get_reg_and_mask(ab8500_regulator[id].vsel_sel_regid, + ab8500_regulator[id].vsel_sel_mask, + &val); + if (found) + found = val2idx(val, ab8500_regulator[id].vsel_sel_val, + 3, &idx); + + if (found && idx < 3) + s->volt_selected = idx + 1; + else + s->volt_selected = 0; + + for (s->volt_len = 0; s->volt_len < 3; s->volt_len++) { + int volt; + int i = s->volt_len; + + found = get_reg_and_mask(ab8500_regulator[id].vsel_regid[i], + ab8500_regulator[id].vsel_mask[i], + &val); + if (!found) + break; + + volt = get_voltage(ab8500_regulator[id].vsel_range[i], + ab8500_regulator[id].vsel_range_len[i], + val); + s->volt[i] = volt; + } + return 0; +} + +static int ab8500_regulator_status_print(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + int id, regid; + int i; + u8 val; + int err; + + /* record current state */ + ab8500_regulator_record_state(AB8500_REGULATOR_STATE_CURRENT); + + /* check if chosen state is recorded */ + if (!ab8500_register_state_saved[status_state]) { + seq_printf(s, "ab8500-regulator status is not recorded.\n"); + goto exit; + } + + /* print dump header */ + err = seq_printf(s, "ab8500-regulator status:\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + + /* print state */ + for (i = 0; i < NUM_REGULATOR_STATE; i++) { + if (i == status_state) + err = seq_printf(s, "-> %i. %12s\n", + i, regulator_state_name[i]); + else + err = seq_printf(s, " %i. %12s\n", + i, regulator_state_name[i]); + if (err < 0) + dev_err(dev, "seq_printf overflow\n"); + } + + /* print labels */ + err = seq_printf(s, + "+-----------+----+--------------+-------------------------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "| name|man |auto |voltage |\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "+-----------+----+--------------+ +-----------------------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "| |mode|mode |0|1|2|3| | 1 | 2 | 3 |\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "+-----------+----+------+-+-+-+-+-+-------+-------+-------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + /* dump registers */ + for (id = 0; id < AB8500_NUM_REGULATORS; id++) { + if (ab8500_register[id].unavailable || + ab8500_regulator[id].unavailable) + continue; + + /* print name */ + err = seq_printf(s, "|%11s|", + ab8500_regulator[id].name); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + id, __LINE__); + + /* print manual mode */ + regid = ab8500_regulator[id].update_regid; + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].update_mask; + for (i = 0; i < 4; i++) { + if (val == ab8500_regulator[id].update_val[i]) + break; + } + err = seq_printf(s, "%4s|", + update_val_name[i]); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + id, __LINE__); + + /* print auto mode */ + regid = ab8500_regulator[id].hw_mode_regid; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].hw_mode_mask; + for (i = 0; i < 4; i++) { + if (val == ab8500_regulator[id].hw_mode_val[i]) + break; + } + err = seq_printf(s, "%6s|", + hw_mode_val_name[i]); + } else { + err = seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + id, __LINE__); + + /* print valid bits */ + for (i = 0; i < 4; i++) { + regid = ab8500_regulator[id].hw_valid_regid[i]; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].hw_valid_mask[i]; + if (val) + err = seq_printf(s, "1|"); + else + err = seq_printf(s, "0|"); + } else { + err = seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + } + + /* print voltage selection */ + regid = ab8500_regulator[id].vsel_sel_regid; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].vsel_sel_mask; + for (i = 0; i < 3; i++) { + if (val == ab8500_regulator[id].vsel_sel_val[i]) + break; + } + if (i < 3) + seq_printf(s, "%i|", i + 1); + else + seq_printf(s, "-|"); + } else { + seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + + for (i = 0; i < 3; i++) { + int volt; + + regid = ab8500_regulator[id].vsel_regid[i]; + if (regid) { + val = ab8500_register_state[status_state][regid] + & ab8500_regulator[id].vsel_mask[i]; + volt = get_voltage( + ab8500_regulator[id].vsel_range[i], + ab8500_regulator[id].vsel_range_len[i], + val); + seq_printf(s, "%7i|", volt); + } else { + seq_printf(s, " |"); + } + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + } + + err = seq_printf(s, "\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i, %i\n", + regid, __LINE__); + + } + err = seq_printf(s, + "+-----------+----+------+-+-+-+-+-+-------+-------+-------+\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + err = seq_printf(s, + "Note! In HW mode, voltage selection is controlled by HW.\n"); + if (err < 0) + dev_err(dev, "seq_printf overflow: %i\n", __LINE__); + + +exit: + return 0; +} + +static int ab8500_regulator_status_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + + /* copy user data */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + /* convert */ + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + + /* set suspend force setting */ + if (user_val > NUM_REGULATOR_STATE) { + dev_err(dev, "debugfs error input > number of states\n"); + return -EINVAL; + } + + status_state = user_val; + + return buf_size; +} + + +static int ab8500_regulator_status_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_regulator_status_print, + inode->i_private); +} + +static const struct file_operations ab8500_regulator_status_fops = { + .open = ab8500_regulator_status_open, + .write = ab8500_regulator_status_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +#ifdef CONFIG_PM + +struct ab8500_force_reg { + char *name; + u8 bank; + u8 addr; + u8 mask; + u8 val; + bool restore; + u8 restore_val; + u8 unavailable; +}; + +static struct ab8500_force_reg ab8500_force_reg[] = { + { + /* + * SysClkCtrl + * OTP: 0x00, HSI: 0x06, suspend: 0x00/0x07 (value/mask) + * [ 2] USBClkEna = disable SysClk path to USB block + * [ 1] TVoutClkEna = disable 27Mhz clock to TVout block + * [ 0] TVoutPllEna = disable TVout pll + * (generate 27Mhz from SysClk) + */ + .name = "SysClkCtrl", + .bank = 0x02, + .addr = 0x0c, + .mask = 0x07, + .val = 0x00, + }, + { + /* + * VsimSysClkCtrl + * OTP: 0x01, HSI: 0x21, suspend: 0x01/0xff (value/mask) + * [ 7] VsimSysClkReq8Valid = no connection + * [ 6] VsimSysClkReq7Valid = no connection + * [ 5] VsimSysClkReq6Valid = no connection + * [ 4] VsimSysClkReq5Valid = no connection + * [ 3] VsimSysClkReq4Valid = no connection + * [ 2] VsimSysClkReq3Valid = no connection + * [ 1] VsimSysClkReq2Valid = no connection + * [ 0] VsimSysClkReq1Valid = Vsim set by SysClkReq1 + */ + .name = "VsimSysClkCtrl", + .bank = 0x02, + .addr = 0x33, + .mask = 0xff, + .val = 0x01, + }, + { + /* + * SysUlpClkCtrl1 + * OTP: 0x00, HSI: 0x00, suspend: 0x00/0x0f (value/mask) + * [ 3] 4500SysClkReq = inactive + * [ 2] UlpClkReq = inactive + * [1:0] SysUlpClkIntSel[1:0] = no internal clock switching. + * Internal clock is SysClk. + */ + .name = "SysUlpClkCtrl1", + .bank = 0x02, + .addr = 0x0b, + .mask = 0x0f, + .val = 0x00, + }, + { + /* + * TVoutCtrl + * OTP: N/A, HSI: N/A, suspend: 0x00/0x03 (value/mask) + * [ 2] PlugTvOn = plug/unplug detection disabled + * [1:0] TvoutDacCtrl[1:0] = "0" forced on DAC input (test) + */ + .name = "TVoutCtrl", + .bank = 0x06, + .addr = 0x80, + .mask = 0x03, + .val = 0x00, + }, +}; + +static void ab9540_force_reg_update(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ab8500_force_reg); i++) { + if (ab8500_force_reg[i].bank == 0x02 && + ab8500_force_reg[i].addr == 0x0C) { + /* + * SysClkCtrl + * OTP: 0x00, HSI: 0x06, suspend: 0x00/0x07 (value/mask) + * [ 2] USBClkEna = disable SysClk path to USB block + */ + ab8500_force_reg[i].mask = 0x04; + ab8500_force_reg[i].val = 0x00; + } else if (ab8500_force_reg[i].bank == 0x06 && + ab8500_force_reg[i].addr == 0x80) { + /* TVoutCtrl not supported by AB9540 */ + ab8500_force_reg[i].unavailable = true; + } + } +} + +void ab8500_regulator_debug_force(void) +{ + int ret, i; + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_SUSPEND); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record suspend state.\n"); + + /* check if registers should be forced */ + if (!setting_suspend_force) + goto exit; + + /* + * Optimize href v2_v50_pwr board for ApSleep/ApDeepSleep + * power consumption measurements + */ + + for (i = 0; i < ARRAY_SIZE(ab8500_force_reg); i++) { + if (ab8500_force_reg[i].unavailable) + continue; + + dev_vdbg(&pdev->dev, "Save and set %s: " + "0x%02x, 0x%02x, 0x%02x, 0x%02x.\n", + ab8500_force_reg[i].name, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].val); + + /* assume that register should be restored */ + ab8500_force_reg[i].restore = true; + + /* get register value before forcing it */ + ret = abx500_get_register_interruptible(&pdev->dev, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + &ab8500_force_reg[i].restore_val); + if (ret < 0) { + dev_err(dev, "Failed to read %s.\n", + ab8500_force_reg[i].name); + ab8500_force_reg[i].restore = false; + break; + } + + /* force register value */ + ret = abx500_mask_and_set_register_interruptible(&pdev->dev, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].val); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to write %s.\n", + ab8500_force_reg[i].name); + ab8500_force_reg[i].restore = false; + } + } + +exit: + /* save state of registers */ + ret = ab8500_regulator_record_state( + AB8500_REGULATOR_STATE_SUSPEND_CORE); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record suspend state.\n"); + + return; +} + +void ab8500_regulator_debug_restore(void) +{ + int ret, i; + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_RESUME_CORE); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record resume state.\n"); + for (i = ARRAY_SIZE(ab8500_force_reg) - 1; i >= 0; i--) { + if (ab8500_force_reg[i].unavailable) + continue; + + /* restore register value */ + if (ab8500_force_reg[i].restore) { + ret = abx500_mask_and_set_register_interruptible( + &pdev->dev, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].restore_val); + if (ret < 0) + dev_err(&pdev->dev, "Failed to restore %s.\n", + ab8500_force_reg[i].name); + dev_vdbg(&pdev->dev, "Restore %s: " + "0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + ab8500_force_reg[i].name, + ab8500_force_reg[i].bank, + ab8500_force_reg[i].addr, + ab8500_force_reg[i].mask, + ab8500_force_reg[i].restore_val); + } + } + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_RESUME); + if (ret < 0) + dev_err(&pdev->dev, "Failed to record resume state.\n"); + + return; +} + +#endif + +static int ab8500_regulator_suspend_force_show(struct seq_file *s, void *p) +{ + /* print suspend standby status */ + if (setting_suspend_force) + return seq_printf(s, "suspend force enabled\n"); + else + return seq_printf(s, "no suspend force\n"); +} + +static int ab8500_regulator_suspend_force_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + + /* copy user data */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + /* convert */ + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + + /* set suspend force setting */ + if (user_val > 1) { + dev_err(dev, "debugfs error input > 1\n"); + return -EINVAL; + } + + if (user_val) + setting_suspend_force = true; + else + setting_suspend_force = false; + + return buf_size; +} + +static int ab8500_regulator_suspend_force_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_regulator_suspend_force_show, + inode->i_private); +} + +static const struct file_operations ab8500_regulator_suspend_force_fops = { + .open = ab8500_regulator_suspend_force_open, + .write = ab8500_regulator_suspend_force_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct dentry *ab8500_regulator_dir; +static struct dentry *ab8500_regulator_dump_file; +static struct dentry *ab8500_regulator_status_file; +static struct dentry *ab8500_regulator_suspend_force_file; + +int __devinit ab8500_regulator_debug_init(struct platform_device *plf) +{ + void __iomem *boot_info_backupram; + int ret; + struct ab8500 *ab8500; + + /* setup dev pointers */ + dev = &plf->dev; + pdev = plf; + + /* save state of registers */ + ret = ab8500_regulator_record_state(AB8500_REGULATOR_STATE_INIT); + if (ret < 0) + dev_err(&plf->dev, "Failed to record init state.\n"); + + ab8500 = dev_get_drvdata(plf->dev.parent); + /* Update data structures for AB9540 */ + if (is_ab9540(ab8500)) { + ab9540_registers_update(); + ab9540_regulators_update(); + ab9540_force_reg_update(); + } + /* make suspend-force default if board profile is v5x-power */ + boot_info_backupram = ioremap(BOOT_INFO_BACKUPRAM1, 0x4); + + if (boot_info_backupram) { + u8 board_profile; + board_profile = readb( + boot_info_backupram + BOARD_PROFILE_BACKUPRAM1); + dev_dbg(dev, "Board profile is 0x%02x\n", board_profile); + + if (board_profile >= OPTION_BOARD_VERSION_V5X) + setting_suspend_force = true; + + iounmap(boot_info_backupram); + } else { + dev_err(dev, "Failed to read backupram.\n"); + } + + /* create directory */ + ab8500_regulator_dir = debugfs_create_dir("ab8500-regulator", NULL); + if (!ab8500_regulator_dir) + goto exit_no_debugfs; + + /* create "dump" file */ + ab8500_regulator_dump_file = debugfs_create_file("dump", + S_IRUGO, ab8500_regulator_dir, &plf->dev, + &ab8500_regulator_dump_fops); + if (!ab8500_regulator_dump_file) + goto exit_destroy_dir; + + /* create "status" file */ + ab8500_regulator_status_file = debugfs_create_file("status", + S_IRUGO, ab8500_regulator_dir, &plf->dev, + &ab8500_regulator_status_fops); + if (!ab8500_regulator_status_file) + goto exit_destroy_dump_file; + + /* + * create "suspend-force-v5x" file. As indicated by the name, this is + * only applicable for v2_v5x hardware versions. + */ + ab8500_regulator_suspend_force_file = debugfs_create_file( + "suspend-force-v5x", + S_IRUGO, ab8500_regulator_dir, &plf->dev, + &ab8500_regulator_suspend_force_fops); + if (!ab8500_regulator_suspend_force_file) + goto exit_destroy_status_file; + + return 0; + +exit_destroy_status_file: + debugfs_remove(ab8500_regulator_status_file); +exit_destroy_dump_file: + debugfs_remove(ab8500_regulator_dump_file); +exit_destroy_dir: + debugfs_remove(ab8500_regulator_dir); +exit_no_debugfs: + dev_err(&plf->dev, "failed to create debugfs entries.\n"); + return -ENOMEM; +} + +int __devexit ab8500_regulator_debug_exit(struct platform_device *plf) +{ + debugfs_remove(ab8500_regulator_suspend_force_file); + debugfs_remove(ab8500_regulator_status_file); + debugfs_remove(ab8500_regulator_dump_file); + debugfs_remove(ab8500_regulator_dir); + + return 0; +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bengt Jonsson <bengt.g.jonsson@stericsson.com"); +MODULE_DESCRIPTION("AB8500 Regulator Debug"); +MODULE_ALIAS("platform:ab8500-regulator-debug"); diff --git a/drivers/regulator/ab8500-debug.h b/drivers/regulator/ab8500-debug.h new file mode 100644 index 00000000000..2b59e556a3f --- /dev/null +++ b/drivers/regulator/ab8500-debug.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) ST-Ericsson SA 2010-2011 + * + * Author: Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson. + * + * License Terms: GNU General Public License v2 + */ + +#ifndef __AB8500_DEBUG_H__ +#define __AB8500_DEBUG_H__ + +/* + * regulator status print + */ +enum ab8500_regulator_id { + AB8500_VARM, + AB8500_VBBP, + AB8500_VBBN, + AB8500_VAPE, + AB8500_VSMPS1, + AB8500_VSMPS2, + AB8500_VSMPS3, + AB8500_VPLL, + AB8500_VREFDDR, + AB8500_VMOD, + AB8500_VEXTSUPPLY1, + AB8500_VEXTSUPPLY2, + AB8500_VEXTSUPPLY3, + AB8500_VRF1, + AB8500_VANA, + AB8500_VAUX1, + AB8500_VAUX2, + AB8500_VAUX3, + AB9540_VAUX4, /* Note: AB9540 only */ + AB8500_VINTCORE, + AB8500_VTVOUT, + AB8500_VAUDIO, + AB8500_VANAMIC1, + AB8500_VANAMIC2, + AB8500_VDMIC, + AB8500_VUSB, + AB8500_VOTG, + AB8500_VBUSBIS, + AB8500_NUM_REGULATORS, +}; + +enum ab8500_regulator_mode { + AB8500_MODE_OFF = 0, + AB8500_MODE_ON, + AB8500_MODE_HW, + AB8500_MODE_LP +}; + +enum ab8500_regulator_hwmode { + AB8500_HWMODE_NONE = 0, + AB8500_HWMODE_HPLP, + AB8500_HWMODE_HPOFF, + AB8500_HWMODE_HP, + AB8500_HWMODE_HP2, +}; + +enum hwmode_auto { + HWM_OFF = 0, + HWM_ON = 1, + HWM_INVAL = 2, +}; + +struct ab8500_debug_regulator_status { + char *name; + enum ab8500_regulator_mode mode; + enum ab8500_regulator_hwmode hwmode; + enum hwmode_auto hwmode_auto[4]; + int volt_selected; + int volt_len; + int volt[4]; +}; + +int ab8500_regulator_debug_read(enum ab8500_regulator_id id, + struct ab8500_debug_regulator_status *s); +#endif /* __AB8500_DEBUG_H__ */ diff --git a/drivers/regulator/ab8500-ext.c b/drivers/regulator/ab8500-ext.c new file mode 100644 index 00000000000..8a5064c07fb --- /dev/null +++ b/drivers/regulator/ab8500-ext.c @@ -0,0 +1,451 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * + * Authors: Bengt Jonsson <bengt.g.jonsson@stericsson.com> + * + * This file is based on drivers/regulator/ab8500.c + * + * AB8500 external regulators + * + * ab8500-ext supports the following regulators: + * - VextSupply3 + */ +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/regulator/ab8500.h> + +/** + * struct ab8500_ext_regulator_info - ab8500 regulator information + * @dev: device pointer + * @desc: regulator description + * @rdev: regulator device + * @cfg: regulator configuration (extension of regulator FW configuration) + * @is_enabled: status of regulator (on/off) + * @fixed_uV: typical voltage (for fixed voltage supplies) + * @update_bank: bank to control on/off + * @update_reg: register to control on/off + * @update_mask: mask to enable/disable and set mode of regulator + * @update_val: bits holding the regulator current mode + * @update_val_hp: bits to set EN pin active (LPn pin deactive) + * normally this means high power mode + * @update_val_lp: bits to set EN pin active and LPn pin active + * normally this means low power mode + * @update_val_hw: bits to set regulator pins in HW control + * SysClkReq pins and logic will choose mode + */ +struct ab8500_ext_regulator_info { + struct device *dev; + struct regulator_desc desc; + struct regulator_dev *rdev; + struct ab8500_ext_regulator_cfg *cfg; + bool is_enabled; + int fixed_uV; + u8 update_bank; + u8 update_reg; + u8 update_mask; + u8 update_val; + u8 update_val_hp; + u8 update_val_lp; + u8 update_val_hw; +}; + +static int enable(struct ab8500_ext_regulator_info *info, u8 *regval) +{ + int ret; + + *regval = info->update_val; + + /* + * To satisfy both HW high power request and SW request, the regulator + * must be on in high power. + */ + if (info->cfg && info->cfg->hwreq) + *regval = info->update_val_hp; + + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->update_bank, info->update_reg, + info->update_mask, *regval); + if (ret < 0) + dev_err(rdev_get_dev(info->rdev), + "couldn't set enable bits for regulator\n"); + + info->is_enabled = true; + + return ret; +} + +static int ab8500_ext_regulator_enable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + u8 regval; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = enable(info, ®val); + + dev_dbg(rdev_get_dev(rdev), "%s-enable (bank, reg, mask, value):" + " 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, regval); + + return ret; +} + +static int ab8500_ext_regulator_set_suspend_enable(struct regulator_dev *rdev) +{ + dev_dbg(rdev_get_dev(rdev), "suspend: "); + + return ab8500_ext_regulator_enable(rdev); +} + +static int disable(struct ab8500_ext_regulator_info *info, u8 *regval) +{ + int ret; + + *regval = 0x0; + + /* + * Set the regulator in HW request mode if configured + */ + if (info->cfg && info->cfg->hwreq) + *regval = info->update_val_hw; + + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->update_bank, info->update_reg, + info->update_mask, *regval); + if (ret < 0) + dev_err(rdev_get_dev(info->rdev), + "couldn't set disable bits for regulator\n"); + + info->is_enabled = false; + + return ret; +} + +static int ab8500_ext_regulator_disable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + u8 regval; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = disable(info, ®val); + + dev_dbg(rdev_get_dev(rdev), "%s-disable (bank, reg, mask, value):" + " 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, regval); + + return ret; +} + +static int ab8500_ext_regulator_set_suspend_disable(struct regulator_dev *rdev) +{ + dev_dbg(rdev_get_dev(rdev), "suspend: "); + + return ab8500_ext_regulator_disable(rdev); +} + +static int ab8500_ext_regulator_is_enabled(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + u8 regval; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = abx500_get_register_interruptible(info->dev, + info->update_bank, info->update_reg, ®val); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't read 0x%x register\n", info->update_reg); + return ret; + } + + dev_dbg(rdev_get_dev(rdev), "%s-is_enabled (bank, reg, mask, value):" + " 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, regval); + + if (((regval & info->update_mask) == info->update_val_lp) || + ((regval & info->update_mask) == info->update_val_hp)) + info->is_enabled = true; + else + info->is_enabled = false; + + return info->is_enabled; +} + +static int ab8500_ext_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + int ret = 0; + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + switch (mode) { + case REGULATOR_MODE_NORMAL: + info->update_val = info->update_val_hp; + break; + case REGULATOR_MODE_IDLE: + info->update_val = info->update_val_lp; + break; + + default: + return -EINVAL; + } + + if (info->is_enabled) { + u8 regval; + + ret = enable(info, ®val); + if (ret < 0) + dev_err(rdev_get_dev(rdev), + "Could not set regulator mode.\n"); + + dev_dbg(rdev_get_dev(rdev), + "%s-set_mode (bank, reg, mask, value): " + "0x%x, 0x%x, 0x%x, 0x%x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, regval); + } + + return ret; +} + +static unsigned int ab8500_ext_regulator_get_mode(struct regulator_dev *rdev) +{ + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + if (info->update_val == info->update_val_hp) + ret = REGULATOR_MODE_NORMAL; + else if (info->update_val == info->update_val_lp) + ret = REGULATOR_MODE_IDLE; + else + ret = -EINVAL; + + return ret; +} + +static int ab8500_ext_fixed_get_voltage(struct regulator_dev *rdev) +{ + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + return info->fixed_uV; +} + +static int ab8500_ext_list_voltage(struct regulator_dev *rdev, + unsigned selector) +{ + struct ab8500_ext_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + /* return the uV for the fixed regulators */ + if (info->fixed_uV) + return info->fixed_uV; + + return -EINVAL; +} + +static struct regulator_ops ab8500_ext_regulator_ops = { + .enable = ab8500_ext_regulator_enable, + .set_suspend_enable = ab8500_ext_regulator_set_suspend_enable, + .disable = ab8500_ext_regulator_disable, + .set_suspend_disable = ab8500_ext_regulator_set_suspend_disable, + .is_enabled = ab8500_ext_regulator_is_enabled, + .set_mode = ab8500_ext_regulator_set_mode, + .get_mode = ab8500_ext_regulator_get_mode, + .get_voltage = ab8500_ext_fixed_get_voltage, + .list_voltage = ab8500_ext_list_voltage, +}; + +static struct ab8500_ext_regulator_info + ab8500_ext_regulator_info[AB8500_NUM_EXT_REGULATORS] = { + [AB8500_EXT_SUPPLY1] = { + .desc = { + .name = "VEXTSUPPLY1", + .ops = &ab8500_ext_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_EXT_SUPPLY1, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 1800000, + .update_bank = 0x04, + .update_reg = 0x08, + .update_mask = 0x03, + .update_val = 0x01, + .update_val_hp = 0x01, + .update_val_lp = 0x03, + .update_val_hw = 0x02, + }, + [AB8500_EXT_SUPPLY2] = { + .desc = { + .name = "VEXTSUPPLY2", + .ops = &ab8500_ext_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_EXT_SUPPLY2, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 1360000, + .update_bank = 0x04, + .update_reg = 0x08, + .update_mask = 0x0c, + .update_val = 0x04, + .update_val_hp = 0x04, + .update_val_lp = 0x0c, + .update_val_hw = 0x08, + }, + [AB8500_EXT_SUPPLY3] = { + .desc = { + .name = "VEXTSUPPLY3", + .ops = &ab8500_ext_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_EXT_SUPPLY3, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 3400000, + .update_bank = 0x04, + .update_reg = 0x08, + .update_mask = 0x30, + .update_val = 0x10, + .update_val_hp = 0x10, + .update_val_lp = 0x30, + .update_val_hw = 0x20, + }, +}; + +__devinit int ab8500_ext_regulator_init(struct platform_device *pdev) +{ + struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); + struct ab8500_platform_data *ppdata; + struct ab8500_regulator_platform_data *pdata; + int i, err; + + if (!ab8500) { + dev_err(&pdev->dev, "null mfd parent\n"); + return -EINVAL; + } + ppdata = dev_get_platdata(ab8500->dev); + if (!ppdata) { + dev_err(&pdev->dev, "null parent pdata\n"); + return -EINVAL; + } + + pdata = ppdata->regulator; + if (!pdata) { + dev_err(&pdev->dev, "null pdata\n"); + return -EINVAL; + } + + /* make sure the platform data has the correct size */ + if (pdata->num_ext_regulator != ARRAY_SIZE(ab8500_ext_regulator_info)) { + dev_err(&pdev->dev, "Configuration error: size mismatch.\n"); + return -EINVAL; + } + + /* check for AB8500 2.x */ + if (is_ab8500_2p0_or_earlier(ab8500)) { + struct ab8500_ext_regulator_info *info; + + /* VextSupply3LPn is inverted on AB8500 2.x */ + info = &ab8500_ext_regulator_info[AB8500_EXT_SUPPLY3]; + info->update_val = 0x30; + info->update_val_hp = 0x30; + info->update_val_lp = 0x10; + } + + /* register all regulators */ + for (i = 0; i < ARRAY_SIZE(ab8500_ext_regulator_info); i++) { + struct ab8500_ext_regulator_info *info = NULL; + + /* assign per-regulator data */ + info = &ab8500_ext_regulator_info[i]; + info->dev = &pdev->dev; + info->cfg = (struct ab8500_ext_regulator_cfg *) + pdata->ext_regulator[i].driver_data; + + /* register regulator with framework */ + info->rdev = regulator_register(&info->desc, &pdev->dev, + &pdata->ext_regulator[i], info, NULL); + if (IS_ERR(info->rdev)) { + err = PTR_ERR(info->rdev); + dev_err(&pdev->dev, "failed to register regulator %s\n", + info->desc.name); + /* when we fail, un-register all earlier regulators */ + while (--i >= 0) { + info = &ab8500_ext_regulator_info[i]; + regulator_unregister(info->rdev); + } + return err; + } + + dev_dbg(rdev_get_dev(info->rdev), + "%s-probed\n", info->desc.name); + } + + return 0; +} + +__devexit int ab8500_ext_regulator_exit(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ab8500_ext_regulator_info); i++) { + struct ab8500_ext_regulator_info *info = NULL; + info = &ab8500_ext_regulator_info[i]; + + dev_vdbg(rdev_get_dev(info->rdev), + "%s-remove\n", info->desc.name); + + regulator_unregister(info->rdev); + } + + return 0; +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Bengt Jonsson <bengt.g.jonsson@stericsson.com>"); +MODULE_DESCRIPTION("AB8500 external regulator driver"); +MODULE_ALIAS("platform:ab8500-ext-regulator"); diff --git a/drivers/regulator/ab8500.c b/drivers/regulator/ab8500.c index c7ee4c15d6f..71328249659 100644 --- a/drivers/regulator/ab8500.c +++ b/drivers/regulator/ab8500.c @@ -21,43 +21,55 @@ #include <linux/regulator/driver.h> #include <linux/regulator/machine.h> #include <linux/regulator/ab8500.h> +#include <linux/mfd/abx500/ab8500-gpio.h> /* for sysclkreq pins */ +#include <mach/id.h> /** * struct ab8500_regulator_info - ab8500 regulator information * @dev: device pointer * @desc: regulator description * @regulator_dev: regulator device + * @is_enabled: status of regulator (on/off) * @max_uV: maximum voltage (for variable voltage supplies) * @min_uV: minimum voltage (for variable voltage supplies) * @fixed_uV: typical voltage (for fixed voltage supplies) + * @load_lp_uA: maximum load in idle (low power) mode * @update_bank: bank to control on/off * @update_reg: register to control on/off - * @update_mask: mask to enable/disable regulator - * @update_val_enable: bits to enable the regulator in normal (high power) mode + * @update_mask: mask to enable/disable and set mode of regulator + * @update_val: bits holding the regulator current mode + * @update_val_idle: bits to enable the regulator in idle (low power) mode + * @update_val_normal: bits to enable the regulator in normal (high power) mode * @voltage_bank: bank to control regulator voltage * @voltage_reg: register to control regulator voltage * @voltage_mask: mask to control regulator voltage * @voltages: supported voltage table * @voltages_len: number of supported voltages for the regulator * @delay: startup/set voltage delay in us + * @gpio_pin: ab8500 gpio pin offset number (for sysclkreq regulator only) */ struct ab8500_regulator_info { struct device *dev; struct regulator_desc desc; struct regulator_dev *regulator; + bool is_enabled; int max_uV; int min_uV; int fixed_uV; + int load_lp_uA; u8 update_bank; u8 update_reg; u8 update_mask; - u8 update_val_enable; + u8 update_val; + u8 update_val_idle; + u8 update_val_normal; u8 voltage_bank; u8 voltage_reg; u8 voltage_mask; int const *voltages; int voltages_len; unsigned int delay; + enum ab8500_pin gpio_pin; }; /* voltage tables for the vauxn/vintcore supplies */ @@ -113,15 +125,17 @@ static int ab8500_regulator_enable(struct regulator_dev *rdev) ret = abx500_mask_and_set_register_interruptible(info->dev, info->update_bank, info->update_reg, - info->update_mask, info->update_val_enable); + info->update_mask, info->update_val); if (ret < 0) dev_err(rdev_get_dev(rdev), "couldn't set enable bits for regulator\n"); + info->is_enabled = true; + dev_vdbg(rdev_get_dev(rdev), "%s-enable (bank, reg, mask, value): 0x%x, 0x%x, 0x%x, 0x%x\n", info->desc.name, info->update_bank, info->update_reg, - info->update_mask, info->update_val_enable); + info->update_mask, info->update_val); return ret; } @@ -143,6 +157,8 @@ static int ab8500_regulator_disable(struct regulator_dev *rdev) dev_err(rdev_get_dev(rdev), "couldn't set disable bits for regulator\n"); + info->is_enabled = false; + dev_vdbg(rdev_get_dev(rdev), "%s-disable (bank, reg, mask, value): 0x%x, 0x%x, 0x%x, 0x%x\n", info->desc.name, info->update_bank, info->update_reg, @@ -151,6 +167,88 @@ static int ab8500_regulator_disable(struct regulator_dev *rdev) return ret; } +static unsigned int ab8500_regulator_get_optimum_mode( + struct regulator_dev *rdev, int input_uV, + int output_uV, int load_uA) +{ + unsigned int mode; + + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + if (load_uA <= info->load_lp_uA) + mode = REGULATOR_MODE_IDLE; + else + mode = REGULATOR_MODE_NORMAL; + + return mode; +} + +static int ab8500_regulator_set_mode(struct regulator_dev *rdev, + unsigned int mode) +{ + int ret = 0; + + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + switch (mode) { + case REGULATOR_MODE_NORMAL: + info->update_val = info->update_val_normal; + break; + case REGULATOR_MODE_IDLE: + info->update_val = info->update_val_idle; + break; + default: + return -EINVAL; + } + + if (info->is_enabled) { + ret = abx500_mask_and_set_register_interruptible(info->dev, + info->update_bank, info->update_reg, + info->update_mask, info->update_val); + if (ret < 0) + dev_err(rdev_get_dev(rdev), + "couldn't set regulator mode\n"); + + dev_vdbg(rdev_get_dev(rdev), + "%s-set_mode (bank, reg, mask, value): " + "0x%x, 0x%x, 0x%x, 0x%x\n", + info->desc.name, info->update_bank, info->update_reg, + info->update_mask, info->update_val); + } + + return ret; +} + +static unsigned int ab8500_regulator_get_mode(struct regulator_dev *rdev) +{ + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + if (info->update_val == info->update_val_normal) + ret = REGULATOR_MODE_NORMAL; + else if (info->update_val == info->update_val_idle) + ret = REGULATOR_MODE_IDLE; + else + ret = -EINVAL; + + return ret; +} + static int ab8500_regulator_is_enabled(struct regulator_dev *rdev) { int ret; @@ -177,9 +275,11 @@ static int ab8500_regulator_is_enabled(struct regulator_dev *rdev) info->update_mask, regval); if (regval & info->update_mask) - return true; + info->is_enabled = true; else - return false; + info->is_enabled = false; + + return info->is_enabled; } static int ab8500_list_voltage(struct regulator_dev *rdev, unsigned selector) @@ -273,8 +373,13 @@ static int ab8500_regulator_set_voltage(struct regulator_dev *rdev, *selector = ret; + /* vintcore register has a different layout */ + if (info->desc.id == AB8500_LDO_INTCORE) + regval = ((u8)ret) << 3; + else + regval = (u8)ret; + /* set the registers for the request */ - regval = (u8)ret; ret = abx500_mask_and_set_register_interruptible(info->dev, info->voltage_bank, info->voltage_reg, info->voltage_mask, regval); @@ -314,9 +419,12 @@ static int ab8500_regulator_set_voltage_time_sel(struct regulator_dev *rdev, return info->delay; } -static struct regulator_ops ab8500_regulator_ops = { +static struct regulator_ops ab8500_regulator_volt_mode_ops = { .enable = ab8500_regulator_enable, .disable = ab8500_regulator_disable, + .get_optimum_mode = ab8500_regulator_get_optimum_mode, + .set_mode = ab8500_regulator_set_mode, + .get_mode = ab8500_regulator_get_mode, .is_enabled = ab8500_regulator_is_enabled, .get_voltage_sel = ab8500_regulator_get_voltage_sel, .set_voltage = ab8500_regulator_set_voltage, @@ -337,16 +445,116 @@ static int ab8500_fixed_get_voltage(struct regulator_dev *rdev) return info->fixed_uV; } -static struct regulator_ops ab8500_regulator_fixed_ops = { +static struct regulator_ops ab8500_regulator_mode_ops = { .enable = ab8500_regulator_enable, .disable = ab8500_regulator_disable, .is_enabled = ab8500_regulator_is_enabled, + .get_optimum_mode = ab8500_regulator_get_optimum_mode, + .set_mode = ab8500_regulator_set_mode, + .get_mode = ab8500_regulator_get_mode, .get_voltage = ab8500_fixed_get_voltage, .list_voltage = ab8500_list_voltage, .enable_time = ab8500_regulator_enable_time, .set_voltage_time_sel = ab8500_regulator_set_voltage_time_sel, }; +static struct regulator_ops ab8500_regulator_ops = { + .enable = ab8500_regulator_enable, + .disable = ab8500_regulator_disable, + .is_enabled = ab8500_regulator_is_enabled, + .get_voltage = ab8500_fixed_get_voltage, + .list_voltage = ab8500_list_voltage, +}; + +static int ab8500_sysclkreq_enable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = ab8500_gpio_config_select(info->dev, info->gpio_pin, false); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't set sysclkreq pin selection\n"); + return ret; + } + + info->is_enabled = true; + + dev_vdbg(rdev_get_dev(rdev), + "%s-enable (gpio_pin, gpio_select): %i, false\n", + info->desc.name, info->gpio_pin); + + return ret; +} + +static int ab8500_sysclkreq_disable(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = ab8500_gpio_config_select(info->dev, info->gpio_pin, true); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't set gpio pin selection\n"); + return ret; + } + + info->is_enabled = false; + + dev_vdbg(rdev_get_dev(rdev), + "%s-disable (gpio_pin, gpio_select): %i, true\n", + info->desc.name, info->gpio_pin); + + return ret; +} + +static int ab8500_sysclkreq_is_enabled(struct regulator_dev *rdev) +{ + int ret; + struct ab8500_regulator_info *info = rdev_get_drvdata(rdev); + bool gpio_select; + + if (info == NULL) { + dev_err(rdev_get_dev(rdev), "regulator info null pointer\n"); + return -EINVAL; + } + + ret = ab8500_gpio_config_get_select(info->dev, info->gpio_pin, + &gpio_select); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "couldn't read gpio pin selection\n"); + return ret; + } + + info->is_enabled = !gpio_select; + + dev_vdbg(rdev_get_dev(rdev), + "%s-is_enabled (gpio_pin, is_enabled): %i, %i\n", + info->desc.name, info->gpio_pin, !gpio_select); + + return info->is_enabled; +} + +static struct regulator_ops ab8500_sysclkreq_ops = { + .enable = ab8500_sysclkreq_enable, + .disable = ab8500_sysclkreq_disable, + .is_enabled = ab8500_sysclkreq_is_enabled, + .get_voltage = ab8500_fixed_get_voltage, + .list_voltage = ab8500_list_voltage, +}; + +/* AB8500 regulator information */ static struct ab8500_regulator_info ab8500_regulator_info[AB8500_NUM_REGULATORS] = { /* @@ -358,7 +566,7 @@ static struct ab8500_regulator_info [AB8500_LDO_AUX1] = { .desc = { .name = "LDO-AUX1", - .ops = &ab8500_regulator_ops, + .ops = &ab8500_regulator_volt_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_AUX1, .owner = THIS_MODULE, @@ -366,10 +574,13 @@ static struct ab8500_regulator_info }, .min_uV = 1100000, .max_uV = 3300000, + .load_lp_uA = 5000, .update_bank = 0x04, .update_reg = 0x09, .update_mask = 0x03, - .update_val_enable = 0x01, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, .voltage_bank = 0x04, .voltage_reg = 0x1f, .voltage_mask = 0x0f, @@ -379,7 +590,7 @@ static struct ab8500_regulator_info [AB8500_LDO_AUX2] = { .desc = { .name = "LDO-AUX2", - .ops = &ab8500_regulator_ops, + .ops = &ab8500_regulator_volt_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_AUX2, .owner = THIS_MODULE, @@ -387,10 +598,13 @@ static struct ab8500_regulator_info }, .min_uV = 1100000, .max_uV = 3300000, + .load_lp_uA = 5000, .update_bank = 0x04, .update_reg = 0x09, .update_mask = 0x0c, - .update_val_enable = 0x04, + .update_val = 0x04, + .update_val_idle = 0x0c, + .update_val_normal = 0x04, .voltage_bank = 0x04, .voltage_reg = 0x20, .voltage_mask = 0x0f, @@ -400,7 +614,7 @@ static struct ab8500_regulator_info [AB8500_LDO_AUX3] = { .desc = { .name = "LDO-AUX3", - .ops = &ab8500_regulator_ops, + .ops = &ab8500_regulator_volt_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_AUX3, .owner = THIS_MODULE, @@ -408,10 +622,13 @@ static struct ab8500_regulator_info }, .min_uV = 1100000, .max_uV = 3300000, + .load_lp_uA = 5000, .update_bank = 0x04, .update_reg = 0x0a, .update_mask = 0x03, - .update_val_enable = 0x01, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, .voltage_bank = 0x04, .voltage_reg = 0x21, .voltage_mask = 0x07, @@ -421,7 +638,7 @@ static struct ab8500_regulator_info [AB8500_LDO_INTCORE] = { .desc = { .name = "LDO-INTCORE", - .ops = &ab8500_regulator_ops, + .ops = &ab8500_regulator_volt_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_INTCORE, .owner = THIS_MODULE, @@ -429,10 +646,13 @@ static struct ab8500_regulator_info }, .min_uV = 1100000, .max_uV = 3300000, + .load_lp_uA = 5000, .update_bank = 0x03, .update_reg = 0x80, .update_mask = 0x44, - .update_val_enable = 0x04, + .update_val = 0x44, + .update_val_idle = 0x44, + .update_val_normal = 0x04, .voltage_bank = 0x03, .voltage_reg = 0x80, .voltage_mask = 0x38, @@ -448,7 +668,275 @@ static struct ab8500_regulator_info [AB8500_LDO_TVOUT] = { .desc = { .name = "LDO-TVOUT", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_TVOUT, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .delay = 500, + .fixed_uV = 2000000, + .load_lp_uA = 1000, + .update_bank = 0x03, + .update_reg = 0x80, + .update_mask = 0x82, + .update_val = 0x02, + .update_val_idle = 0x82, + .update_val_normal = 0x02, + }, + [AB8500_LDO_AUDIO] = { + .desc = { + .name = "LDO-AUDIO", + .ops = &ab8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_AUDIO, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 2000000, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x02, + .update_val = 0x02, + }, + [AB8500_LDO_ANAMIC1] = { + .desc = { + .name = "LDO-ANAMIC1", + .ops = &ab8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_ANAMIC1, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 2050000, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x08, + .update_val = 0x08, + }, + [AB8500_LDO_ANAMIC2] = { + .desc = { + .name = "LDO-ANAMIC2", + .ops = &ab8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_ANAMIC2, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 2050000, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x10, + .update_val = 0x10, + }, + [AB8500_LDO_DMIC] = { + .desc = { + .name = "LDO-DMIC", + .ops = &ab8500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_DMIC, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 1800000, + .update_bank = 0x03, + .update_reg = 0x83, + .update_mask = 0x04, + .update_val = 0x04, + }, + + /* + * Regulators with fixed voltage and normal/idle modes + */ + [AB8500_LDO_ANA] = { + .desc = { + .name = "LDO-ANA", + .ops = &ab8500_regulator_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_ANA, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 1200000, + .load_lp_uA = 1000, + .update_bank = 0x04, + .update_reg = 0x06, + .update_mask = 0x0c, + .update_val = 0x04, + .update_val_idle = 0x0c, + .update_val_normal = 0x04, + }, + + /* + * SysClkReq regulators + */ + [AB8500_SYSCLKREQ_2] = { + .desc = { + .name = "SYSCLKREQ-2", + .ops = &ab8500_sysclkreq_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_SYSCLKREQ_2, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 1, /* bogus value */ + .gpio_pin = AB8500_PIN_GPIO1, + }, + [AB8500_SYSCLKREQ_4] = { + .desc = { + .name = "SYSCLKREQ-4", + .ops = &ab8500_sysclkreq_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_SYSCLKREQ_4, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 1, /* bogus value */ + .gpio_pin = AB8500_PIN_GPIO3, + }, +}; + +/* AB9540 regulator information */ +static struct ab8500_regulator_info + ab9540_regulator_info[AB9540_NUM_REGULATORS] = { + /* + * Variable Voltage Regulators + * name, min mV, max mV, + * update bank, reg, mask, enable val + * volt bank, reg, mask, table, table length + */ + [AB9540_LDO_AUX1] = { + .desc = { + .name = "LDO-AUX1", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_AUX1, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vauxn_voltages), + }, + .min_uV = 1100000, + .max_uV = 3300000, + .load_lp_uA = 5000, + .update_bank = 0x04, + .update_reg = 0x09, + .update_mask = 0x03, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, + .voltage_bank = 0x04, + .voltage_reg = 0x1f, + .voltage_mask = 0x0f, + .voltages = ldo_vauxn_voltages, + .voltages_len = ARRAY_SIZE(ldo_vauxn_voltages), + }, + [AB9540_LDO_AUX2] = { + .desc = { + .name = "LDO-AUX2", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_AUX2, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vauxn_voltages), + }, + .min_uV = 1100000, + .max_uV = 3300000, + .load_lp_uA = 5000, + .update_bank = 0x04, + .update_reg = 0x09, + .update_mask = 0x0c, + .update_val = 0x04, + .update_val_idle = 0x0c, + .update_val_normal = 0x04, + .voltage_bank = 0x04, + .voltage_reg = 0x20, + .voltage_mask = 0x0f, + .voltages = ldo_vauxn_voltages, + .voltages_len = ARRAY_SIZE(ldo_vauxn_voltages), + }, + [AB9540_LDO_AUX3] = { + .desc = { + .name = "LDO-AUX3", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_AUX3, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vaux3_voltages), + }, + .min_uV = 1100000, + .max_uV = 3300000, + .load_lp_uA = 5000, + .update_bank = 0x04, + .update_reg = 0x0a, + .update_mask = 0x03, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, + .voltage_bank = 0x04, + .voltage_reg = 0x21, + .voltage_mask = 0x07, + .voltages = ldo_vaux3_voltages, + .voltages_len = ARRAY_SIZE(ldo_vaux3_voltages), + }, + [AB9540_LDO_AUX4] = { + .desc = { + .name = "LDO-AUX4", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB9540_LDO_AUX4, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vauxn_voltages), + }, + .min_uV = 1100000, + .max_uV = 3300000, + .load_lp_uA = 5000, + /* values for Vaux4Regu register */ + .update_bank = 0x04, + .update_reg = 0x2e, + .update_mask = 0x03, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, + /* values for Vaux4SEL register */ + .voltage_bank = 0x04, + .voltage_reg = 0x2f, + .voltage_mask = 0x0f, + .voltages = ldo_vauxn_voltages, + .voltages_len = ARRAY_SIZE(ldo_vauxn_voltages), + }, + [AB9540_LDO_INTCORE] = { + .desc = { + .name = "LDO-INTCORE", + .ops = &ab8500_regulator_volt_mode_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_LDO_INTCORE, + .owner = THIS_MODULE, + .n_voltages = ARRAY_SIZE(ldo_vintcore_voltages), + }, + .min_uV = 1100000, + .max_uV = 3300000, + .load_lp_uA = 5000, + .update_bank = 0x03, + .update_reg = 0x80, + .update_mask = 0x44, + .update_val = 0x44, + .update_val_idle = 0x44, + .update_val_normal = 0x04, + .voltage_bank = 0x03, + .voltage_reg = 0x80, + .voltage_mask = 0x38, + .voltages = ldo_vintcore_voltages, + .voltages_len = ARRAY_SIZE(ldo_vintcore_voltages), + }, + + /* + * Fixed Voltage Regulators + * name, fixed mV, + * update bank, reg, mask, enable val + */ + [AB9540_LDO_TVOUT] = { + .desc = { + .name = "LDO-TVOUT", + .ops = &ab8500_regulator_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_TVOUT, .owner = THIS_MODULE, @@ -456,17 +944,20 @@ static struct ab8500_regulator_info }, .delay = 10000, .fixed_uV = 2000000, + .load_lp_uA = 1000, .update_bank = 0x03, .update_reg = 0x80, .update_mask = 0x82, - .update_val_enable = 0x02, + .update_val = 0x02, + .update_val_idle = 0x82, + .update_val_normal = 0x02, }, - [AB8500_LDO_USB] = { + [AB9540_LDO_USB] = { .desc = { .name = "LDO-USB", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_ops, .type = REGULATOR_VOLTAGE, - .id = AB8500_LDO_USB, + .id = AB9540_LDO_USB, .owner = THIS_MODULE, .n_voltages = 1, }, @@ -474,12 +965,14 @@ static struct ab8500_regulator_info .update_bank = 0x03, .update_reg = 0x82, .update_mask = 0x03, - .update_val_enable = 0x01, + .update_val = 0x01, + .update_val_idle = 0x03, + .update_val_normal = 0x01, }, - [AB8500_LDO_AUDIO] = { + [AB9540_LDO_AUDIO] = { .desc = { .name = "LDO-AUDIO", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_AUDIO, .owner = THIS_MODULE, @@ -489,12 +982,12 @@ static struct ab8500_regulator_info .update_bank = 0x03, .update_reg = 0x83, .update_mask = 0x02, - .update_val_enable = 0x02, + .update_val = 0x02, }, - [AB8500_LDO_ANAMIC1] = { + [AB9540_LDO_ANAMIC1] = { .desc = { .name = "LDO-ANAMIC1", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_ANAMIC1, .owner = THIS_MODULE, @@ -504,12 +997,12 @@ static struct ab8500_regulator_info .update_bank = 0x03, .update_reg = 0x83, .update_mask = 0x08, - .update_val_enable = 0x08, + .update_val = 0x08, }, - [AB8500_LDO_ANAMIC2] = { + [AB9540_LDO_ANAMIC2] = { .desc = { .name = "LDO-ANAMIC2", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_ANAMIC2, .owner = THIS_MODULE, @@ -519,12 +1012,12 @@ static struct ab8500_regulator_info .update_bank = 0x03, .update_reg = 0x83, .update_mask = 0x10, - .update_val_enable = 0x10, + .update_val = 0x10, }, - [AB8500_LDO_DMIC] = { + [AB9540_LDO_DMIC] = { .desc = { .name = "LDO-DMIC", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_DMIC, .owner = THIS_MODULE, @@ -534,25 +1027,58 @@ static struct ab8500_regulator_info .update_bank = 0x03, .update_reg = 0x83, .update_mask = 0x04, - .update_val_enable = 0x04, + .update_val = 0x04, }, - [AB8500_LDO_ANA] = { + + /* + * Regulators with fixed voltage and normal/idle modes + */ + [AB9540_LDO_ANA] = { .desc = { .name = "LDO-ANA", - .ops = &ab8500_regulator_fixed_ops, + .ops = &ab8500_regulator_mode_ops, .type = REGULATOR_VOLTAGE, .id = AB8500_LDO_ANA, .owner = THIS_MODULE, .n_voltages = 1, }, .fixed_uV = 1200000, + .load_lp_uA = 1000, .update_bank = 0x04, .update_reg = 0x06, .update_mask = 0x0c, - .update_val_enable = 0x04, + .update_val = 0x04, + .update_val_idle = 0x0c, + .update_val_normal = 0x04, }, - + /* + * SysClkReq regulators + */ + [AB9540_SYSCLKREQ_2] = { + .desc = { + .name = "SYSCLKREQ-2", + .ops = &ab8500_sysclkreq_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_SYSCLKREQ_2, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 1, /* bogus value */ + .gpio_pin = AB8500_PIN_GPIO1, + }, + [AB9540_SYSCLKREQ_4] = { + .desc = { + .name = "SYSCLKREQ-4", + .ops = &ab8500_sysclkreq_ops, + .type = REGULATOR_VOLTAGE, + .id = AB8500_SYSCLKREQ_4, + .owner = THIS_MODULE, + .n_voltages = 1, + }, + .fixed_uV = 1, /* bogus value */ + .gpio_pin = AB8500_PIN_GPIO3, + }, }; struct ab8500_reg_init { @@ -568,13 +1094,13 @@ struct ab8500_reg_init { .mask = _mask, \ } +/* AB8500 register init */ static struct ab8500_reg_init ab8500_reg_init[] = { /* * 0x30, VanaRequestCtrl - * 0x0C, VpllRequestCtrl * 0xc0, VextSupply1RequestCtrl */ - REG_INIT(AB8500_REGUREQUESTCTRL2, 0x03, 0x04, 0xfc), + REG_INIT(AB8500_REGUREQUESTCTRL2, 0x03, 0x04, 0xf0), /* * 0x03, VextSupply2RequestCtrl * 0x0c, VextSupply3RequestCtrl @@ -641,13 +1167,21 @@ static struct ab8500_reg_init ab8500_reg_init[] = { REG_INIT(AB8500_REGUSWHPREQVALID2, 0x03, 0x0e, 0x1f), /* * 0x02, SysClkReq2Valid1 - * ... + * 0x04, SysClkReq3Valid1 + * 0x08, SysClkReq4Valid1 + * 0x10, SysClkReq5Valid1 + * 0x20, SysClkReq6Valid1 + * 0x40, SysClkReq7Valid1 * 0x80, SysClkReq8Valid1 */ REG_INIT(AB8500_REGUSYSCLKREQVALID1, 0x03, 0x0f, 0xfe), /* * 0x02, SysClkReq2Valid2 - * ... + * 0x04, SysClkReq3Valid2 + * 0x08, SysClkReq4Valid2 + * 0x10, SysClkReq5Valid2 + * 0x20, SysClkReq6Valid2 + * 0x40, SysClkReq7Valid2 * 0x80, SysClkReq8Valid2 */ REG_INIT(AB8500_REGUSYSCLKREQVALID2, 0x03, 0x10, 0xfe), @@ -672,8 +1206,8 @@ static struct ab8500_reg_init ab8500_reg_init[] = { */ REG_INIT(AB8500_REGUCTRL1VAMIC, 0x03, 0x84, 0x03), /* + * 0x03, VpllRegu (NOTE! PRCMU register bits) * 0x0c, VanaRegu - * 0x03, VpllRegu */ REG_INIT(AB8500_VPLLVANAREGU, 0x04, 0x06, 0x0f), /* @@ -699,10 +1233,6 @@ static struct ab8500_reg_init ab8500_reg_init[] = { */ REG_INIT(AB8500_VRF1VAUX3REGU, 0x04, 0x0a, 0x03), /* - * 0x3f, Vsmps1Sel1 - */ - REG_INIT(AB8500_VSMPS1SEL1, 0x04, 0x13, 0x3f), - /* * 0x0f, Vaux1Sel */ REG_INIT(AB8500_VAUX1SEL, 0x04, 0x1f, 0x0f), @@ -735,79 +1265,412 @@ static struct ab8500_reg_init ab8500_reg_init[] = { REG_INIT(AB8500_REGUCTRLDISCH2, 0x04, 0x44, 0x16), }; +/* Possibility to add debug */ +int __attribute__((weak)) ab8500_regulator_debug_init( + struct platform_device *pdev) +{ + return 0; +} + +int __attribute__((weak)) ab8500_regulator_debug_exit( + struct platform_device *pdev) +{ + return 0; +} + +/* AB9540 register init */ +static struct ab8500_reg_init ab9540_reg_init[] = { + /* + * 0x03, VarmRequestCtrl + * 0x0c, VapeRequestCtrl + * 0x30, Vsmps1RequestCtrl + * 0xc0, Vsmps2RequestCtrl + */ + REG_INIT(AB9540_REGUREQUESTCTRL1, 0x03, 0x03, 0xff), + /* + * 0x03, Vsmps3RequestCtrl + * 0x0c, VpllRequestCtrl + * 0x30, VanaRequestCtrl + * 0xc0, VextSupply1RequestCtrl + */ + REG_INIT(AB9540_REGUREQUESTCTRL2, 0x03, 0x04, 0xff), + /* + * 0x03, VextSupply2RequestCtrl + * 0x0c, VextSupply3RequestCtrl + * 0x30, Vaux1RequestCtrl + * 0xc0, Vaux2RequestCtrl + */ + REG_INIT(AB9540_REGUREQUESTCTRL3, 0x03, 0x05, 0xff), + /* + * 0x03, Vaux3RequestCtrl + * 0x04, SwHPReq + */ + REG_INIT(AB9540_REGUREQUESTCTRL4, 0x03, 0x06, 0x07), + /* + * 0x01, Vsmps1SysClkReq1HPValid + * 0x02, Vsmps2SysClkReq1HPValid + * 0x04, Vsmps3SysClkReq1HPValid + * 0x08, VanaSysClkReq1HPValid + * 0x10, VpllSysClkReq1HPValid + * 0x20, Vaux1SysClkReq1HPValid + * 0x40, Vaux2SysClkReq1HPValid + * 0x80, Vaux3SysClkReq1HPValid + */ + REG_INIT(AB9540_REGUSYSCLKREQ1HPVALID1, 0x03, 0x07, 0xff), + /* + * 0x01, VapeSysClkReq1HPValid + * 0x02, VarmSysClkReq1HPValid + * 0x04, VbbSysClkReq1HPValid + * 0x08, VmodSysClkReq1HPValid + * 0x10, VextSupply1SysClkReq1HPValid + * 0x20, VextSupply2SysClkReq1HPValid + * 0x40, VextSupply3SysClkReq1HPValid + */ + REG_INIT(AB9540_REGUSYSCLKREQ1HPVALID2, 0x03, 0x08, 0x7f), + /* + * 0x01, Vsmps1HwHPReq1Valid + * 0x02, Vsmps2HwHPReq1Valid + * 0x04, Vsmps3HwHPReq1Valid + * 0x08, VanaHwHPReq1Valid + * 0x10, VpllHwHPReq1Valid + * 0x20, Vaux1HwHPReq1Valid + * 0x40, Vaux2HwHPReq1Valid + * 0x80, Vaux3HwHPReq1Valid + */ + REG_INIT(AB9540_REGUHWHPREQ1VALID1, 0x03, 0x09, 0xff), + /* + * 0x01, VextSupply1HwHPReq1Valid + * 0x02, VextSupply2HwHPReq1Valid + * 0x04, VextSupply3HwHPReq1Valid + * 0x08, VmodHwHPReq1Valid + */ + REG_INIT(AB9540_REGUHWHPREQ1VALID2, 0x03, 0x0a, 0x0f), + /* + * 0x01, Vsmps1HwHPReq2Valid + * 0x02, Vsmps2HwHPReq2Valid + * 0x03, Vsmps3HwHPReq2Valid + * 0x08, VanaHwHPReq2Valid + * 0x10, VpllHwHPReq2Valid + * 0x20, Vaux1HwHPReq2Valid + * 0x40, Vaux2HwHPReq2Valid + * 0x80, Vaux3HwHPReq2Valid + */ + REG_INIT(AB9540_REGUHWHPREQ2VALID1, 0x03, 0x0b, 0xff), + /* + * 0x01, VextSupply1HwHPReq2Valid + * 0x02, VextSupply2HwHPReq2Valid + * 0x04, VextSupply3HwHPReq2Valid + * 0x08, VmodHwHPReq2Valid + */ + REG_INIT(AB9540_REGUHWHPREQ2VALID2, 0x03, 0x0c, 0x0f), + /* + * 0x01, VapeSwHPReqValid + * 0x02, VarmSwHPReqValid + * 0x04, Vsmps1SwHPReqValid + * 0x08, Vsmps2SwHPReqValid + * 0x10, Vsmps3SwHPReqValid + * 0x20, VanaSwHPReqValid + * 0x40, VpllSwHPReqValid + * 0x80, Vaux1SwHPReqValid + */ + REG_INIT(AB9540_REGUSWHPREQVALID1, 0x03, 0x0d, 0xff), + /* + * 0x01, Vaux2SwHPReqValid + * 0x02, Vaux3SwHPReqValid + * 0x04, VextSupply1SwHPReqValid + * 0x08, VextSupply2SwHPReqValid + * 0x10, VextSupply3SwHPReqValid + * 0x20, VmodSwHPReqValid + */ + REG_INIT(AB9540_REGUSWHPREQVALID2, 0x03, 0x0e, 0x3f), + /* + * 0x02, SysClkReq2Valid1 + * ... + * 0x80, SysClkReq8Valid1 + */ + REG_INIT(AB9540_REGUSYSCLKREQVALID1, 0x03, 0x0f, 0xfe), + /* + * 0x02, SysClkReq2Valid2 + * ... + * 0x80, SysClkReq8Valid2 + */ + REG_INIT(AB9540_REGUSYSCLKREQVALID2, 0x03, 0x10, 0xfe), + /* + * 0x01, Vaux4SwHPReqValid + * 0x02, Vaux4HwHPReq2Valid + * 0x04, Vaux4HwHPReq1Valid + * 0x08, Vaux4SysClkReq1HPValid + */ + REG_INIT(AB9540_REGUVAUX4REQVALID, 0x03, 0x11, 0x0f), + /* + * 0x02, VTVoutEna + * 0x04, Vintcore12Ena + * 0x38, Vintcore12Sel + * 0x40, Vintcore12LP + * 0x80, VTVoutLP + */ + REG_INIT(AB9540_REGUMISC1, 0x03, 0x80, 0xfe), + /* + * 0x02, VaudioEna + * 0x04, VdmicEna + * 0x08, Vamic1Ena + * 0x10, Vamic2Ena + */ + REG_INIT(AB9540_VAUDIOSUPPLY, 0x03, 0x83, 0x1e), + /* + * 0x01, Vamic1_dzout + * 0x02, Vamic2_dzout + */ + REG_INIT(AB9540_REGUCTRL1VAMIC, 0x03, 0x84, 0x03), + /* + * 0x03, Vsmps1Regu + * 0x0c, Vsmps1SelCtrl + * 0x10, Vsmps1AutoMode + * 0x20, Vsmps1PWMMode + */ + REG_INIT(AB9540_VSMPS1REGU, 0x04, 0x03, 0x3f), + /* + * 0x03, Vsmps2Regu + * 0x0c, Vsmps2SelCtrl + * 0x10, Vsmps2AutoMode + * 0x20, Vsmps2PWMMode + */ + REG_INIT(AB9540_VSMPS2REGU, 0x04, 0x04, 0x3f), + /* + * 0x03, Vsmps3Regu + * 0x0c, Vsmps3SelCtrl + * NOTE! PRCMU register + */ + REG_INIT(AB9540_VSMPS3REGU, 0x04, 0x05, 0x0f), + /* + * 0x03, VpllRegu + * 0x0c, VanaRegu + */ + REG_INIT(AB9540_VPLLVANAREGU, 0x04, 0x06, 0x0f), + /* + * 0x03, VextSupply1Regu + * 0x0c, VextSupply2Regu + * 0x30, VextSupply3Regu + * 0x40, ExtSupply2Bypass + * 0x80, ExtSupply3Bypass + */ + REG_INIT(AB9540_EXTSUPPLYREGU, 0x04, 0x08, 0xff), + /* + * 0x03, Vaux1Regu + * 0x0c, Vaux2Regu + */ + REG_INIT(AB9540_VAUX12REGU, 0x04, 0x09, 0x0f), + /* + * 0x0c, Vrf1Regu + * 0x03, Vaux3Regu + */ + REG_INIT(AB9540_VRF1VAUX3REGU, 0x04, 0x0a, 0x0f), + /* + * 0x3f, Vsmps1Sel1 + */ + REG_INIT(AB9540_VSMPS1SEL1, 0x04, 0x13, 0x3f), + /* + * 0x3f, Vsmps1Sel2 + */ + REG_INIT(AB9540_VSMPS1SEL2, 0x04, 0x14, 0x3f), + /* + * 0x3f, Vsmps1Sel3 + */ + REG_INIT(AB9540_VSMPS1SEL3, 0x04, 0x15, 0x3f), + /* + * 0x3f, Vsmps2Sel1 + */ + REG_INIT(AB9540_VSMPS2SEL1, 0x04, 0x17, 0x3f), + /* + * 0x3f, Vsmps2Sel2 + */ + REG_INIT(AB9540_VSMPS2SEL2, 0x04, 0x18, 0x3f), + /* + * 0x3f, Vsmps2Sel3 + */ + REG_INIT(AB9540_VSMPS2SEL3, 0x04, 0x19, 0x3f), + /* + * 0x7f, Vsmps3Sel1 + * NOTE! PRCMU register + */ + REG_INIT(AB9540_VSMPS3SEL1, 0x04, 0x1b, 0x7f), + /* + * 0x7f, Vsmps3Sel2 + * NOTE! PRCMU register + */ + REG_INIT(AB9540_VSMPS3SEL2, 0x04, 0x1c, 0x7f), + /* + * 0x0f, Vaux1Sel + */ + REG_INIT(AB9540_VAUX1SEL, 0x04, 0x1f, 0x0f), + /* + * 0x0f, Vaux2Sel + */ + REG_INIT(AB9540_VAUX2SEL, 0x04, 0x20, 0x0f), + /* + * 0x07, Vaux3Sel + * 0x30, Vrf1Sel + */ + REG_INIT(AB9540_VRF1VAUX3SEL, 0x04, 0x21, 0x37), + /* + * 0x01, VextSupply12LP + */ + REG_INIT(AB9540_REGUCTRL2SPARE, 0x04, 0x22, 0x01), + /* + * 0x03, Vaux4RequestCtrl + */ + REG_INIT(AB9540_VAUX4REQCTRL, 0x04, 0x2d, 0x03), + /* + * 0x03, Vaux4Regu + */ + REG_INIT(AB9540_VAUX4REGU, 0x04, 0x2e, 0x03), + /* + * 0x08, Vaux4Sel + */ + REG_INIT(AB9540_VAUX4SEL, 0x04, 0x2f, 0x0f), + /* + * 0x01, VpllDisch + * 0x02, Vrf1Disch + * 0x04, Vaux1Disch + * 0x08, Vaux2Disch + * 0x10, Vaux3Disch + * 0x20, Vintcore12Disch + * 0x40, VTVoutDisch + * 0x80, VaudioDisch + */ + REG_INIT(AB9540_REGUCTRLDISCH, 0x04, 0x43, 0xff), + /* + * 0x01, VsimDisch + * 0x02, VanaDisch + * 0x04, VdmicPullDownEna + * 0x08, VpllPullDownEna + * 0x10, VdmicDisch + */ + REG_INIT(AB9540_REGUCTRLDISCH2, 0x04, 0x44, 0x1f), + /* + * 0x01, Vaux4Disch + */ + REG_INIT(AB9540_REGUCTRLDISCH3, 0x04, 0x48, 0x01), +}; + static __devinit int ab8500_regulator_probe(struct platform_device *pdev) { struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); - struct ab8500_platform_data *pdata; + struct ab8500_platform_data *ppdata; + struct ab8500_regulator_platform_data *pdata; int i, err; + struct ab8500_regulator_info *regulator_info; + int regulator_info_size; + struct ab8500_reg_init *reg_init; + int reg_init_size; + /* cache values needed repeatedly inside for-loops */ if (!ab8500) { dev_err(&pdev->dev, "null mfd parent\n"); return -EINVAL; } - pdata = dev_get_platdata(ab8500->dev); + + ppdata = dev_get_platdata(ab8500->dev); + if (!ppdata) { + dev_err(&pdev->dev, "null parent pdata\n"); + return -EINVAL; + } + + pdata = ppdata->regulator; if (!pdata) { dev_err(&pdev->dev, "null pdata\n"); return -EINVAL; } + if (is_ab9540(ab8500)) { + regulator_info = ab9540_regulator_info; + regulator_info_size = ARRAY_SIZE(ab9540_regulator_info); + reg_init = ab9540_reg_init; + reg_init_size = AB9540_NUM_REGULATOR_REGISTERS; + } else { + regulator_info = ab8500_regulator_info; + regulator_info_size = ARRAY_SIZE(ab8500_regulator_info); + reg_init = ab8500_reg_init; + reg_init_size = AB8500_NUM_REGULATOR_REGISTERS; + } + /* make sure the platform data has the correct size */ - if (pdata->num_regulator != ARRAY_SIZE(ab8500_regulator_info)) { + if (pdata->num_regulator != regulator_info_size) { dev_err(&pdev->dev, "Configuration error: size mismatch.\n"); return -EINVAL; } + /* initialize debug (initial state is recorded with this call) */ + err = ab8500_regulator_debug_init(pdev); + if (err) + return err; + /* initialize registers */ - for (i = 0; i < pdata->num_regulator_reg_init; i++) { + for (i = 0; i < pdata->num_reg_init; i++) { int id; - u8 value; + u8 mask, value; - id = pdata->regulator_reg_init[i].id; - value = pdata->regulator_reg_init[i].value; + id = pdata->reg_init[i].id; + mask = pdata->reg_init[i].mask; + value = pdata->reg_init[i].value; /* check for configuration errors */ - if (id >= AB8500_NUM_REGULATOR_REGISTERS) { - dev_err(&pdev->dev, - "Configuration error: id outside range.\n"); - return -EINVAL; - } - if (value & ~ab8500_reg_init[id].mask) { - dev_err(&pdev->dev, - "Configuration error: value outside mask.\n"); - return -EINVAL; - } + BUG_ON(id >= reg_init_size); + BUG_ON(value & ~mask); + BUG_ON(mask & ~reg_init[id].mask); /* initialize register */ err = abx500_mask_and_set_register_interruptible(&pdev->dev, - ab8500_reg_init[id].bank, - ab8500_reg_init[id].addr, - ab8500_reg_init[id].mask, - value); + reg_init[id].bank, + reg_init[id].addr, + mask, value); if (err < 0) { dev_err(&pdev->dev, "Failed to initialize 0x%02x, 0x%02x.\n", - ab8500_reg_init[id].bank, - ab8500_reg_init[id].addr); + reg_init[id].bank, + reg_init[id].addr); return err; } dev_vdbg(&pdev->dev, " init: 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", - ab8500_reg_init[id].bank, - ab8500_reg_init[id].addr, - ab8500_reg_init[id].mask, - value); + reg_init[id].bank, + reg_init[id].addr, + mask, value); + } + + /* + * This changes the default setting for VextSupply3Regu to low power. + * Active high or low is depending on OTP which is changed from ab8500v3.0. + * Remove this when ab8500v2.0 is no longer important. + * This only affects power consumption and it depends on the + * HREF OTP configurations. + */ + if (is_ab8500_2p0_or_earlier(ab8500)) { + err = abx500_mask_and_set_register_interruptible(&pdev->dev, + AB8500_REGU_CTRL2, 0x08, 0x30, 0x30); + if (err < 0) { + dev_err(&pdev->dev, + "Failed to override 0x%02x, 0x%02x.\n", + AB8500_REGU_CTRL2, 0x08); + return err; + } } + /* register external regulators (before Vaux1, 2 and 3) */ + err = ab8500_ext_regulator_init(pdev); + if (err) + return err; + /* register all regulators */ - for (i = 0; i < ARRAY_SIZE(ab8500_regulator_info); i++) { + for (i = 0; i < regulator_info_size; i++) { struct ab8500_regulator_info *info = NULL; /* assign per-regulator data */ - info = &ab8500_regulator_info[i]; + info = ®ulator_info[i]; info->dev = &pdev->dev; /* fix for hardware before ab8500v2.0 */ - if (abx500_get_chip_id(info->dev) < 0x20) { + if (is_ab8500_1p1_or_earlier(ab8500)) { if (info->desc.id == AB8500_LDO_AUX3) { info->desc.n_voltages = ARRAY_SIZE(ldo_vauxn_voltages); @@ -827,7 +1690,7 @@ static __devinit int ab8500_regulator_probe(struct platform_device *pdev) info->desc.name); /* when we fail, un-register all earlier regulators */ while (--i >= 0) { - info = &ab8500_regulator_info[i]; + info = ®ulator_info[i]; regulator_unregister(info->regulator); } return err; @@ -842,11 +1705,23 @@ static __devinit int ab8500_regulator_probe(struct platform_device *pdev) static __devexit int ab8500_regulator_remove(struct platform_device *pdev) { - int i; + int i, err; + struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); + struct ab8500_regulator_info *regulator_info; + int regulator_info_size; + - for (i = 0; i < ARRAY_SIZE(ab8500_regulator_info); i++) { + if (is_ab9540(ab8500)) { + regulator_info = ab9540_regulator_info; + regulator_info_size = ARRAY_SIZE(ab9540_regulator_info); + } else { + regulator_info = ab8500_regulator_info; + regulator_info_size = ARRAY_SIZE(ab8500_regulator_info); + } + + for (i = 0; i < regulator_info_size; i++) { struct ab8500_regulator_info *info = NULL; - info = &ab8500_regulator_info[i]; + info = ®ulator_info[i]; dev_vdbg(rdev_get_dev(info->regulator), "%s-remove\n", info->desc.name); @@ -854,6 +1729,16 @@ static __devexit int ab8500_regulator_remove(struct platform_device *pdev) regulator_unregister(info->regulator); } + /* remove external regulators (after Vaux1, 2 and 3) */ + err = ab8500_ext_regulator_exit(pdev); + if (err) + return err; + + /* remove regulator debug */ + err = ab8500_regulator_debug_exit(pdev); + if (err) + return err; + return 0; } @@ -886,5 +1771,6 @@ module_exit(ab8500_regulator_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Sundar Iyer <sundar.iyer@stericsson.com>"); +MODULE_AUTHOR("Bengt Jonsson <bengt.g.jonsson@stericsson.com>"); MODULE_DESCRIPTION("Regulator Driver for ST-Ericsson AB8500 Mixed-Sig PMIC"); MODULE_ALIAS("platform:ab8500-regulator"); diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index 046fb1bd861..bc6c12c8a76 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -81,6 +81,7 @@ struct regulator { struct device_attribute dev_attr; struct regulator_dev *rdev; struct dentry *debugfs; + int use; }; static int _regulator_is_enabled(struct regulator_dev *rdev); @@ -199,11 +200,13 @@ static int regulator_check_consumers(struct regulator_dev *rdev, */ if (!regulator->min_uV && !regulator->max_uV) continue; - - if (*max_uV > regulator->max_uV) - *max_uV = regulator->max_uV; - if (*min_uV < regulator->min_uV) - *min_uV = regulator->min_uV; + + if (regulator->use) { + if (*max_uV > regulator->max_uV) + *max_uV = regulator->max_uV; + if (*min_uV < regulator->min_uV) + *min_uV = regulator->min_uV; + } } if (*min_uV > *max_uV) @@ -602,6 +605,32 @@ static ssize_t regulator_suspend_standby_state_show(struct device *dev, static DEVICE_ATTR(suspend_standby_state, 0444, regulator_suspend_standby_state_show, NULL); +static ssize_t regulator_use_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct regulator_dev *rdev = dev_get_drvdata(dev); + struct regulator *reg; + size_t size = 0; + + if (rdev->use_count == 0) + return sprintf(buf, "no users\n"); + + list_for_each_entry(reg, &rdev->consumer_list, list) { + if (!reg->use) + continue; + + if (reg->dev != NULL) + size += sprintf((buf + size), "%s (%d) ", + dev_name(reg->dev), reg->use); + else + size += sprintf((buf + size), "unknown (%d) ", + reg->use); + } + size += sprintf((buf + size), "\n"); + + return size; +} +static DEVICE_ATTR(use, 0444, regulator_use_show, NULL); /* * These are the only attributes are present for all regulators. @@ -1491,12 +1520,8 @@ static int _regulator_enable(struct regulator_dev *rdev) trace_regulator_enable_delay(rdev_get_name(rdev)); - if (delay >= 1000) { - mdelay(delay / 1000); - udelay(delay % 1000); - } else if (delay) { - udelay(delay); - } + if (delay) + usleep_range(delay, delay); trace_regulator_enable_complete(rdev_get_name(rdev)); @@ -1540,6 +1565,8 @@ int regulator_enable(struct regulator *regulator) if (ret != 0 && rdev->supply) regulator_disable(rdev->supply); + else + regulator->use++; return ret; } @@ -1613,6 +1640,9 @@ int regulator_disable(struct regulator *regulator) if (ret == 0 && rdev->supply) regulator_disable(rdev->supply); + if (ret == 0) + regulator->use--; + return ret; } EXPORT_SYMBOL_GPL(regulator_disable); @@ -2699,6 +2729,10 @@ static int add_regulator_attributes(struct regulator_dev *rdev) struct regulator_ops *ops = rdev->desc->ops; int status = 0; + status = device_create_file(dev, &dev_attr_use); + if (status < 0) + dev_warn(dev, "Create sysfs file \"use\" failed"); + /* some attributes need specific methods to be displayed */ if ((ops->get_voltage && ops->get_voltage(rdev) >= 0) || (ops->get_voltage_sel && ops->get_voltage_sel(rdev) >= 0)) { diff --git a/drivers/regulator/db5500-prcmu.c b/drivers/regulator/db5500-prcmu.c new file mode 100644 index 00000000000..189362ab8e0 --- /dev/null +++ b/drivers/regulator/db5500-prcmu.c @@ -0,0 +1,334 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Authors: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson + * Bengt Jonsson <bengt.g.jonsson@stericsson.com> for ST-Ericsson + * + * Power domain regulators on DB5500 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/db5500-prcmu.h> + +#include <linux/mfd/dbx500-prcmu.h> + +#include "dbx500-prcmu.h" +static int db5500_regulator_enable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-enable\n", + info->desc.name); + + if (!info->is_enabled) { + info->is_enabled = true; + if (!info->exclude_from_power_state) + power_state_active_enable(); + } + + return 0; +} + +static int db5500_regulator_disable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + int ret = 0; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-disable\n", + info->desc.name); + + if (info->is_enabled) { + info->is_enabled = false; + if (!info->exclude_from_power_state) + ret = power_state_active_disable(); + } + + return ret; +} + +static int db5500_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-%s-is_enabled (is_enabled):" + " %i\n", info->desc.name, info->is_enabled); + + return info->is_enabled; +} + +/* db5500 regulator operations */ +static struct regulator_ops db5500_regulator_ops = { + .enable = db5500_regulator_enable, + .disable = db5500_regulator_disable, + .is_enabled = db5500_regulator_is_enabled, +}; + +/* + * EPOD control + */ +static bool epod_on[NUM_EPOD_ID]; +static bool epod_ramret[NUM_EPOD_ID]; + +static inline int epod_id_to_index(u16 epod_id) +{ + return epod_id - DB5500_EPOD_ID_BASE; +} + +static int enable_epod(u16 epod_id, bool ramret) +{ + int idx = epod_id_to_index(epod_id); + int ret; + + if (ramret) { + if (!epod_on[idx]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); + if (ret < 0) + return ret; + } + epod_ramret[idx] = true; + } else { + ret = prcmu_set_epod(epod_id, EPOD_STATE_ON); + if (ret < 0) + return ret; + epod_on[idx] = true; + } + + return 0; +} + +static int disable_epod(u16 epod_id, bool ramret) +{ + int idx = epod_id_to_index(epod_id); + int ret; + + if (ramret) { + if (!epod_on[idx]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_OFF); + if (ret < 0) + return ret; + } + epod_ramret[idx] = false; + } else { + if (epod_ramret[idx]) { + ret = prcmu_set_epod(epod_id, EPOD_STATE_RAMRET); + if (ret < 0) + return ret; + } else { + ret = prcmu_set_epod(epod_id, EPOD_STATE_OFF); + if (ret < 0) + return ret; + } + epod_on[idx] = false; + } + + return 0; +} + +/* + * Regulator switch + */ +static int db5500_regulator_switch_enable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-switch-%s-enable\n", + info->desc.name); + + ret = enable_epod(info->epod_id, info->is_ramret); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "regulator-switch-%s-enable: prcmu call failed\n", + info->desc.name); + goto out; + } + + info->is_enabled = true; +out: + return ret; +} + +static int db5500_regulator_switch_disable(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + int ret; + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), "regulator-switch-%s-disable\n", + info->desc.name); + + ret = disable_epod(info->epod_id, info->is_ramret); + if (ret < 0) { + dev_err(rdev_get_dev(rdev), + "regulator_switch-%s-disable: prcmu call failed\n", + info->desc.name); + goto out; + } + + info->is_enabled = 0; +out: + return ret; +} + +static int db5500_regulator_switch_is_enabled(struct regulator_dev *rdev) +{ + struct dbx500_regulator_info *info = rdev_get_drvdata(rdev); + + if (info == NULL) + return -EINVAL; + + dev_vdbg(rdev_get_dev(rdev), + "regulator-switch-%s-is_enabled (is_enabled): %i\n", + info->desc.name, info->is_enabled); + + return info->is_enabled; +} + +static struct regulator_ops db5500_regulator_switch_ops = { + .enable = db5500_regulator_switch_enable, + .disable = db5500_regulator_switch_disable, + .is_enabled = db5500_regulator_switch_is_enabled, +}; + +/* + * Regulator information + */ +#define DB5500_REGULATOR_SWITCH(_name, reg) \ + [DB5500_REGULATOR_SWITCH_##reg] = { \ + .desc = { \ + .name = _name, \ + .id = DB5500_REGULATOR_SWITCH_##reg, \ + .ops = &db5500_regulator_switch_ops, \ + .type = REGULATOR_VOLTAGE, \ + .owner = THIS_MODULE, \ + }, \ + .epod_id = DB5500_EPOD_ID_##reg, \ +} + +static struct dbx500_regulator_info + dbx500_regulator_info[DB5500_NUM_REGULATORS] = { + [DB5500_REGULATOR_VAPE] = { + .desc = { + .name = "db5500-vape", + .id = DB5500_REGULATOR_VAPE, + .ops = &db5500_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + }, + }, + DB5500_REGULATOR_SWITCH("db5500-sga", SGA), + DB5500_REGULATOR_SWITCH("db5500-hva", HVA), + DB5500_REGULATOR_SWITCH("db5500-sia", SIA), + DB5500_REGULATOR_SWITCH("db5500-disp", DISP), + DB5500_REGULATOR_SWITCH("db5500-esram12", ESRAM12), +}; + +static int __devinit db5500_regulator_probe(struct platform_device *pdev) +{ + struct regulator_init_data *db5500_init_data = + dev_get_platdata(&pdev->dev); + int i, err; + + /* register all regulators */ + for (i = 0; i < ARRAY_SIZE(dbx500_regulator_info); i++) { + struct dbx500_regulator_info *info; + struct regulator_init_data *init_data = &db5500_init_data[i]; + + /* assign per-regulator data */ + info = &dbx500_regulator_info[i]; + info->dev = &pdev->dev; + + /* register with the regulator framework */ + info->rdev = regulator_register(&info->desc, &pdev->dev, + init_data, info); + if (IS_ERR(info->rdev)) { + err = PTR_ERR(info->rdev); + dev_err(&pdev->dev, "failed to register %s: err %i\n", + info->desc.name, err); + + /* if failing, unregister all earlier regulators */ + i--; + while (i >= 0) { + info = &dbx500_regulator_info[i]; + regulator_unregister(info->rdev); + i--; + } + return err; + } + + dev_dbg(rdev_get_dev(info->rdev), + "regulator-%s-probed\n", info->desc.name); + } + + return 0; +} + +static int __exit db5500_regulator_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dbx500_regulator_info); i++) { + struct dbx500_regulator_info *info; + info = &dbx500_regulator_info[i]; + + dev_vdbg(rdev_get_dev(info->rdev), + "regulator-%s-remove\n", info->desc.name); + + regulator_unregister(info->rdev); + } + + return 0; +} + +static struct platform_driver db5500_regulator_driver = { + .driver = { + .name = "db5500-prcmu-regulators", + .owner = THIS_MODULE, + }, + .probe = db5500_regulator_probe, + .remove = __exit_p(db5500_regulator_remove), +}; + +static int __init db5500_regulator_init(void) +{ + int ret; + + ret = platform_driver_register(&db5500_regulator_driver); + if (ret < 0) + return -ENODEV; + + return 0; +} + +static void __exit db5500_regulator_exit(void) +{ + platform_driver_unregister(&db5500_regulator_driver); +} + +arch_initcall(db5500_regulator_init); +module_exit(db5500_regulator_exit); + +MODULE_AUTHOR("STMicroelectronics/ST-Ericsson"); +MODULE_DESCRIPTION("DB5500 regulator driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/regulator/dbx500-prcmu.c b/drivers/regulator/dbx500-prcmu.c index f2e5ecdc586..bee4f7be93b 100644 --- a/drivers/regulator/dbx500-prcmu.c +++ b/drivers/regulator/dbx500-prcmu.c @@ -9,6 +9,7 @@ */ #include <linux/kernel.h> +#include <linux/module.h> #include <linux/err.h> #include <linux/regulator/driver.h> #include <linux/debugfs.h> @@ -62,12 +63,105 @@ out: return ret; } +struct ux500_regulator { + char *name; + void (*enable)(void); + int (*disable)(void); + int count; +}; + +static struct ux500_regulator ux500_atomic_regulators[] = { + { + .name = "dma40.0", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "ssp0", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "ssp1", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "spi0", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "spi1", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "spi2", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "spi3", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "cryp1", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, + { + .name = "hash1", + .enable = power_state_active_enable, + .disable = power_state_active_disable, + }, +}; + +struct ux500_regulator *__must_check ux500_regulator_get(struct device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ux500_atomic_regulators); i++) { + if (!strcmp(dev_name(dev), ux500_atomic_regulators[i].name)) + return &ux500_atomic_regulators[i]; + } + + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL_GPL(ux500_regulator_get); + +int ux500_regulator_atomic_enable(struct ux500_regulator *regulator) +{ + if (regulator) { + regulator->count++; + regulator->enable(); + return 0; + } + return -EINVAL; +} +EXPORT_SYMBOL_GPL(ux500_regulator_atomic_enable); + +int ux500_regulator_atomic_disable(struct ux500_regulator *regulator) +{ + if (regulator) { + regulator->count--; + return regulator->disable(); + } + return -EINVAL; +} +EXPORT_SYMBOL_GPL(ux500_regulator_atomic_disable); + +void ux500_regulator_put(struct ux500_regulator *regulator) +{ + /* Here for symetric reasons and for possible future use */ +} +EXPORT_SYMBOL_GPL(ux500_regulator_put); + #ifdef CONFIG_REGULATOR_DEBUG static struct ux500_regulator_debug { struct dentry *dir; - struct dentry *status_file; - struct dentry *power_state_cnt_file; struct dbx500_regulator_info *regulator_array; int num_regulators; u8 *state_before_suspend; @@ -119,6 +213,35 @@ static const struct file_operations ux500_regulator_power_state_cnt_fops = { .owner = THIS_MODULE, }; +static int ux500_regulator_power_state_use_print(struct seq_file *s, void *p) +{ + int i; + + seq_printf(s, "\nPower state usage:\n\n"); + + for (i = 0; i < ARRAY_SIZE(ux500_atomic_regulators); i++) { + seq_printf(s, "%s\t : %d\n", + ux500_atomic_regulators[i].name, + ux500_atomic_regulators[i].count); + } + return 0; +} + +static int ux500_regulator_power_state_use_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ux500_regulator_power_state_use_print, + inode->i_private); +} + +static const struct file_operations ux500_regulator_power_state_use_fops = { + .open = ux500_regulator_power_state_use_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + static int ux500_regulator_status_print(struct seq_file *s, void *p) { struct device *dev = s->private; @@ -180,22 +303,27 @@ ux500_regulator_debug_init(struct platform_device *pdev, { /* create directory */ rdebug.dir = debugfs_create_dir("ux500-regulator", NULL); - if (!rdebug.dir) + if (IS_ERR_OR_NULL(rdebug.dir)) goto exit_no_debugfs; /* create "status" file */ - rdebug.status_file = debugfs_create_file("status", - S_IRUGO, rdebug.dir, &pdev->dev, - &ux500_regulator_status_fops); - if (!rdebug.status_file) - goto exit_destroy_dir; + if (IS_ERR_OR_NULL(debugfs_create_file("status", + S_IRUGO, rdebug.dir, &pdev->dev, + &ux500_regulator_status_fops))) + goto exit_fail; + + /* create "power-state-count" file */ + if (IS_ERR_OR_NULL(debugfs_create_file("power-state-count", + S_IRUGO, rdebug.dir, &pdev->dev, + &ux500_regulator_power_state_cnt_fops))) + goto exit_fail; /* create "power-state-count" file */ - rdebug.power_state_cnt_file = debugfs_create_file("power-state-count", - S_IRUGO, rdebug.dir, &pdev->dev, - &ux500_regulator_power_state_cnt_fops); - if (!rdebug.power_state_cnt_file) - goto exit_destroy_status; + if (IS_ERR_OR_NULL(debugfs_create_file("power-state-usage", + S_IRUGO, rdebug.dir, &pdev->dev, + &ux500_regulator_power_state_use_fops))) + goto exit_fail; + rdebug.regulator_array = regulator_info; rdebug.num_regulators = num_regulators; @@ -204,27 +332,22 @@ ux500_regulator_debug_init(struct platform_device *pdev, if (!rdebug.state_before_suspend) { dev_err(&pdev->dev, "could not allocate memory for saving state\n"); - goto exit_destroy_power_state; + goto exit_fail; } rdebug.state_after_suspend = kzalloc(num_regulators, GFP_KERNEL); if (!rdebug.state_after_suspend) { dev_err(&pdev->dev, "could not allocate memory for saving state\n"); - goto exit_free; + goto exit_fail; } dbx500_regulator_testcase(regulator_info, num_regulators); return 0; -exit_free: +exit_fail: kfree(rdebug.state_before_suspend); -exit_destroy_power_state: - debugfs_remove(rdebug.power_state_cnt_file); -exit_destroy_status: - debugfs_remove(rdebug.status_file); -exit_destroy_dir: - debugfs_remove(rdebug.dir); + debugfs_remove_recursive(rdebug.dir); exit_no_debugfs: dev_err(&pdev->dev, "failed to create debugfs entries.\n"); return -ENOMEM; |