From 679794b81e4848037bfc693a323fde5d46b08ffb Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Wed, 16 Jun 2010 16:59:00 +0200 Subject: Reset to linux-next patch: commit 774959368b9b567685179682cebfcf63e540b17b Author: Colin Cross Date: Thu May 19 17:24:51 2011 -0700 ARM: smp_twd: Reconfigure clockevents after cpufreq change Change-Id: If31593fe23c00df528af597e6fd2baaf0c102753 Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32132 --- arch/arm/kernel/smp_twd.c | 90 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 01c186222f3..d562a33a642 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -10,13 +10,17 @@ */ #include #include +#include +#include #include #include +#include #include #include #include #include #include +#include #include #include @@ -24,7 +28,9 @@ /* set up by the platform code */ void __iomem *twd_base; +static struct clk *twd_clk; static unsigned long twd_timer_rate; +static DEFINE_PER_CPU(struct clock_event_device *, twd_ce); static void twd_set_mode(enum clock_event_mode mode, struct clock_event_device *clk) @@ -80,6 +86,52 @@ int twd_timer_ack(void) return 0; } +#ifdef CONFIG_CPU_FREQ + +/* + * Updates clockevent frequency when the cpu frequency changes. + * Called on the cpu that is changing frequency with interrupts disabled. + */ +static void twd_update_frequency(void *data) +{ + twd_timer_rate = clk_get_rate(twd_clk); + + clockevents_update_freq(__get_cpu_var(twd_ce), twd_timer_rate); +} + +static int twd_cpufreq_transition(struct notifier_block *nb, + unsigned long state, void *data) +{ + struct cpufreq_freqs *freqs = data; + + /* + * The twd clock events must be reprogrammed to account for the new + * frequency. The timer is local to a cpu, so cross-call to the + * changing cpu. + */ + if (state == CPUFREQ_POSTCHANGE || state == CPUFREQ_RESUMECHANGE) + smp_call_function_single(freqs->cpu, twd_update_frequency, + NULL, 1); + + return NOTIFY_OK; +} + +static struct notifier_block twd_cpufreq_nb = { + .notifier_call = twd_cpufreq_transition, +}; + +static int twd_cpufreq_init(void) +{ + if (!IS_ERR_OR_NULL(twd_clk)) + return cpufreq_register_notifier(&twd_cpufreq_nb, + CPUFREQ_TRANSITION_NOTIFIER); + + return 0; +} +core_initcall(twd_cpufreq_init); + +#endif + static void __cpuinit twd_calibrate_rate(void) { unsigned long count; @@ -119,12 +171,39 @@ static void __cpuinit twd_calibrate_rate(void) } } +static struct clk *twd_get_clock(void) +{ + struct clk *clk; + int err; + + clk = clk_get_sys("smp_twd", NULL); + if (IS_ERR(clk)) { + pr_err("smp_twd: clock not found: %d\n", (int)PTR_ERR(clk)); + return clk; + } + + err = clk_enable(clk); + if (err) { + pr_err("smp_twd: clock failed to enable: %d\n", err); + clk_put(clk); + return ERR_PTR(err); + } + + return clk; +} + /* * Setup the local clock events for a CPU. */ void __cpuinit twd_timer_setup(struct clock_event_device *clk) { - twd_calibrate_rate(); + if (!twd_clk) + twd_clk = twd_get_clock(); + + if (!IS_ERR_OR_NULL(twd_clk)) + twd_timer_rate = clk_get_rate(twd_clk); + else + twd_calibrate_rate(); clk->name = "local_timer"; clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT | @@ -132,12 +211,11 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) clk->rating = 350; clk->set_mode = twd_set_mode; clk->set_next_event = twd_set_next_event; - clk->shift = 20; - clk->mult = div_sc(twd_timer_rate, NSEC_PER_SEC, clk->shift); - clk->max_delta_ns = clockevent_delta2ns(0xffffffff, clk); - clk->min_delta_ns = clockevent_delta2ns(0xf, clk); - clockevents_register_device(clk); + __get_cpu_var(twd_ce) = clk; + + clockevents_config_and_register(clk, twd_timer_rate, + 0xf, 0xffffffff); /* Make sure our local interrupt controller has this enabled */ gic_enable_ppi(clk->irq); -- cgit v1.2.3 From 36ba52c0582d836ad88b8ec956f13a225539b1a0 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Thu, 22 Sep 2011 15:42:25 +0200 Subject: ARM: kernel: smp_twd: Add save/restore functions Add simple save and restore functions which needs to be called before/after the core(s) are powered off. Change-Id: I95b7e5205d067e3c4949ba09370ea099633ca54e Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32133 --- arch/arm/include/asm/smp_twd.h | 8 ++++++++ arch/arm/kernel/smp_twd.c | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/arch/arm/include/asm/smp_twd.h b/arch/arm/include/asm/smp_twd.h index fed9981fba0..2379b5f930a 100644 --- a/arch/arm/include/asm/smp_twd.h +++ b/arch/arm/include/asm/smp_twd.h @@ -25,4 +25,12 @@ extern void __iomem *twd_base; int twd_timer_ack(void); void twd_timer_setup(struct clock_event_device *); +#if defined(CONFIG_HOTPLUG) || defined(CONFIG_CPU_IDLE) +void twd_save(void); +void twd_restore(void); +#else +static inline void twd_save(void) { } +static inline void twd_restore(void) { } +#endif + #endif diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index d562a33a642..36c68def2a4 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -32,6 +32,9 @@ static struct clk *twd_clk; static unsigned long twd_timer_rate; static DEFINE_PER_CPU(struct clock_event_device *, twd_ce); +static DEFINE_PER_CPU(u32, twd_ctrl); +static DEFINE_PER_CPU(u32, twd_load); + static void twd_set_mode(enum clock_event_mode mode, struct clock_event_device *clk) { @@ -220,3 +223,24 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) /* Make sure our local interrupt controller has this enabled */ gic_enable_ppi(clk->irq); } + +#if defined(CONFIG_HOTPLUG) || defined(CONFIG_CPU_IDLE) +void twd_save(void) +{ + int this_cpu = smp_processor_id(); + + per_cpu(twd_ctrl, this_cpu) = __raw_readl(twd_base + TWD_TIMER_CONTROL); + per_cpu(twd_load, this_cpu) = __raw_readl(twd_base + TWD_TIMER_LOAD); + +} + +void twd_restore(void) +{ + int this_cpu = smp_processor_id(); + + __raw_writel(per_cpu(twd_ctrl, this_cpu), + twd_base + TWD_TIMER_CONTROL); + __raw_writel(per_cpu(twd_load, this_cpu), + twd_base + TWD_TIMER_LOAD); +} +#endif -- cgit v1.2.3 From 255c9c354aebb86e5245c856fcfeed0bd25e180c Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Mon, 14 Jun 2010 15:09:42 +0200 Subject: add prototype for cpu_idle_wait in system.h Signed-off-by: Mian Yousaf Kaukab Change-Id: I010d13db08b903c2199023152b989d2b17ca842f Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/2245 Reviewed-by: Jonas ABERG Signed-off-by: Lee Jones --- arch/arm/include/asm/system.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/include/asm/system.h b/arch/arm/include/asm/system.h index 832888d0c20..42d7d5c6594 100644 --- a/arch/arm/include/asm/system.h +++ b/arch/arm/include/asm/system.h @@ -110,6 +110,8 @@ extern void cpu_init(void); void arm_machine_restart(char mode, const char *cmd); extern void (*arm_pm_restart)(char str, const char *cmd); +void cpu_idle_wait(void); + #define UDBG_UNDEFINED (1 << 0) #define UDBG_SYSCALL (1 << 1) #define UDBG_BADABORT (1 << 2) -- cgit v1.2.3 From ace606e36304fccbf5abbaf3923eb14da639c406 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Tue, 22 Jun 2010 11:37:20 +0200 Subject: fix build errors with CPU_IDLE enabled Current PM for Montblanc enables both LOCAL_TIMERS and GENERIC_CLOCKEVENTS_BROADCAST. This causes build error with v2.6.34. This patch fixes this error. The PM scheme should be reviewed for v2.6.34. Signed-off-by: Mian Yousaf Kaukab Change-Id: Icdafd6f60cec858fc1311614be2e053bba168ef3 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/2257 Reviewed-by: Jonas ABERG Signed-off-by: Lee Jones --- arch/arm/kernel/smp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index d88ff0230e8..693cf511d99 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -485,7 +485,7 @@ void show_local_irqs(struct seq_file *p, int prec) #endif #ifdef CONFIG_GENERIC_CLOCKEVENTS_BROADCAST -static void smp_timer_broadcast(const struct cpumask *mask) +void smp_timer_broadcast(const struct cpumask *mask) { smp_cross_call(mask, IPI_TIMER); } -- cgit v1.2.3 From dd6fd6aa7cda9e84b19854dfa29423261d75bab3 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Thu, 24 Mar 2011 11:42:48 +0000 Subject: ARM: smp: don't recalculate loops_per_jiffy Manually applied patch based on Jonas Aaberg's commit in aid of the 2.6.35 -> 2.6.38 GLK upgrade loops_per_jiffy is calculated at boot, so no need to recalculate loops_per_jiffy each time a secondary cpu goes online. ST-Ericsson Linux next: Yes, 2011-01-12 ST-Ericsson ID: ER282335 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I359b3d2bcc03f1bdb0da641c6975478568778acd Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/11290 Reviewed-by: QATOOLS Reviewed-by: Mattias WALLIN Signed-off-by: Lee Jones --- arch/arm/kernel/smp.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index 693cf511d99..fe9e2954ee4 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -313,8 +313,6 @@ asmlinkage void __cpuinit secondary_start_kernel(void) */ percpu_timer_setup(); - calibrate_delay(); - smp_store_cpu_info(cpu); /* -- cgit v1.2.3 From 6f9493d76c9011f39de4195b6b60147c4cd1f1df Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 21 Mar 2011 16:53:46 +0100 Subject: misc/bh1780gli: add PM features misc/bh1780gli: change unsigned long to long Change-Id: Ia1fc1425bf77e5af042980e0fbdf4f964dbcc03a Signed-off-by: Robert Marklund --- drivers/misc/bh1780gli.c | 68 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/drivers/misc/bh1780gli.c b/drivers/misc/bh1780gli.c index 82fe2d06782..428708c78e1 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -18,10 +18,12 @@ * this program. If not, see . */ #include +#include #include #include #include #include +#include #define BH1780_REG_CONTROL 0x80 #define BH1780_REG_PARTID 0x8A @@ -39,6 +41,7 @@ struct bh1780_data { struct i2c_client *client; + struct regulator *regulator; int power_state; /* lock for sysfs operations */ struct mutex lock; @@ -71,6 +74,9 @@ static ssize_t bh1780_show_lux(struct device *dev, struct bh1780_data *ddata = platform_get_drvdata(pdev); int lsb, msb; + if (ddata->power_state == BH1780_POFF) + return -EINVAL; + lsb = bh1780_read(ddata, BH1780_REG_DLOW, "DLOW"); if (lsb < 0) return lsb; @@ -88,13 +94,9 @@ static ssize_t bh1780_show_power_state(struct device *dev, { struct platform_device *pdev = to_platform_device(dev); struct bh1780_data *ddata = platform_get_drvdata(pdev); - int state; - - state = bh1780_read(ddata, BH1780_REG_CONTROL, "CONTROL"); - if (state < 0) - return state; - return sprintf(buf, "%d\n", state & BH1780_POWMASK); + /* we already maintain a sw state */ + return sprintf(buf, "%d\n", ddata->power_state); } static ssize_t bh1780_store_power_state(struct device *dev, @@ -103,7 +105,7 @@ static ssize_t bh1780_store_power_state(struct device *dev, { struct platform_device *pdev = to_platform_device(dev); struct bh1780_data *ddata = platform_get_drvdata(pdev); - unsigned long val; + long val; int error; error = strict_strtoul(buf, 0, &val); @@ -115,12 +117,22 @@ static ssize_t bh1780_store_power_state(struct device *dev, mutex_lock(&ddata->lock); + if (ddata->power_state == val) + return count; + + if (ddata->power_state == BH1780_POFF) + regulator_enable(ddata->regulator); + error = bh1780_write(ddata, BH1780_REG_CONTROL, val, "CONTROL"); if (error < 0) { mutex_unlock(&ddata->lock); + regulator_disable(ddata->regulator); return error; } + if (val == BH1780_POFF) + regulator_disable(ddata->regulator); + msleep(BH1780_PON_DELAY); ddata->power_state = val; mutex_unlock(&ddata->lock); @@ -130,7 +142,7 @@ static ssize_t bh1780_store_power_state(struct device *dev, static DEVICE_ATTR(lux, S_IRUGO, bh1780_show_lux, NULL); -static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO, +static DEVICE_ATTR(power_state, S_IWUGO | S_IRUGO, bh1780_show_power_state, bh1780_store_power_state); static struct attribute *bh1780_attributes[] = { @@ -152,21 +164,37 @@ static int __devinit bh1780_probe(struct i2c_client *client, if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) { ret = -EIO; - goto err_op_failed; + return ret; } ddata = kzalloc(sizeof(struct bh1780_data), GFP_KERNEL); if (ddata == NULL) { + dev_err(&client->dev, "failed to alloc ddata\n"); ret = -ENOMEM; - goto err_op_failed; + return ret; } ddata->client = client; i2c_set_clientdata(client, ddata); + dev_set_name(&client->dev, "bh1780"); + + ddata->regulator = regulator_get(&client->dev, "v-als"); + if (IS_ERR(ddata->regulator)) { + dev_err(&client->dev, "failed to get regulator\n"); + ret = PTR_ERR(ddata->regulator); + goto free_ddata; + } + + regulator_enable(ddata->regulator); ret = bh1780_read(ddata, BH1780_REG_PARTID, "PART ID"); - if (ret < 0) - goto err_op_failed; + if (ret < 0) { + dev_err(&client->dev, "failed to read part ID\n"); + goto put_regulator; + } + + regulator_disable(ddata->regulator); + ddata->power_state = BH1780_POFF; dev_info(&client->dev, "Ambient Light Sensor, Rev : %d\n", (ret & BH1780_REVMASK)); @@ -175,11 +203,14 @@ static int __devinit bh1780_probe(struct i2c_client *client, ret = sysfs_create_group(&client->dev.kobj, &bh1780_attr_group); if (ret) - goto err_op_failed; + goto put_regulator; return 0; -err_op_failed: +put_regulator: + regulator_disable(ddata->regulator); + regulator_put(ddata->regulator); +free_ddata: kfree(ddata); return ret; } @@ -215,6 +246,8 @@ static int bh1780_suspend(struct device *dev) if (ret < 0) return ret; + regulator_disable(ddata->regulator); + return 0; } @@ -229,11 +262,16 @@ static int bh1780_resume(struct device *dev) ret = bh1780_write(ddata, BH1780_REG_CONTROL, state, "CONTROL"); + regulator_enable(ddata->regulator); + + ret = bh1780_write(ddata, BH1780_REG_CONTROL, ddata->power_state, + "CONTROL"); if (ret < 0) return ret; return 0; } + static SIMPLE_DEV_PM_OPS(bh1780_pm, bh1780_suspend, bh1780_resume); #define BH1780_PMOPS (&bh1780_pm) #else @@ -252,7 +290,7 @@ static struct i2c_driver bh1780_driver = { .driver = { .name = "bh1780", .pm = BH1780_PMOPS, -}, + }, }; static int __init bh1780_init(void) -- cgit v1.2.3 From 636a0fe85cf5fdd3051ac336285fe65dfd20cafa Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 21 Mar 2011 16:58:25 +0100 Subject: mpcore_wdt: Set correct watchdog rate mpcore_wdt: Use notifiers to modify WD rate mpcore_wdt: Stop watchdog of non-crashing cores mpcore_wdt: don't sleep in atomic context Signed-off-by: Robert Marklund --- drivers/watchdog/mpcore_wdt.c | 99 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 6 deletions(-) diff --git a/drivers/watchdog/mpcore_wdt.c b/drivers/watchdog/mpcore_wdt.c index 4dc31024d26..0603422d316 100644 --- a/drivers/watchdog/mpcore_wdt.c +++ b/drivers/watchdog/mpcore_wdt.c @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include @@ -47,6 +49,8 @@ struct mpcore_wdt { static struct platform_device *mpcore_wdt_dev; static DEFINE_SPINLOCK(wdt_lock); +static DEFINE_PER_CPU(unsigned long, mpcore_wdt_rate); + #define TIMER_MARGIN 60 static int mpcore_margin = TIMER_MARGIN; module_param(mpcore_margin, int, 0); @@ -67,6 +71,8 @@ MODULE_PARM_DESC(mpcore_noboot, "MPcore watchdog action, " "set to 1 to ignore reboots, 0 to reboot (default=" __MODULE_STRING(ONLY_TESTING) ")"); +#define MPCORE_WDT_PERIPHCLK_PRESCALER 2 + /* * This is the interrupt handler. Note that we only use this * in testing mode, so don't actually do a reboot here. @@ -99,9 +105,8 @@ static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt) spin_lock(&wdt_lock); /* Assume prescale is set to 256 */ - count = __raw_readl(wdt->base + TWD_WDOG_COUNTER); - count = (0xFFFFFFFFU - count) * (HZ / 5); - count = (count / 256) * mpcore_margin; + count = per_cpu(mpcore_wdt_rate, smp_processor_id()) / 256; + count = count*mpcore_margin; /* Reload the counter */ writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD); @@ -109,6 +114,56 @@ static void mpcore_wdt_keepalive(struct mpcore_wdt *wdt) spin_unlock(&wdt_lock); } +static void mpcore_wdt_set_rate(unsigned long new_rate) +{ + unsigned long count; + unsigned long long rate_tmp; + unsigned long old_rate; + + spin_lock(&wdt_lock); + old_rate = per_cpu(mpcore_wdt_rate, smp_processor_id()); + per_cpu(mpcore_wdt_rate, smp_processor_id()) = new_rate; + + if (mpcore_wdt_dev) { + struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev); + count = readl(wdt->base + TWD_WDOG_COUNTER); + /* The goal: count = count * (new_rate/old_rate); */ + rate_tmp = (unsigned long long)count * new_rate; + do_div(rate_tmp, old_rate); + count = rate_tmp; + writel(count + wdt->perturb, wdt->base + TWD_WDOG_LOAD); + wdt->perturb = wdt->perturb ? 0 : 1; + } + spin_unlock(&wdt_lock); +} + +static void mpcore_wdt_update_cpu_frequency_on_cpu(void *data) +{ + struct cpufreq_freqs *freq = data; + mpcore_wdt_set_rate((freq->new * 1000) / + MPCORE_WDT_PERIPHCLK_PRESCALER); +} + +static int mpcore_wdt_cpufreq_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct cpufreq_freqs *freq = data; + + if (event == CPUFREQ_RESUMECHANGE || + (event == CPUFREQ_PRECHANGE && freq->new > freq->old) || + (event == CPUFREQ_POSTCHANGE && freq->new < freq->old)) + smp_call_function_single(freq->cpu, + mpcore_wdt_update_cpu_frequency_on_cpu, + freq, 1); + + return 0; +} + +static struct notifier_block mpcore_wdt_cpufreq_notifier_block = { + .notifier_call = mpcore_wdt_cpufreq_notifier, +}; + + static void mpcore_wdt_stop(struct mpcore_wdt *wdt) { spin_lock(&wdt_lock); @@ -143,6 +198,20 @@ static int mpcore_wdt_set_heartbeat(int t) return 0; } +static int mpcore_wdt_stop_notifier(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev); + printk(KERN_INFO "Stopping watchdog on non-crashing core %u\n", + smp_processor_id()); + mpcore_wdt_stop(wdt); + return NOTIFY_STOP; +} + +static struct notifier_block mpcore_wdt_stop_block = { + .notifier_call = mpcore_wdt_stop_notifier, +}; + /* * /dev/watchdog handling */ @@ -150,7 +219,7 @@ static int mpcore_wdt_open(struct inode *inode, struct file *file) { struct mpcore_wdt *wdt = platform_get_drvdata(mpcore_wdt_dev); - if (test_and_set_bit(0, &wdt->timer_alive)) + if (cpumask_test_and_set_cpu(smp_processor_id(), &wdt->timer_alive)) return -EBUSY; if (nowayout) @@ -158,6 +227,9 @@ static int mpcore_wdt_open(struct inode *inode, struct file *file) file->private_data = wdt; + atomic_notifier_chain_register(&crash_percpu_notifier_list, + &mpcore_wdt_stop_block); + /* * Activate timer */ @@ -181,7 +253,7 @@ static int mpcore_wdt_release(struct inode *inode, struct file *file) "unexpected close, not stopping watchdog!\n"); mpcore_wdt_keepalive(wdt); } - clear_bit(0, &wdt->timer_alive); + cpumask_clear_cpu(smp_processor_id(), &wdt->timer_alive); wdt->expect_close = 0; return 0; } @@ -448,16 +520,31 @@ static char banner[] __initdata = KERN_INFO "MPcore Watchdog Timer: 0.1. " static int __init mpcore_wdt_init(void) { + int i; + /* * Check that the margin value is within it's range; * if not reset to the default */ if (mpcore_wdt_set_heartbeat(mpcore_margin)) { mpcore_wdt_set_heartbeat(TIMER_MARGIN); - printk(KERN_INFO "mpcore_margin value must be 0 < mpcore_margin < 65536, using %d\n", + printk(KERN_INFO "mpcore_wdt: mpcore_margin value must be 0 < mpcore_margin < 65536, using %d\n", TIMER_MARGIN); } + cpufreq_register_notifier(&mpcore_wdt_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + for_each_online_cpu(i) + per_cpu(mpcore_wdt_rate, i) = + (cpufreq_get(i) * 1000) / MPCORE_WDT_PERIPHCLK_PRESCALER; + + for_each_online_cpu(i) + printk(KERN_INFO + "mpcore_wdt: rate for core %d is %lu.%02luMHz.\n", i, + per_cpu(mpcore_wdt_rate, i) / 1000000, + (per_cpu(mpcore_wdt_rate, i) / 10000) % 100); + printk(banner, mpcore_noboot, mpcore_margin, nowayout); return platform_driver_register(&mpcore_wdt_driver); -- cgit v1.2.3 From 446a33820a59029017bcd1c669665034165dc5e9 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Tue, 22 Mar 2011 10:14:24 +0100 Subject: amba-pl022: Initialize burstsize from FIFO trigger level amba-pl022: support runtime PM Signed-off-by: Robert Marklund --- drivers/spi/spi-pl022.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 730b4a37b82..856dd06661e 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -924,6 +924,12 @@ static int configure_dma(struct pl022 *pl022) struct dma_async_tx_descriptor *rxdesc; struct dma_async_tx_descriptor *txdesc; + /* DMA burstsize should be same as the FIFO trigger level */ + rx_conf.src_maxburst = pl022->rx_lev_trig ? 1 << + (pl022->rx_lev_trig + 1) : pl022->rx_lev_trig; + tx_conf.dst_maxburst = pl022->tx_lev_trig ? 1 << + (pl022->tx_lev_trig + 1) : pl022->tx_lev_trig; + /* Check that the channels are available */ if (!rxchan || !txchan) return -ENODEV; @@ -1547,6 +1553,7 @@ static void pump_messages(struct work_struct *work) pm_runtime_get_sync(&pl022->adev->dev); amba_vcore_enable(pl022->adev); amba_pclk_enable(pl022->adev); + pm_runtime_get_sync(&pl022->adev->dev); clk_enable(pl022->clk); restore_state(pl022); flush(pl022); @@ -2189,6 +2196,9 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id) pm_runtime_enable(dev); pm_runtime_resume(dev); + pm_runtime_enable(dev); + pm_runtime_resume(dev); + pl022->clk = clk_get(&adev->dev, NULL); if (IS_ERR(pl022->clk)) { status = PTR_ERR(pl022->clk); @@ -2279,6 +2289,7 @@ pl022_remove(struct amba_device *adev) free_irq(adev->irq[0], pl022); clk_disable(pl022->clk); clk_put(pl022->clk); + pm_runtime_disable(&adev->dev); iounmap(pl022->virtbase); amba_release_regions(adev); tasklet_disable(&pl022->pump_transfers); @@ -2365,6 +2376,14 @@ static struct vendor_data vendor_db5500_pl023 = { .loopback = true, }; +static struct vendor_data vendor_db5500_pl023 = { + .fifodepth = 32, + .max_bpw = 32, + .unidir = false, + .extended_cr = true, + .pl023 = true, +}; + static struct amba_id pl022_ids[] = { { /* -- cgit v1.2.3 From 67932bf0def7c2014cea4ca47c0a84e32f3b4d42 Mon Sep 17 00:00:00 2001 From: Per Fransson Date: Fri, 9 Jul 2010 12:14:56 +0200 Subject: arm: allow passing an ELF64 header to elf_check_arch() Signed-off-by: Per Fransson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/2732 Tested-by: Per FRANSSON Reviewed-by: Jonas ABERG Change-Id: I616a21ea86221d179fff1e87174aafa0eeee7f98 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/3181 Tested-by: Mian Yousaf KAUKAB Reviewed-by: Srinidhi KASAGAR Signed-off-by: Mian Yousaf Kaukab Signed-off-by: Lee Jones --- arch/arm/include/asm/elf.h | 4 ++-- arch/arm/kernel/elf.c | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/arch/arm/include/asm/elf.h b/arch/arm/include/asm/elf.h index 0e9ce8d9686..85cf3655914 100644 --- a/arch/arm/include/asm/elf.h +++ b/arch/arm/include/asm/elf.h @@ -96,8 +96,8 @@ struct elf32_hdr; /* * This is used to ensure we don't load something for the wrong architecture. */ -extern int elf_check_arch(const struct elf32_hdr *); -#define elf_check_arch elf_check_arch +extern int arm_elf_check_arch(const struct elf32_hdr *); +#define elf_check_arch(x) arm_elf_check_arch((const struct elf32_hdr *)(x)) #define vmcore_elf64_check_arch(x) (0) diff --git a/arch/arm/kernel/elf.c b/arch/arm/kernel/elf.c index 9b05c6a0dce..ddd49b23365 100644 --- a/arch/arm/kernel/elf.c +++ b/arch/arm/kernel/elf.c @@ -4,11 +4,13 @@ #include #include -int elf_check_arch(const struct elf32_hdr *x) +int arm_elf_check_arch(const struct elf32_hdr *x) { unsigned int eflags; /* Make sure it's an ARM executable */ + if (x->e_ident[EI_CLASS] != ELF_CLASS) + return 0; if (x->e_machine != EM_ARM) return 0; @@ -35,7 +37,7 @@ int elf_check_arch(const struct elf32_hdr *x) } return 1; } -EXPORT_SYMBOL(elf_check_arch); +EXPORT_SYMBOL(arm_elf_check_arch); void elf_set_personality(const struct elf32_hdr *x) { -- cgit v1.2.3 From 6255410bd74c420b4f19294bf89d82e3bcb45319 Mon Sep 17 00:00:00 2001 From: Per Fransson Date: Thu, 27 Jan 2011 14:08:16 +0100 Subject: mpcore_wdt: Stop watchdog of non-crashing cores When kexec'ing to u-boot only the crashing core is used (and will have to be kicked by u-boot). The watchdog of other core has to be stopped before leaving linux. ST-Ericsson ID: - Change-Id: I329630fd8040f8af3e54fc51aa7187217938f0b8 Signed-off-by: Per Fransson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13678 Reviewed-by: QATOOLS Reviewed-by: Srinidhi KASAGAR Reviewed-by: Jonas ABERG Signed-off-by: Lee Jones --- arch/arm/kernel/machine_kexec.c | 1 + include/linux/kexec.h | 1 + kernel/kexec.c | 2 ++ 3 files changed, 4 insertions(+) diff --git a/arch/arm/kernel/machine_kexec.c b/arch/arm/kernel/machine_kexec.c index e59bbd496c3..ae1d73a15d3 100644 --- a/arch/arm/kernel/machine_kexec.c +++ b/arch/arm/kernel/machine_kexec.c @@ -47,6 +47,7 @@ void machine_crash_nonpanic_core(void *unused) printk(KERN_DEBUG "CPU %u will stop doing anything useful since another CPU has crashed\n", smp_processor_id()); crash_save_cpu(®s, smp_processor_id()); + atomic_notifier_call_chain(&crash_percpu_notifier_list, 0, NULL); flush_cache_all(); atomic_dec(&waiting_for_crash_ipi); diff --git a/include/linux/kexec.h b/include/linux/kexec.h index c2478a342cd..76b4db78ec1 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -158,6 +158,7 @@ unsigned long paddr_vmcoreinfo_note(void); extern struct kimage *kexec_image; extern struct kimage *kexec_crash_image; +extern struct atomic_notifier_head crash_percpu_notifier_list; #ifndef kexec_flush_icache_page #define kexec_flush_icache_page(page) diff --git a/kernel/kexec.c b/kernel/kexec.c index 296fbc84d65..cfb421ddae2 100644 --- a/kernel/kexec.c +++ b/kernel/kexec.c @@ -50,6 +50,8 @@ u32 vmcoreinfo_note[VMCOREINFO_NOTE_SIZE/4]; size_t vmcoreinfo_size; size_t vmcoreinfo_max_size = sizeof(vmcoreinfo_data); +ATOMIC_NOTIFIER_HEAD(crash_percpu_notifier_list); + /* Location of the reserved area for the crash kernel */ struct resource crashk_res = { .name = "Crash kernel", -- cgit v1.2.3 From 4ad6fa33259b6ac79659fc9514bda6cb8c180cc7 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 10 Oct 2011 16:29:13 +0200 Subject: mfd: stmpe: Recheck the status to avoid failure stmpe: add stmpe documentation to DocBook Signed-off-by: Robert Marklund --- Documentation/DocBook/Makefile | 3 +- Documentation/DocBook/stmpe.tmpl | 119 +++++++++++++++++++++++++++++++++++++++ drivers/mfd/stmpe.c | 18 +++++- 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 Documentation/DocBook/stmpe.tmpl diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile index 66725a3d30d..3f118e4defb 100644 --- a/Documentation/DocBook/Makefile +++ b/Documentation/DocBook/Makefile @@ -14,7 +14,8 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ genericirq.xml s390-drivers.xml uio-howto.xml scsi.xml \ 80211.xml debugobjects.xml sh.xml regulator.xml \ alsa-driver-api.xml writing-an-alsa-driver.xml \ - tracepoint.xml drm.xml media_api.xml + tracepoint.xml drm.xml media_api.xml \ + stmpe.xml include $(srctree)/Documentation/DocBook/media/Makefile diff --git a/Documentation/DocBook/stmpe.tmpl b/Documentation/DocBook/stmpe.tmpl new file mode 100644 index 00000000000..6759149d6ce --- /dev/null +++ b/Documentation/DocBook/stmpe.tmpl @@ -0,0 +1,119 @@ + + + + + + STMPE IO-Port Expander guide + + + + Rabin + Vincent + +
+ rabin.vincent@stericsson.com +
+
+
+
+ + + 2010 + ST-Ericsson + + + + + Linux standard functions + + + + + + This documentation is free software; you can redistribute + it and/or modify it under the terms of the GNU General Public + License version 2 as published by the Free Software Foundation. + + + + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + + + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, + MA 02111-1307 USA + + + + For more details see the file COPYING in the source + distribution of Linux. + + + +
+ + + + + Introduction + + This documentation describes the driver for STMicroelectronics + STMPExxxx port expander devices. + + + + + Known Bugs And Assumptions + + + + None. + + + None. + + + + + + + + + Public Functions Provided + + List of public interfaces in stmpe driver + +!Edrivers/mfd/stmpe.c + + + + Private Functions + + STMPE Keypad driver + STMPE GPIO driver + +
+ stmpe-keypad.c +!Idrivers/input/keyboard/stmpe-keypad.c +
+
+ stmpe-gpio.c +!Idrivers/gpio/stmpe-gpio.c +
+
+ + + Other Data Structures + + This Section lists some of the Data structure used by the stmpe driver and client drivers. + +!Iinclude/linux/mfd/stmpe.h +!Idrivers/mfd/stmpe.h + +
diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c index 2963689cf45..f9abf20975b 100644 --- a/drivers/mfd/stmpe.c +++ b/drivers/mfd/stmpe.c @@ -674,7 +674,7 @@ static irqreturn_t stmpe_irq(int irq, void *data) ret = stmpe_block_read(stmpe, israddr, num, isr); if (ret < 0) return IRQ_NONE; - +back: for (i = 0; i < num; i++) { int bank = num - i - 1; u8 status = isr[i]; @@ -696,6 +696,22 @@ static irqreturn_t stmpe_irq(int irq, void *data) stmpe_reg_write(stmpe, israddr + i, clear); } + /* + It may happen that on the first status read interrupt + sources may not showup, so read one more time. + */ + ret = stmpe_block_read(stmpe, israddr, num, isr); + if (ret >= 0) { + for (i = 0; i < num; i++) { + int bank = num - i - 1; + u8 status = isr[i]; + + status &= stmpe->ier[bank]; + if (status) + goto back; + } + } + return IRQ_HANDLED; } -- cgit v1.2.3 From 0ca8f61a262ae4a42ecc586545eda9772a727acb Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 10:36:12 +0100 Subject: i2c: nomadic-i2c driver updates for clock and power, busy management Moreover driver documentation is added in DocBook Signed-off-by: Jonas Aaberg Signed-off-by: Mian Yousaf Kaukab --- Documentation/DocBook/i2c.tmpl | 116 +++++++++++++++++++++++++++++++++++++++ drivers/i2c/busses/i2c-nomadik.c | 6 ++ 2 files changed, 122 insertions(+) create mode 100644 Documentation/DocBook/i2c.tmpl diff --git a/Documentation/DocBook/i2c.tmpl b/Documentation/DocBook/i2c.tmpl new file mode 100644 index 00000000000..8a4cb49204e --- /dev/null +++ b/Documentation/DocBook/i2c.tmpl @@ -0,0 +1,116 @@ + + + + + + I2C + + + + Srinidhi + Kasagar + +
+ srinidhi.kasagar@stericsson.com +
+
+
+ + Sachin + Verma + +
+ sachin.verma@st.com +
+
+
+
+ + + 2009-2010 + ST-Ericsson + + + + + Linux standard functions + + + + + + + + This documentation is free software; you can redistribute + it and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + + + + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU General Public License for more details. + + + + You should have received a copy of the GNU General Public + License along with this program; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, + MA 02111-1307 USA + + + + For more details see the file COPYING in the source + distribution of Linux. + + +
+ + + + Introduction + + This Documentation describes the API's provided by the I2C controller Driver. + Since this driver registers the transferfunction with kernel framework, there + are only private functions in this I2C bus driver. This driver currently + works only in master mode and does 7 bit adderssing only. There is no support + for 10 bit addressing. The driver currently supports standard mode (100KHz) + and Fast mode (400KHz) operation. + + + + Known Bugs And Assumptions + + + + None + + + None. + + + + + + + + + Public Functions Provided + + Not Applicable + + + + + Private Functions + + This Section lists the functions used internally by the I2C controller driver. + +!Idrivers/i2c/busses/i2c-nomadik.c + + +
diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index b228e09c5d0..a2bcd986310 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -266,6 +266,9 @@ static int init_hw(struct nmk_i2c_dev *dev) dev->cli.operation = I2C_NO_OPERATION; exit: + clk_disable(dev->clk); + + udelay(I2C_DELAY); return stat; } @@ -633,6 +636,8 @@ static int nmk_i2c_xfer(struct i2c_adapter *i2c_adap, clk_enable(dev->clk); + dev->busy = true; + status = init_hw(dev); if (status) goto out; @@ -1044,6 +1049,7 @@ static struct platform_driver nmk_i2c_driver = { }, .probe = nmk_i2c_probe, .remove = __devexit_p(nmk_i2c_remove), + .suspend = nmk_i2c_suspend, }; static int __init nmk_i2c_init(void) -- cgit v1.2.3 From 6d0f0f319dc99c68296cf1850451dc4bec2093ab Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 10:39:49 +0100 Subject: nmk-i2c: use pm_runtime API Use the pm_runtime API for pins control. ST-Ericsson Linux next: - ST-Ericsson ID: ER323382 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ib847143b96950c25221c6aa25dae541017840677 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/16045 Tested-by: Rabin VINCENT Reviewed-by: Srinidhi KASAGAR Reviewed-by: Jonas ABERG --- drivers/i2c/busses/i2c-nomadik.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index a2bcd986310..9ceca3f295f 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -252,6 +252,8 @@ static int init_hw(struct nmk_i2c_dev *dev) { int stat; + clk_enable(dev->clk); + stat = flush_i2c_fifo(dev); if (stat) goto exit; @@ -432,9 +434,10 @@ static int read_i2c(struct nmk_i2c_dev *dev) } if (timeout == 0) { - /* Controller timed out */ - dev_err(&dev->pdev->dev, "read from slave 0x%x timed out\n", + /* controller has timedout, re-init the h/w */ + dev_err(&dev->pdev->dev, "Read from Slave 0x%x timed out\n", dev->cli.slave_adr); + (void) init_hw(dev); status = -ETIMEDOUT; } return status; @@ -519,9 +522,10 @@ static int write_i2c(struct nmk_i2c_dev *dev) } if (timeout == 0) { - /* Controller timed out */ - dev_err(&dev->pdev->dev, "write to slave 0x%x timed out\n", + /* controller has timedout, re-init the h/w */ + dev_err(&dev->pdev->dev, "Write to slave 0x%x timed out\n", dev->cli.slave_adr); + (void) init_hw(dev); status = -ETIMEDOUT; } -- cgit v1.2.3 From 30fc39abeaf942e135dcbc3e27814c97f6009480 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 11:31:20 +0100 Subject: i2c-nomadik: Add code to retry on timeout failure and set i2c clock to 400KHz It is seen that i2c-nomadik controller randomly stops generating the interrupts leading to a i2c timeout. As a workaround to this problem add retries, to the on going transfer on failure. Since this behaviour does not get changed with a wait for longer duration, the timeout can be reduced to a lower value, leading to a quicker retry attempt. ST-Ericsson ID: ER 325661 Signed-off-by: Virupax Sadashivpetimath Change-Id: Ic9db2b15ff5ce7e656cbbdfeb19270da9e32f9d1 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/16972 Tested-by: Virupax SADASHIVPETIMATH Reviewed-by: Jonas ABERG --- drivers/i2c/busses/i2c-nomadik.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index 9ceca3f295f..775924fe96c 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -252,8 +252,6 @@ static int init_hw(struct nmk_i2c_dev *dev) { int stat; - clk_enable(dev->clk); - stat = flush_i2c_fifo(dev); if (stat) goto exit; @@ -268,8 +266,6 @@ static int init_hw(struct nmk_i2c_dev *dev) dev->cli.operation = I2C_NO_OPERATION; exit: - clk_disable(dev->clk); - udelay(I2C_DELAY); return stat; } -- cgit v1.2.3 From c30372585081d224524c064dc1bc17043732e220 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 10:39:49 +0100 Subject: nmk-i2c: use pm_runtime API Use the pm_runtime API for pins control. ST-Ericsson Linux next: - ST-Ericsson ID: ER323382 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ib847143b96950c25221c6aa25dae541017840677 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/16045 Tested-by: Rabin VINCENT Reviewed-by: Srinidhi KASAGAR Reviewed-by: Jonas ABERG --- drivers/i2c/busses/i2c-nomadik.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index 775924fe96c..8a8eb143479 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -252,6 +252,8 @@ static int init_hw(struct nmk_i2c_dev *dev) { int stat; + clk_enable(dev->clk); + stat = flush_i2c_fifo(dev); if (stat) goto exit; -- cgit v1.2.3 From 2dbfbdfadd4aea8f21cf33f65586d20360f302c4 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 21 Mar 2011 11:31:20 +0100 Subject: i2c-nomadik: Add code to retry on timeout failure and set i2c clock to 400KHz It is seen that i2c-nomadik controller randomly stops generating the interrupts leading to a i2c timeout. As a workaround to this problem add retries, to the on going transfer on failure. Since this behaviour does not get changed with a wait for longer duration, the timeout can be reduced to a lower value, leading to a quicker retry attempt. ST-Ericsson ID: ER 325661 Signed-off-by: Virupax Sadashivpetimath Change-Id: Ic9db2b15ff5ce7e656cbbdfeb19270da9e32f9d1 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/16972 Tested-by: Virupax SADASHIVPETIMATH Reviewed-by: Jonas ABERG --- drivers/i2c/busses/i2c-nomadik.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index 8a8eb143479..775924fe96c 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -252,8 +252,6 @@ static int init_hw(struct nmk_i2c_dev *dev) { int stat; - clk_enable(dev->clk); - stat = flush_i2c_fifo(dev); if (stat) goto exit; -- cgit v1.2.3 From d55c62ef54a8a81d2f82090ae6d13b9077ddbb24 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 23 Mar 2011 15:47:22 +0100 Subject: backlight/leds: add low threshold to pwm backlight/led The intensity of the backlight/led can be varied from a range of max_brightness to zero. Though most, if not all the pwm based backlight/led devices start flickering at lower brightness value. And also for each device there exists a brightness value below which the backlight appears to be turned off though the value is not equal to zero. If the range of brightness for a device is from zero to max_brightness. A graph is plotted for brightness Vs intensity fo the pwm based backlight/led device has to be a linear graph. intensity | / | / | / |/ --------- 0 max_brightness But pratically on measuring the above we note that the intensity of backlight/led goes to zero(OFF) when the value in not zero almost nearing to zero(some x%). so the graph looks like intensity | / | / | / | | ------------ 0 x max_brightness In order to overcome this drawback knowing this x% i.e nothing but the low threshold beyond which the backlight/led is off and will have no effect, the brightness value is being offset by the low threshold value(retaining the linearity of the graph). Now the graph becomes intensity | / | / | / | / ------------- 0 max_brightness With this for each and every digit increment in the brightness from zero there is a change in the intensity of backlight/led. Devices having this behaviour can set the low threshold brightness(lth_brightness) and pass the same as platform data else can have it as zero. ST-Ericsson ID: Task168737 Change-Id: I7198ec89aa69e0c687d329b21f723fd8d5368928 Signed-off-by: Prajadevi H Signed-off-by: Arun Murthy Acked-by: Linus Walleij Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/5231 Reviewed-by: Linus WALLEIJ --- drivers/leds/leds-pwm.c | 8 +++++++- include/linux/leds_pwm.h | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index 666daf77872..775cd67c604 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -27,6 +27,7 @@ struct led_pwm_data { struct led_classdev cdev; struct pwm_device *pwm; unsigned int active_low; + unsigned int lth_brightness; unsigned int period; }; @@ -42,7 +43,10 @@ static void led_pwm_set(struct led_classdev *led_cdev, pwm_config(led_dat->pwm, 0, period); pwm_disable(led_dat->pwm); } else { - pwm_config(led_dat->pwm, brightness * period / max, period); + brightness = led_dat->lth_brightness + (brightness * + (led_dat->period - led_dat->lth_brightness) / max); + pwm_config(led_dat->pwm, brightness, led_dat->period); + pwm_enable(led_dat->pwm); } } @@ -79,6 +83,8 @@ static int led_pwm_probe(struct platform_device *pdev) led_dat->cdev.default_trigger = cur_led->default_trigger; led_dat->active_low = cur_led->active_low; led_dat->period = cur_led->pwm_period_ns; + led_dat->lth_brightness = cur_led->lth_brightness * + (cur_led->pwm_period_ns / cur_led->max_brightness); led_dat->cdev.brightness_set = led_pwm_set; led_dat->cdev.brightness = LED_OFF; led_dat->cdev.max_brightness = cur_led->max_brightness; diff --git a/include/linux/leds_pwm.h b/include/linux/leds_pwm.h index 33a07116748..9c5eab6e086 100644 --- a/include/linux/leds_pwm.h +++ b/include/linux/leds_pwm.h @@ -11,6 +11,7 @@ struct led_pwm { u8 active_low; unsigned max_brightness; unsigned pwm_period_ns; + unsigned int lth_brightness; }; struct led_pwm_platform_data { -- cgit v1.2.3 From cbfd7f85db389a5987d9829bf10a67440a4f280b Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 11 Oct 2011 09:09:06 +0200 Subject: abx500: big merge from GLK 2.6.35 Signed-off-by: Philippe Langlais --- arch/arm/mach-ux500/include/mach/ab8500_gpadc.h | 36 + drivers/hwmon/Kconfig | 26 + drivers/hwmon/Makefile | 2 + drivers/hwmon/ab8500.c | 715 +++++++ drivers/hwmon/db8500.c | 403 ++++ drivers/mfd/Kconfig | 26 +- drivers/mfd/Makefile | 2 + drivers/mfd/ab5500-core.c | 2158 +++++++++++++++++++++ drivers/mfd/ab8500-core.c | 3 + drivers/mfd/ab8500-debugfs.c | 230 ++- drivers/mfd/ab8500-denc.c | 515 +++++ drivers/mfd/ab8500-gpadc.c | 12 +- drivers/mfd/ab8500-sysctrl.c | 22 + drivers/misc/ab8500-pwm.c | 45 +- drivers/power/Kconfig | 13 + drivers/power/Makefile | 1 + drivers/power/ab8500_btemp.c | 1080 +++++++++++ drivers/power/ab8500_chargalg.c | 1833 ++++++++++++++++++ drivers/power/ab8500_charger.c | 2368 +++++++++++++++++++++++ drivers/power/ab8500_fg.c | 1859 ++++++++++++++++++ include/linux/mfd/ab8500.h | 20 +- include/linux/mfd/ab8500/bm.h | 456 +++++ include/linux/mfd/ab8500/denc-regs.h | 357 ++++ include/linux/mfd/ab8500/denc.h | 82 + include/linux/mfd/ab8500/gpadc.h | 2 +- include/linux/mfd/ab8500/ux500_chargalg.h | 38 + include/linux/mfd/abx500.h | 58 +- 27 files changed, 12341 insertions(+), 21 deletions(-) create mode 100644 arch/arm/mach-ux500/include/mach/ab8500_gpadc.h create mode 100644 drivers/hwmon/ab8500.c create mode 100755 drivers/hwmon/db8500.c create mode 100755 drivers/mfd/ab5500-core.c create mode 100644 drivers/mfd/ab8500-denc.c create mode 100644 drivers/power/ab8500_btemp.c create mode 100644 drivers/power/ab8500_chargalg.c create mode 100644 drivers/power/ab8500_charger.c create mode 100644 drivers/power/ab8500_fg.c create mode 100644 include/linux/mfd/ab8500/bm.h create mode 100644 include/linux/mfd/ab8500/denc-regs.h create mode 100644 include/linux/mfd/ab8500/denc.h create mode 100644 include/linux/mfd/ab8500/ux500_chargalg.h diff --git a/arch/arm/mach-ux500/include/mach/ab8500_gpadc.h b/arch/arm/mach-ux500/include/mach/ab8500_gpadc.h new file mode 100644 index 00000000000..4289dcfc0aa --- /dev/null +++ b/arch/arm/mach-ux500/include/mach/ab8500_gpadc.h @@ -0,0 +1,36 @@ +/* + * ab8500_gpadc.c - AB8500 GPADC Driver + * + * Copyright (C) 2010 ST-Ericsson SA + * Licensed under GPLv2. + * + * Author: Arun R Murthy + */ + +#ifndef _AB8500_GPADC_H +#define _Ab8500_GPADC_H + +/* GPADC source: From datasheer(ADCSwSel[4:0] in GPADCCtrl2) */ +#define BAT_CTRL 0x01 +#define ACC_DETECT1 0x04 +#define ACC_DETECT2 0x05 +#define MAIN_BAT_V 0x08 +#define BK_BAT_V 0x0C +#define VBUS_V 0x09 +#define MAIN_CHARGER_V 0x03 +#define MAIN_CHARGER_C 0x0A +#define USB_CHARGER_C 0x0B +#define DIE_TEMP 0x0D +#define BTEMP_BALL 0x02 + +struct ab8500_gpadc_device_info { + struct completion ab8500_gpadc_complete; + struct mutex ab8500_gpadc_lock; +#if defined(CONFIG_REGULATOR) + struct regulator *regu; +#endif +}; + +int ab8500_gpadc_conversion(int input); + +#endif /* _AB8500_GPADC_H */ diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 0b62c3c6b7c..8ae4cc3b94e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -39,6 +39,32 @@ config HWMON_DEBUG_CHIP comment "Native drivers" +config SENSORS_AB8500 + tristate "AB8500 thermal monitoring" + depends on AB8500_CORE + default n + help + If you say yes here you get support for the thermal sensor part + of the AB8500 chip. The driver includes thermal management for + AB8500 die and two GPADC channels. The GPADC channel are preferably + used to access sensors outside the AB8500 chip. + + This driver can also be built as a module. If so, the module + will be called ab8500-temp. + +config SENSORS_DB8500 + tristate "DB8500 thermal monitoring" + depends on UX500_SOC_DB8500 + default n + help + If you say yes here you get support for the thermal sensor part + of the DB8500 chip. The driver includes thermal management for + DB8500 die. + + This driver can also be built as a module. If so, the module + will be called db8500_temp. + + config SENSORS_ABITUGURU tristate "Abit uGuru (rev 1 & 2)" depends on X86 && DMI && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 3c9ccefea79..b191234b3f2 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -19,6 +19,8 @@ obj-$(CONFIG_SENSORS_W83795) += w83795.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o +obj-$(CONFIG_SENSORS_AB8500) += ab8500.o +obj-$(CONFIG_SENSORS_DB8500) += db8500.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o obj-$(CONFIG_SENSORS_AD7414) += ad7414.o diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c new file mode 100644 index 00000000000..640a6638db4 --- /dev/null +++ b/drivers/hwmon/ab8500.c @@ -0,0 +1,715 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Martin Persson for + * ST-Ericsson. + * License terms: GNU Gereral Public License (GPL) version 2 + * + * Note: + * + * AB8500 does not provide auto ADC, so to monitor the required + * temperatures, a periodic work is used. It is more important + * to not wake up the CPU than to perform this job, hence the use + * of a deferred delay. + * + * A deferred delay for thermal monitor is considered safe because: + * If the chip gets too hot during a sleep state it's most likely + * due to external factors, such as the surrounding temperature. + * I.e. no SW decisions will make any difference. + * + * If/when the AB8500 thermal warning temperature is reached (threshold + * cannot be changed by SW), an interrupt is set and the driver + * notifies user space via a sysfs event. If a shut down is not + * triggered by user space within a certain time frame, + * pm_power off is called. + * + * If/when AB8500 thermal shutdown temperature is reached a hardware + * shutdown of the AB8500 will occur. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * If AB8500 warm interrupt is set, user space will be notified. + * If user space doesn't shut down the platform within this time + * frame, this driver will. Time unit is ms. + */ +#define DEFAULT_POWER_OFF_DELAY 10000 + +#define NUM_SENSORS 4 + +/* The driver monitors GPADC - ADC_AUX1 and ADC_AUX2 */ +#define NUM_MONITORED_SENSORS 2 + +struct ab8500_temp { + struct platform_device *pdev; + struct device *hwmon_dev; + struct ab8500_gpadc *gpadc; + u8 gpadc_addr[NUM_SENSORS]; + unsigned long min[NUM_SENSORS]; + unsigned long max[NUM_SENSORS]; + unsigned long max_hyst[NUM_SENSORS]; + unsigned long crit[NUM_SENSORS]; + unsigned long min_alarm[NUM_SENSORS]; + unsigned long max_alarm[NUM_SENSORS]; + unsigned long max_hyst_alarm[NUM_SENSORS]; + unsigned long crit_alarm[NUM_SENSORS]; + struct delayed_work work; + struct delayed_work power_off_work; + struct mutex lock; + /* Delay (ms) between temperature readings */ + unsigned long gpadc_monitor_delay; + /* Delay (ms) before power off */ + unsigned long power_off_delay; +}; + +/* + * Thresholds are considered inactive if set to 0. + * To avoid confusion for user space applications, + * the temp monitor delay is set to 0 if all thresholds + * are 0. + */ +static bool find_active_thresholds(struct ab8500_temp *data) +{ + int i; + for (i = 0; i < NUM_MONITORED_SENSORS; i++) + if (data->max[i] != 0 || data->max_hyst[i] != 0 + || data->min[i] != 0) + return true; + + dev_dbg(&data->pdev->dev, "No active thresholds," + "cancel deferred job (if it exists)" + "and reset temp monitor delay\n"); + mutex_lock(&data->lock); + data->gpadc_monitor_delay = 0; + cancel_delayed_work_sync(&data->work); + mutex_unlock(&data->lock); + return false; +} + +static void thermal_power_off(struct work_struct *work) +{ + struct ab8500_temp *data = container_of(work, struct ab8500_temp, + power_off_work.work); + + dev_warn(&data->pdev->dev, "Power off due to AB8500 thermal warning\n"); + pm_power_off(); +} + +static void gpadc_monitor(struct work_struct *work) +{ + unsigned long delay_in_jiffies; + int val, i, ret; + /* Container for alarm node name */ + char alarm_node[30]; + + bool updated_min_alarm = false; + bool updated_max_alarm = false; + bool updated_max_hyst_alarm = false; + struct ab8500_temp *data = container_of(work, struct ab8500_temp, + work.work); + + for (i = 0; i < NUM_MONITORED_SENSORS; i++) { + /* Thresholds are considered inactive if set to 0 */ + if (data->max[i] == 0 && data->max_hyst[i] == 0 + && data->min[i] == 0) + continue; + + val = ab8500_gpadc_convert(data->gpadc, data->gpadc_addr[i]); + if (val < 0) { + dev_err(&data->pdev->dev, "GPADC read failed\n"); + continue; + } + + mutex_lock(&data->lock); + if (data->min[i] != 0) { + if (val < data->min[i]) { + if (data->min_alarm[i] == 0) { + data->min_alarm[i] = 1; + updated_min_alarm = true; + } + } else { + if (data->min_alarm[i] == 1) { + data->min_alarm[i] = 0; + updated_min_alarm = true; + } + } + + } + if (data->max[i] != 0) { + if (val > data->max[i]) { + if (data->max_alarm[i] == 0) { + data->max_alarm[i] = 1; + updated_max_alarm = true; + } + } else { + if (data->max_alarm[i] == 1) { + data->max_alarm[i] = 0; + updated_max_alarm = true; + } + } + + } + if (data->max_hyst[i] != 0) { + if (val > data->max_hyst[i]) { + if (data->max_hyst_alarm[i] == 0) { + data->max_hyst_alarm[i] = 1; + updated_max_hyst_alarm = true; + } + } else { + if (data->max_hyst_alarm[i] == 1) { + data->max_hyst_alarm[i] = 0; + updated_max_hyst_alarm = true; + } + } + } + mutex_unlock(&data->lock); + + /* hwmon attr index starts at 1, thus "i+1" below */ + if (updated_min_alarm) { + ret = snprintf(alarm_node, 16, "temp%d_min_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + if (updated_max_alarm) { + ret = snprintf(alarm_node, 16, "temp%d_max_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + if (updated_max_hyst_alarm) { + ret = snprintf(alarm_node, 21, "temp%d_max_hyst_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + } + delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); + schedule_delayed_work(&data->work, delay_in_jiffies); +} + +static inline void gpadc_monitor_exit(struct platform_device *pdev) +{ + struct ab8500_temp *data = platform_get_drvdata(pdev); + cancel_delayed_work_sync(&data->work); +} + +static ssize_t set_temp_monitor_delay(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int res; + unsigned long delay_in_jiffies, delay_in_s; + struct ab8500_temp *data = dev_get_drvdata(dev); + + if (!find_active_thresholds(data)) { + dev_dbg(dev, "No thresholds to monitor, disregarding delay\n"); + return count; + } + res = strict_strtoul(buf, 10, &delay_in_s); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->gpadc_monitor_delay = delay_in_s * 1000; + mutex_unlock(&data->lock); + delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); + schedule_delayed_work(&data->work, delay_in_jiffies); + + return count; +} + +static ssize_t set_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int res; + unsigned long delay_in_jiffies, delay_in_s; + struct ab8500_temp *data = dev_get_drvdata(dev); + + res = strict_strtoul(buf, 10, &delay_in_s); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->power_off_delay = delay_in_s * 1000; + mutex_unlock(&data->lock); + delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); + + return count; +} + +static ssize_t show_temp_monitor_delay(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + /* return time in s, not ms */ + return sprintf(buf, "%lu\n", (data->gpadc_monitor_delay) / 1000); +} + +static ssize_t show_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + /* return time in s, not ms */ + return sprintf(buf, "%lu\n", (data->power_off_delay) / 1000); +} + +/* HWMON sysfs interface */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + /* + * To avoid confusion between sensor label and chip name, the function + * "show_label" is not used to return the chip name. + */ + return sprintf(buf, "ab8500\n"); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + char *name; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int index = attr->index; + + /* + * Make sure these labels correspond to the attribute indexes + * used when calling SENSOR_DEVICE_ATRR. + * Temperature sensors outside ab8500 (read via GPADC) are marked + * with prefix ext_ + */ + switch (index) { + case 1: + name = "ext_rtc_xtal"; + break; + case 2: + name = "ext_db8500"; + break; + case 3: + name = "battery"; + break; + case 4: + name = "ab8500"; + break; + default: + return -EINVAL; + } + return sprintf(buf, "%s\n", name); +} + +static ssize_t show_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int val; + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + u8 gpadc_addr = data->gpadc_addr[attr->index - 1]; + + val = ab8500_gpadc_convert(data->gpadc, gpadc_addr); + if (val < 0) + dev_err(&data->pdev->dev, "GPADC read failed\n"); + + return sprintf(buf, "%d\n", val); +} + +/* set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->min_alarm[attr->index - 1] = 0; + + data->min[attr->index - 1] = val; + mutex_unlock(&data->lock); + if (val == 0) + (void) find_active_thresholds(data); + + return count; +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->max_alarm[attr->index - 1] = 0; + + data->max[attr->index - 1] = val; + mutex_unlock(&data->lock); + if (val == 0) + (void) find_active_thresholds(data); + + return count; +} + +static ssize_t set_max_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->max_hyst_alarm[attr->index - 1] = 0; + + data->max_hyst[attr->index - 1] = val; + mutex_unlock(&data->lock); + if (val == 0) + (void) find_active_thresholds(data); + + return count; +} + +/* + * show functions (RO nodes) + * Notice that min/max/max_hyst refer to millivolts and not millidegrees + */ +static ssize_t show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->min[attr->index - 1]); +} + +static ssize_t show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max[attr->index - 1]); +} + +static ssize_t show_max_hyst(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_hyst[attr->index - 1]); +} + +/* Alarms */ +static ssize_t show_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->min_alarm[attr->index - 1]); +} + +static ssize_t show_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_alarm[attr->index - 1]); +} + +static ssize_t show_max_hyst_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_hyst_alarm[attr->index - 1]); +} + +static ssize_t show_crit_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct ab8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->crit_alarm[attr->index - 1]); +} + +/*These node are not included in the kernel hwmon sysfs interface */ +static SENSOR_DEVICE_ATTR(temp_monitor_delay, S_IRUGO | S_IWUSR, + show_temp_monitor_delay, set_temp_monitor_delay, 0); +static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, + show_temp_power_off_delay, + set_temp_power_off_delay, 0); + +/* Chip name, required by hwmon*/ +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); + +/* GPADC - ADC_AUX1 */ +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 1); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 1); + +/* GPADC - ADC_AUX2 */ +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min, 2); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max, 2); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 2); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_min_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_max_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 2); + +/* GPADC - BTEMP_BALL */ +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 3); + +/* AB8500 */ +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, + show_crit_alarm, NULL, 4); + +static struct attribute *ab8500_temp_attributes[] = { + &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, + &sensor_dev_attr_temp_monitor_delay.dev_attr.attr, + &sensor_dev_attr_name.dev_attr.attr, + /* GPADC - ADC_AUX1 */ + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst_alarm.dev_attr.attr, + /* GPADC - ADC_AUX2 */ + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst_alarm.dev_attr.attr, + /* GPADC - BTEMP_BALL */ + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + /* AB8500 */ + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group ab8500_temp_group = { + .attrs = ab8500_temp_attributes, +}; + +static irqreturn_t ab8500_temp_irq_handler(int irq, void *irq_data) +{ + unsigned long delay_in_jiffies; + struct platform_device *pdev = irq_data; + struct ab8500_temp *data = platform_get_drvdata(pdev); + + /* + * Make sure the magic numbers below corresponds to the node + * used for AB8500 thermal warning from HW. + */ + mutex_lock(&data->lock); + data->crit_alarm[3] = 1; + mutex_unlock(&data->lock); + sysfs_notify(&pdev->dev.kobj, NULL, "temp4_crit_alarm"); + dev_info(&pdev->dev, "AB8500 thermal warning, power off in %lu s\n", + data->power_off_delay); + delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); + schedule_delayed_work(&data->power_off_work, delay_in_jiffies); + return IRQ_HANDLED; +} + +static int setup_irqs(struct platform_device *pdev) +{ + int ret; + int irq = platform_get_irq_byname(pdev, "AB8500_TEMP_WARM"); + + if (irq < 0) + dev_err(&pdev->dev, "Get irq by name failed\n"); + + ret = request_threaded_irq(irq, NULL, ab8500_temp_irq_handler, + IRQF_NO_SUSPEND, "ab8500-temp", pdev); + if (ret < 0) + dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); + + return ret; +} + +static int __devinit ab8500_temp_probe(struct platform_device *pdev) +{ + struct ab8500_temp *data; + int err; + + data = kzalloc(sizeof(struct ab8500_temp), GFP_KERNEL); + if (!data) + return -ENOMEM; + + err = setup_irqs(pdev); + if (err < 0) + goto exit; + + data->gpadc = ab8500_gpadc_get(); + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit; + } + + INIT_DELAYED_WORK_DEFERRABLE(&data->work, gpadc_monitor); + INIT_DELAYED_WORK(&data->power_off_work, thermal_power_off); + + /* + * Setup HW defined data. + * + * Reference hardware (HREF): + * + * GPADC - ADC_AUX1, connected to NTC R2148 next to RTC_XTAL on HREF + * GPADC - ADC_AUX2, connected to NTC R2150 near DB8500 on HREF + * Hence, temp#_min/max/max_hyst refer to millivolts and not + * millidegrees + * + * HREF HW does not support reading AB8500 temperature. BUT an + * AB8500 IRQ will be launched if die crit temp limit is reached. + * + * Also: + * Battery temperature thresholds will not be exposed via hwmon. + * + * Make sure indexes correspond to the attribute indexes + * used when calling SENSOR_DEVICE_ATRR + */ + data->gpadc_addr[0] = ADC_AUX1; + data->gpadc_addr[1] = ADC_AUX2; + data->gpadc_addr[2] = BTEMP_BALL; + + mutex_init(&data->lock); + data->pdev = pdev; + data->power_off_delay = DEFAULT_POWER_OFF_DELAY; + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &ab8500_temp_group); + if (err < 0) { + dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); + goto exit_platform_data; + } + + return 0; + +exit_platform_data: + platform_set_drvdata(pdev, NULL); +exit: + kfree(data); + return err; +} + +static int __devexit ab8500_temp_remove(struct platform_device *pdev) +{ + struct ab8500_temp *data = platform_get_drvdata(pdev); + + gpadc_monitor_exit(pdev); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &ab8500_temp_group); + platform_set_drvdata(pdev, NULL); + kfree(data); + return 0; +} + +/* No action required in suspend/resume, thus the lack of functions */ +static struct platform_driver ab8500_temp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ab8500-temp", + }, + .probe = ab8500_temp_probe, + .remove = __devexit_p(ab8500_temp_remove), +}; + +static int __init ab8500_temp_init(void) +{ + return platform_driver_register(&ab8500_temp_driver); +} + +static void __exit ab8500_temp_exit(void) +{ + platform_driver_unregister(&ab8500_temp_driver); +} + +MODULE_AUTHOR("Martin Persson "); +MODULE_DESCRIPTION("AB8500 temperature driver"); +MODULE_LICENSE("GPL"); + +module_init(ab8500_temp_init) +module_exit(ab8500_temp_exit) diff --git a/drivers/hwmon/db8500.c b/drivers/hwmon/db8500.c new file mode 100755 index 00000000000..09630f2f707 --- /dev/null +++ b/drivers/hwmon/db8500.c @@ -0,0 +1,403 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + * + * Author: WenHai Fang for + * ST-Ericsson. + * License terms: GNU Gereral Public License (GPL) version 2 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * If DB8500 warm interrupt is set, user space will be notified. + * If user space doesn't shut down the platform within this time + * frame, this driver will. Time unit is ms. + */ +#define DEFAULT_POWER_OFF_DELAY 10000 + +/* + * Default measure period to 0xFF x cycle32k + */ +#define DEFAULT_MEASURE_TIME 0xFF + +/* This driver monitors DB thermal*/ +#define NUM_SENSORS 1 + +struct db8500_temp { + struct platform_device *pdev; + struct device *hwmon_dev; + unsigned char min[NUM_SENSORS]; + unsigned char max[NUM_SENSORS]; + unsigned char crit[NUM_SENSORS]; + unsigned short measure_time; + struct delayed_work power_off_work; + struct mutex lock; + /* Delay (ms) before power off */ + unsigned long power_off_delay; +}; + +static void thermal_power_off(struct work_struct *work) +{ + struct db8500_temp *data = container_of(work, struct db8500_temp, + power_off_work.work); + + dev_warn(&data->pdev->dev, "Power off due to DB8500 thermal warning\n"); + pm_power_off(); +} + +static ssize_t set_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int res; + unsigned long delay_in_s; + struct db8500_temp *data = dev_get_drvdata(dev); + + res = strict_strtoul(buf, 10, &delay_in_s); + if (res < 0) { + dev_warn(&data->pdev->dev, "Set power_off_delay wrong\n"); + return res; + } + + mutex_lock(&data->lock); + data->power_off_delay = delay_in_s * 1000; + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct db8500_temp *data = dev_get_drvdata(dev); + /* return time in s, not ms */ + return sprintf(buf, "%lu\n", (data->power_off_delay) / 1000); +} + +/* HWMON sysfs interface */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "db8500\n"); +} + +/* set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val &= 0xFF; + if (val > data->max[attr->index - 1]) + val = data->max[attr->index - 1]; + + data->min[attr->index - 1] = val; + + (void)prcmu_config_hotmon(data->min[attr->index - 1], + data->max[attr->index - 1]); + mutex_unlock(&data->lock); + return count; +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val &= 0xFF; + if (val < data->min[attr->index - 1]) + val = data->min[attr->index - 1]; + + data->max[attr->index - 1] = val; + + (void)prcmu_config_hotmon(data->min[attr->index - 1], + data->max[attr->index - 1]); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_crit(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val = (val > 255) ? 0xFF : val; + + data->crit[attr->index - 1] = val; + (void)prcmu_config_hotdog(data->crit[attr->index - 1]); + mutex_unlock(&data->lock); + + return count; +} + +/* set functions (W nodes) */ +static ssize_t start_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct db8500_temp *data = dev_get_drvdata(dev); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->measure_time = val & 0xFFFF; + mutex_unlock(&data->lock); + + (void)prcmu_start_temp_sense(data->measure_time); + dev_dbg(&data->pdev->dev, "DB8500 thermal start measurement\n"); + return count; +} + +static ssize_t stop_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct db8500_temp *data = dev_get_drvdata(dev); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + (void)prcmu_stop_temp_sense(); + dev_dbg(&data->pdev->dev, "DB8500 thermal stop measurement\n"); + + return count; +} + +/* + * show functions (RO nodes) + * Notice that min/max/max_hyst refer to millivolts and not millidegrees + */ +static ssize_t show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->min[attr->index - 1]); +} + +static ssize_t show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->max[attr->index - 1]); +} + +static ssize_t show_crit(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->crit[attr->index - 1]); +} + + +/*These node are not included in the kernel hwmon sysfs interface */ +static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, + show_temp_power_off_delay, + set_temp_power_off_delay, 0); + +/* Chip name, required by hwmon*/ +static SENSOR_DEVICE_ATTR(temp1_start, S_IWUSR, NULL, start_temp, 0); +static SENSOR_DEVICE_ATTR(temp1_stop, S_IWUSR, NULL, stop_temp, 0); +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, + show_crit, set_crit, 1); + +static struct attribute *db8500_temp_attributes[] = { + &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, + &sensor_dev_attr_name.dev_attr.attr, + &sensor_dev_attr_temp1_start.dev_attr.attr, + &sensor_dev_attr_temp1_stop.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + NULL +}; + +static const struct attribute_group db8500_temp_group = { + .attrs = db8500_temp_attributes, +}; + +static irqreturn_t prcmu_hotmon_low_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + sysfs_notify(&pdev->dev.kobj, NULL, "prcmu_hotmon_low alarm"); + dev_dbg(&pdev->dev, "DB8500 thermal low warning\n"); + return IRQ_HANDLED; +} + +static irqreturn_t prcmu_hotmon_high_irq_handler(int irq, void *irq_data) +{ + unsigned long delay_in_jiffies; + struct platform_device *pdev = irq_data; + struct db8500_temp *data = platform_get_drvdata(pdev); + + sysfs_notify(&pdev->dev.kobj, NULL, "prcmu_hotmon_high alarm"); + dev_dbg(&pdev->dev, "DB8500 thermal warning, power off in %lu s\n", + (data->power_off_delay) / 1000); + delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); + schedule_delayed_work(&data->power_off_work, delay_in_jiffies); + return IRQ_HANDLED; +} + +static int __devinit db8500_temp_probe(struct platform_device *pdev) +{ + struct db8500_temp *data; + int err = 0, i; + int irq; + + dev_dbg(&pdev->dev, "db8500_temp: Function db8500_temp_probe.\n"); + + data = kzalloc(sizeof(struct db8500_temp), GFP_KERNEL); + if (!data) + return -ENOMEM; + + irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); + if (irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed\n"); + goto exit; + } + + err = request_threaded_irq(irq, NULL, prcmu_hotmon_low_irq_handler, + IRQF_NO_SUSPEND, "db8500_temp_low", pdev); + if (err < 0) { + dev_err(&pdev->dev, "db8500: Failed allocate HOTMON_LOW.\n"); + goto exit; + } else { + dev_dbg(&pdev->dev, "db8500: Succeed allocate HOTMON_LOW.\n"); + } + + irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); + if (irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed\n"); + goto exit; + } + + err = request_threaded_irq(irq, NULL, prcmu_hotmon_high_irq_handler, + IRQF_NO_SUSPEND, "db8500_temp_high", pdev); + if (err < 0) { + dev_err(&pdev->dev, "db8500: Failed allocate HOTMON_HIGH.\n"); + goto exit; + } else { + dev_dbg(&pdev->dev, "db8500: Succeed allocate HOTMON_HIGH.\n"); + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit; + } + + for (i = 0; i < NUM_SENSORS; i++) { + data->min[i] = 0; + data->max[i] = 0xFF; + } + + mutex_init(&data->lock); + INIT_DELAYED_WORK(&data->power_off_work, thermal_power_off); + + data->pdev = pdev; + data->power_off_delay = DEFAULT_POWER_OFF_DELAY; + data->measure_time = DEFAULT_MEASURE_TIME; + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &db8500_temp_group); + if (err < 0) { + dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); + goto exit_platform_data; + } + + return 0; + +exit_platform_data: + platform_set_drvdata(pdev, NULL); +exit: + kfree(data); + return err; +} + +static int __devexit db8500_temp_remove(struct platform_device *pdev) +{ + struct db8500_temp *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &db8500_temp_group); + platform_set_drvdata(pdev, NULL); + kfree(data); + return 0; +} + +/* No action required in suspend/resume, thus the lack of functions */ +static struct platform_driver db8500_temp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "db8500_temp", + }, + .probe = db8500_temp_probe, + .remove = __devexit_p(db8500_temp_remove), +}; + +static int __init db8500_temp_init(void) +{ + return platform_driver_register(&db8500_temp_driver); +} + +static void __exit db8500_temp_exit(void) +{ + platform_driver_unregister(&db8500_temp_driver); +} + +MODULE_AUTHOR("WenHai Fang "); +MODULE_DESCRIPTION("DB8500 temperature driver"); +MODULE_LICENSE("GPL"); + +module_init(db8500_temp_init) +module_exit(db8500_temp_exit) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 21574bdf485..c65235b5312 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -332,6 +332,20 @@ config MFD_TC6393XB help Support for Toshiba Mobile IO Controller TC6393XB +config AB5500_CORE + bool "ST-Ericsson AB5500 Mixed Signal Circuit core functions" + select MFD_CORE + depends on GENERIC_HARDIRQS && ABX500_CORE + help + Select this to enable the AB5500 Mixed Signal IC core + functionality. This connects to a AB5500 chip on the I2C bus via + the Power and Reset Management Unit (PRCMU). It exposes a number + of symbols needed for dependent devices to read and write + registers and subscribe to events from this multi-functional IC. + This is needed to use other features of the AB5500 such as + battery-backed RTC, charging control, Regulators, LEDs, vibrator, + system power and temperature, power management and ALSA sound. + config PMIC_DA903X bool "Dialog Semiconductor DA9030/DA9034 PMIC Support" depends on I2C=y @@ -597,6 +611,14 @@ config AB8500_I2C_CORE the I2C bus is connected to the Power Reset and Mangagement Unit, PRCMU. +config AB8500_DENC + bool "AB8500_DENC driver support(CVBS)" + depends on AB8500_CORE + help + Select this option to add driver support for analog TV out through + AB8500. + + config AB8500_DEBUG bool "Enable debug info via debugfs" depends on AB8500_CORE && DEBUG_FS @@ -607,10 +629,10 @@ config AB8500_DEBUG config AB8500_GPADC bool "AB8500 GPADC driver" - depends on AB8500_CORE && REGULATOR_AB8500 + depends on AB8500_CORE default y help - AB8500 GPADC driver used to convert Acc and battery/ac/usb voltage + AB8500 GPADC driver used to convert Acc and battery/ac/usb voltage. config AB3550_CORE bool "ST-Ericsson AB3550 Mixed Signal Circuit core functions" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index c58020303d1..0bf175a91e4 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -2,6 +2,7 @@ # Makefile for multifunction miscellaneous devices # +obj-$(CONFIG_AB5500_CORE) += ab5500-core.o 88pm860x-objs := 88pm860x-core.o 88pm860x-i2c.o obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o obj-$(CONFIG_MFD_SM501) += sm501.o @@ -82,6 +83,7 @@ obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o obj-$(CONFIG_AB3550_CORE) += ab3550-core.o obj-$(CONFIG_AB8500_CORE) += ab8500-core.o ab8500-sysctrl.o obj-$(CONFIG_AB8500_DEBUG) += ab8500-debugfs.o +obj-$(CONFIG_AB8500_DENC) += ab8500-denc.o obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o # ab8500-i2c need to come after db8500-prcmu (which provides the channel) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c new file mode 100755 index 00000000000..a4fa61d3d9b --- /dev/null +++ b/drivers/mfd/ab5500-core.c @@ -0,0 +1,2158 @@ +/* + * Copyright (C) 2007-2010 ST-Ericsson + * License terms: GNU General Public License (GPL) version 2 + * Low-level core for exclusive access to the AB5500 IC on the I2C bus + * and some basic chip-configuration. + * Author: Bengt Jonsson + * Author: Mattias Nilsson + * Author: Mattias Wallin + * Author: Rickard Andersson + * Author: Karl Komierowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AB5500_NAME_STRING "ab5500" +#define AB5500_ID_FORMAT_STRING "AB5500 %s" +#define AB5500_NUM_EVENT_REG 23 + +/* These are the only registers inside AB5500 used in this main file */ + +/* Read/write operation values. */ +#define AB5500_PERM_RD (0x01) +#define AB5500_PERM_WR (0x02) + +/* Read/write permissions. */ +#define AB5500_PERM_RO (AB5500_PERM_RD) +#define AB5500_PERM_RW (AB5500_PERM_RD | AB5500_PERM_WR) + +#define AB5500_MASK_BASE (0x60) +#define AB5500_MASK_END (0x79) +#define AB5500_CHIP_ID (0x20) + +/** + * struct ab5500 + * @access_mutex: lock out concurrent accesses to the AB registers + * @dev: a pointer to the device struct for this chip driver + * @ab5500_irq: the analog baseband irq + * @irq_base: the platform configuration irq base for subdevices + * @chip_name: name of this chip variant + * @chip_id: 8 bit chip ID for this chip variant + * @mask_work: a worker for writing to mask registers + * @event_lock: a lock to protect the event_mask + * @abb_events: a local bit mask of the prcmu wakeup events + * @event_mask: a local copy of the mask event registers + * @last_event_mask: a copy of the last event_mask written to hardware + * @startup_events: a copy of the first reading of the event registers + * @startup_events_read: whether the first events have been read + */ +struct ab5500 { + struct mutex access_mutex; + struct device *dev; + unsigned int ab5500_irq; + unsigned int irq_base; + char chip_name[32]; + u8 chip_id; + struct work_struct mask_work; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) + struct work_struct irq_work; +#endif + spinlock_t event_lock; + u32 abb_events; + u8 event_mask[AB5500_NUM_EVENT_REG]; + u8 last_event_mask[AB5500_NUM_EVENT_REG]; + u8 startup_events[AB5500_NUM_EVENT_REG]; + bool startup_events_read; +#ifdef CONFIG_DEBUG_FS + unsigned int debug_bank; + unsigned int debug_address; +#endif +}; + +/** + * struct ab5500_bank + * @slave_addr: I2C slave_addr found in AB5500 specification + * @name: Documentation name of the bank. For reference + */ +struct ab5500_bank { + u8 slave_addr; + const char *name; +}; + +static const struct ab5500_bank bankinfo[AB5500_NUM_BANKS] = { + [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = {0x4A, "VIT_IO_I2C_CLK_TST_OTP"}, + [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = {0x4B, "VDDDIG_IO_I2C_CLK_TST"}, + [AB5500_BANK_VDENC] = {0x06, "VDENC"}, + [AB5500_BANK_SIM_USBSIM] = {0x04, "SIM_USBSIM"}, + [AB5500_BANK_LED] = {0x10, "LED"}, + [AB5500_BANK_ADC] = {0x0A, "ADC"}, + [AB5500_BANK_RTC] = {0x0F, "RTC"}, + [AB5500_BANK_STARTUP] = {0x03, "STARTUP"}, + [AB5500_BANK_DBI_ECI] = {0x07, "DBI-ECI"}, + [AB5500_BANK_CHG] = {0x0B, "CHG"}, + [AB5500_BANK_FG_BATTCOM_ACC] = {0x0C, "FG_BATCOM_ACC"}, + [AB5500_BANK_USB] = {0x05, "USB"}, + [AB5500_BANK_IT] = {0x0E, "IT"}, + [AB5500_BANK_VIBRA] = {0x02, "VIBRA"}, + [AB5500_BANK_AUDIO_HEADSETUSB] = {0x0D, "AUDIO_HEADSETUSB"}, +}; + +/** + * struct ab5500_reg_range + * @first: the first address of the range + * @last: the last address of the range + * @perm: access permissions for the range + */ +struct ab5500_reg_range { + u8 first; + u8 last; + u8 perm; +}; + +/** + * struct ab5500_i2c_ranges + * @count: the number of ranges in the list + * @range: the list of register ranges + */ +struct ab5500_i2c_ranges { + u8 nranges; + u8 bankid; + const struct ab5500_reg_range *range; +}; + +/** + * struct ab5500_i2c_banks + * @count: the number of ranges in the list + * @range: the list of register ranges + */ +struct ab5500_i2c_banks { + u8 nbanks; + const struct ab5500_i2c_ranges *bank; +}; + +/* + * Permissible register ranges for reading and writing per device and bank. + * + * The ranges must be listed in increasing address order, and no overlaps are + * allowed. It is assumed that write permission implies read permission + * (i.e. only RO and RW permissions should be used). Ranges with write + * permission must not be split up. + */ + +#define NO_RANGE {.count = 0, .range = NULL,} + +static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { + [AB5500_DEVID_ADC] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_ADC, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x20, + .last = 0x58, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_LEDS] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_LED, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0C, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + /* What registers should this device access?*/ + [AB5500_DEVID_POWER] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_CHG, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x03, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x10, + .last = 0x30, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_REGULATORS] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_STARTUP, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x50, + .last = 0xE0, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_SIM] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_SIM_USBSIM, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x13, + .last = 0x19, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_RTC] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_LED, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0C, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_CHARGER] = { + .nbanks = 2, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_CHG, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x10, + .last = 0x18, + .perm = AB5500_PERM_RW, + }, + }, + }, + { + .bankid = AB5500_BANK_ADC, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x1F, + .last = 0x58, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_FUELGAUGE] = { + .nbanks = 2, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_FG_BATTCOM_ACC, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x10, + .perm = AB5500_PERM_RW, + }, + }, + }, + { + .bankid = AB5500_BANK_ADC, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x20, + .last = 0x58, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_VIBRATOR] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_VIBRA, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x10, + .last = 0x13, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_CODEC] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges[]) { + { + .bankid = AB5500_BANK_AUDIO_HEADSETUSB, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x48, + .perm = AB5500_PERM_RW, + }, + + }, + }, + }, + }, +}; + +/* I appologize for the resource names beeing a mix of upper case + * and lower case but I want them to be exact as the documentation */ +static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { + [AB5500_DEVID_LEDS] = { + .name = "ab5500-leds", + .id = AB5500_DEVID_LEDS, + }, + [AB5500_DEVID_POWER] = { + .name = "ab5500-power", + .id = AB5500_DEVID_POWER, + }, + [AB5500_DEVID_REGULATORS] = { + .name = "ab5500-regulators", + .id = AB5500_DEVID_REGULATORS, + }, + [AB5500_DEVID_SIM] = { + .name = "ab5500-sim", + .id = AB5500_DEVID_SIM, + .num_resources = 1, + .resources = (struct resource[]) { + { + .name = "SIMOFF", + .flags = IORESOURCE_IRQ, + .start = 16, /*rising*/ + .end = 17, /*falling*/ + }, + }, + }, + [AB5500_DEVID_RTC] = { + .name = "ab5500-rtc", + .id = AB5500_DEVID_RTC, + }, + [AB5500_DEVID_CHARGER] = { + .name = "ab5500-charger", + .id = AB5500_DEVID_CHARGER, + }, + [AB5500_DEVID_ADC] = { + .name = "ab5500-adc", + .id = AB5500_DEVID_ADC, + .num_resources = 10, + .resources = (struct resource[]) { + { + .name = "TRIGGER-0", + .flags = IORESOURCE_IRQ, + .start = 0, + .end = 0, + }, + { + .name = "TRIGGER-1", + .flags = IORESOURCE_IRQ, + .start = 1, + .end = 1, + }, + { + .name = "TRIGGER-2", + .flags = IORESOURCE_IRQ, + .start = 2, + .end = 2, + }, + { + .name = "TRIGGER-3", + .flags = IORESOURCE_IRQ, + .start = 3, + .end = 3, + }, + { + .name = "TRIGGER-4", + .flags = IORESOURCE_IRQ, + .start = 4, + .end = 4, + }, + { + .name = "TRIGGER-5", + .flags = IORESOURCE_IRQ, + .start = 5, + .end = 5, + }, + { + .name = "TRIGGER-6", + .flags = IORESOURCE_IRQ, + .start = 6, + .end = 6, + }, + { + .name = "TRIGGER-7", + .flags = IORESOURCE_IRQ, + .start = 7, + .end = 7, + }, + { + .name = "TRIGGER-VBAT-TXON", + .flags = IORESOURCE_IRQ, + .start = 9, + .end = 9, + }, + { + .name = "TRIGGER-VBAT", + .flags = IORESOURCE_IRQ, + .start = 8, + .end = 8, + }, + }, + }, + [AB5500_DEVID_FUELGAUGE] = { + .name = "ab5500-fuelgauge", + .id = AB5500_DEVID_FUELGAUGE, + .num_resources = 6, + .resources = (struct resource[]) { + { + .name = "Batt_attach", + .flags = IORESOURCE_IRQ, + .start = 61, + .end = 61, + }, + { + .name = "Batt_removal", + .flags = IORESOURCE_IRQ, + .start = 62, + .end = 62, + }, + { + .name = "UART_framing", + .flags = IORESOURCE_IRQ, + .start = 63, + .end = 63, + }, + { + .name = "UART_overrun", + .flags = IORESOURCE_IRQ, + .start = 64, + .end = 64, + }, + { + .name = "UART_Rdy_RX", + .flags = IORESOURCE_IRQ, + .start = 65, + .end = 65, + }, + { + .name = "UART_Rdy_TX", + .flags = IORESOURCE_IRQ, + .start = 66, + .end = 66, + }, + }, + }, + [AB5500_DEVID_VIBRATOR] = { + .name = "ab5500-vibrator", + .id = AB5500_DEVID_VIBRATOR, + }, + [AB5500_DEVID_CODEC] = { + .name = "ab5500-codec", + .id = AB5500_DEVID_CODEC, + .num_resources = 3, + .resources = (struct resource[]) { + { + .name = "audio_spkr1_ovc", + .flags = IORESOURCE_IRQ, + .start = 77, + .end = 77, + }, + { + .name = "audio_plllocked", + .flags = IORESOURCE_IRQ, + .start = 78, + .end = 78, + }, + { + .name = "audio_spkr2_ovc", + .flags = IORESOURCE_IRQ, + .start = 140, + .end = 140, + }, + }, + }, + [AB5500_DEVID_USB] = { + .name = "ab5500-usb", + .id = AB5500_DEVID_USB, + .num_resources = 35, + .resources = (struct resource[]) { + { + .name = "DCIO", + .flags = IORESOURCE_IRQ, + .start = 67, + .end = 68, + }, + { + .name = "VBUS", + .flags = IORESOURCE_IRQ, + .start = 69, + .end = 70, + }, + { + .name = "CHGstate_10_PCVBUSchg", + .flags = IORESOURCE_IRQ, + .start = 71, + .end = 71, + }, + { + .name = "DCIOreverse_ovc", + .flags = IORESOURCE_IRQ, + .start = 72, + .end = 72, + }, + { + .name = "USBCharDetDone", + .flags = IORESOURCE_IRQ, + .start = 73, + .end = 73, + }, + { + .name = "DCIO_no_limit", + .flags = IORESOURCE_IRQ, + .start = 74, + .end = 74, + }, + { + .name = "USB_suspend", + .flags = IORESOURCE_IRQ, + .start = 75, + .end = 75, + }, + { + .name = "DCIOreverse_fwdcurrent", + .flags = IORESOURCE_IRQ, + .start = 76, + .end = 76, + }, + { + .name = "Vbus_Imeasmax_change", + .flags = IORESOURCE_IRQ, + .start = 79, + .end = 80, + }, + { + .name = "OVV", + .flags = IORESOURCE_IRQ, + .start = 117, + .end = 117, + }, + { + .name = "USBcharging_NOTok", + .flags = IORESOURCE_IRQ, + .start = 123, + .end = 123, + }, + { + .name = "usb_adp_sensoroff", + .flags = IORESOURCE_IRQ, + .start = 126, + .end = 126, + }, + { + .name = "usb_adp_probeplug", + .flags = IORESOURCE_IRQ, + .start = 127, + .end = 127, + }, + { + .name = "usb_adp_sinkerror", + .flags = IORESOURCE_IRQ, + .start = 128, + .end = 128, + }, + { + .name = "usb_adp_sourceerror", + .flags = IORESOURCE_IRQ, + .start = 129, + .end = 129, + }, + { + .name = "usb_idgnd", + .flags = IORESOURCE_IRQ, + .start = 130, + .end = 131, + }, + { + .name = "usb_iddetR1", + .flags = IORESOURCE_IRQ, + .start = 132, + .end = 133, + }, + { + .name = "usb_iddetR2", + .flags = IORESOURCE_IRQ, + .start = 134, + .end = 135, + }, + { + .name = "usb_iddetR3", + .flags = IORESOURCE_IRQ, + .start = 136, + .end = 137, + }, + { + .name = "usb_iddetR4", + .flags = IORESOURCE_IRQ, + .start = 138, + .end = 139, + }, + { + .name = "CharTempWindowOk", + .flags = IORESOURCE_IRQ, + .start = 143, + .end = 144, + }, + { + .name = "USB_SprDetect", + .flags = IORESOURCE_IRQ, + .start = 145, + .end = 145, + }, + { + .name = "usb_adp_probe_unplug", + .flags = IORESOURCE_IRQ, + .start = 146, + .end = 146, + }, + { + .name = "VBUSChDrop", + .flags = IORESOURCE_IRQ, + .start = 147, + .end = 148, + }, + { + .name = "dcio_char_rec_done", + .flags = IORESOURCE_IRQ, + .start = 149, + .end = 149, + }, + { + .name = "Charging_stopped_by_temp", + .flags = IORESOURCE_IRQ, + .start = 150, + .end = 150, + }, + { + .name = "CHGstate_11_SafeModeVBUS", + .flags = IORESOURCE_IRQ, + .start = 169, + .end = 169, + }, + { + .name = "CHGstate_12_comletedVBUS", + .flags = IORESOURCE_IRQ, + .start = 170, + .end = 170, + }, + { + .name = "CHGstate_13_completedVBUS", + .flags = IORESOURCE_IRQ, + .start = 171, + .end = 171, + }, + { + .name = "CHGstate_14_FullChgDCIO", + .flags = IORESOURCE_IRQ, + .start = 172, + .end = 172, + }, + { + .name = "CHGstate_15_SafeModeDCIO", + .flags = IORESOURCE_IRQ, + .start = 173, + .end = 173, + }, + { + .name = "CHGstate_16_OFFsuspendDCIO", + .flags = IORESOURCE_IRQ, + .start = 174, + .end = 174, + }, + { + .name = "CHGstate_17_completedDCIO", + .flags = IORESOURCE_IRQ, + .start = 175, + .end = 175, + }, + { + .name = "o_it_dcio_char_rec_notok", + .flags = IORESOURCE_IRQ, + .start = 176, + .end = 176, + }, + { + .name = "usb_link_update", + .flags = IORESOURCE_IRQ, + .start = 177, + .end = 177, + }, + }, + }, + [AB5500_DEVID_OTP] = { + .name = "ab5500-otp", + .id = AB5500_DEVID_OTP, + }, + [AB5500_DEVID_VIDEO] = { + .name = "ab5500-video", + .id = AB5500_DEVID_VIDEO, + .num_resources = 1, + .resources = (struct resource[]) { + { + .name = "plugTVdet", + .flags = IORESOURCE_IRQ, + .start = 111, + .end = 111, + }, + }, + }, + [AB5500_DEVID_DBIECI] = { + .name = "ab5500-dbieci", + .id = AB5500_DEVID_DBIECI, + .num_resources = 10, + .resources = (struct resource[]) { + { + .name = "COLL", + .flags = IORESOURCE_IRQ, + .start = 112, + .end = 112, + }, + { + .name = "RESERR", + .flags = IORESOURCE_IRQ, + .start = 113, + .end = 113, + }, + { + .name = "FRAERR", + .flags = IORESOURCE_IRQ, + .start = 114, + .end = 114, + }, + { + .name = "COMERR", + .flags = IORESOURCE_IRQ, + .start = 115, + .end = 115, + }, + { + .name = "BSI_indicator", + .flags = IORESOURCE_IRQ, + .start = 116, + .end = 116, + }, + { + .name = "SPDSET", + .flags = IORESOURCE_IRQ, + .start = 118, + .end = 118, + }, + { + .name = "DSENT", + .flags = IORESOURCE_IRQ, + .start = 119, + .end = 119, + }, + { + .name = "DREC", + .flags = IORESOURCE_IRQ, + .start = 120, + .end = 120, + }, + { + .name = "ACCINT", + .flags = IORESOURCE_IRQ, + .start = 121, + .end = 121, + }, + { + .name = "NOPINT", + .flags = IORESOURCE_IRQ, + .start = 122, + .end = 122, + }, + }, + }, +}; + +/* + * This stubbed prcmu functionality should be removed when the prcmu driver + * implements it. + */ +static u8 prcmu_event_buf[AB5500_NUM_EVENT_REG]; + +void prcmu_get_abb_event_buf(u8 **buf) +{ + *buf = prcmu_event_buf; +} + +/* + * Functionality for getting/setting register values. + */ +static int get_register_interruptible(struct ab5500 *ab, u8 bank, u8 reg, + u8 *value) +{ + int err; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, reg, value, 1); + + mutex_unlock(&ab->access_mutex); + return err; +} + +static int get_register_page_interruptible(struct ab5500 *ab, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + int err; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + /* The hardware limit for get page is 4 */ + if (numregs > 4) + return -EINVAL; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, first_reg, + regvals, numregs); + + mutex_unlock(&ab->access_mutex); + return err; +} + +static int mask_and_set_register_interruptible(struct ab5500 *ab, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + int err = 0; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + if (bitmask) { + u8 buf; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + + if (bitmask == 0xFF) /* No need to read in this case. */ + buf = bitvalues; + else { /* Read and modify the register value. */ + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, + reg, &buf, 1); + if (err) + return err; + + buf = ((~bitmask & buf) | (bitmask & bitvalues)); + } + /* Write the new value. */ + err = db5500_prcmu_abb_write(bankinfo[bank].slave_addr, reg, + &buf, 1); + + mutex_unlock(&ab->access_mutex); + } + return err; +} + +/* + * Read/write permission checking functions. + */ +static const struct ab5500_i2c_ranges *get_bankref(u8 devid, u8 bank) +{ + u8 i; + + if (devid < AB5500_NUM_DEVICES) { + for (i = 0; i < ab5500_bank_ranges[devid].nbanks; i++) { + if (ab5500_bank_ranges[devid].bank[i].bankid == bank) + return &ab5500_bank_ranges[devid].bank[i]; + } + } + return NULL; +} + +static bool page_write_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg) +{ + u8 i; /* range loop index */ + const struct ab5500_i2c_ranges *bankref; + + bankref = get_bankref(devid, bank); + if (bankref == NULL || last_reg < first_reg) + return false; + + for (i = 0; i < bankref->nranges; i++) { + if (first_reg < bankref->range[i].first) + break; + if ((last_reg <= bankref->range[i].last) && + (bankref->range[i].perm & AB5500_PERM_WR)) + return true; + } + return false; +} + +static bool reg_write_allowed(u8 devid, u8 bank, u8 reg) +{ + return page_write_allowed(devid, bank, reg, reg); +} + +static bool page_read_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg) +{ + u8 i; + const struct ab5500_i2c_ranges *bankref; + + bankref = get_bankref(devid, bank); + if (bankref == NULL || last_reg < first_reg) + return false; + + + /* Find the range (if it exists in the list) that includes first_reg. */ + for (i = 0; i < bankref->nranges; i++) { + if (first_reg < bankref->range[i].first) + return false; + if (first_reg <= bankref->range[i].last) + break; + } + /* Make sure that the entire range up to and including last_reg is + * readable. This may span several of the ranges in the list. + */ + while ((i < bankref->nranges) && + (bankref->range[i].perm & AB5500_PERM_RD)) { + if (last_reg <= bankref->range[i].last) + return true; + if ((++i >= bankref->nranges) || + (bankref->range[i].first != + (bankref->range[i - 1].last + 1))) { + break; + } + } + return false; +} + +static bool reg_read_allowed(u8 devid, u8 bank, u8 reg) +{ + return page_read_allowed(devid, bank, reg, reg); +} + + +/* + * The exported register access functionality. + */ +int ab5500_get_chip_id(struct device *dev) +{ + struct ab5500 *ab = dev_get_drvdata(dev->parent); + + return (int)ab->chip_id; +} + +int ab5500_mask_and_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + struct ab5500 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB5500_NUM_BANKS <= bank) || + !reg_write_allowed(pdev->id, bank, reg)) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return mask_and_set_register_interruptible(ab, bank, reg, + bitmask, bitvalues); +} + +int ab5500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 value) +{ + return ab5500_mask_and_set_register_interruptible(dev, bank, reg, 0xFF, + value); +} + +int ab5500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, + u8 *value) +{ + struct ab5500 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB5500_NUM_BANKS <= bank) || + !reg_read_allowed(pdev->id, bank, reg)) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return get_register_interruptible(ab, bank, reg, value); +} + +int ab5500_get_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + struct ab5500 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB5500_NUM_BANKS <= bank) || + !page_read_allowed(pdev->id, bank, + first_reg, (first_reg + numregs - 1))) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return get_register_page_interruptible(ab, bank, first_reg, regvals, + numregs); +} + +int ab5500_event_registers_startup_state_get(struct device *dev, u8 *event) +{ + struct ab5500 *ab; + + ab = dev_get_drvdata(dev->parent); + if (!ab->startup_events_read) + return -EAGAIN; /* Try again later */ + + memcpy(event, ab->startup_events, AB5500_NUM_EVENT_REG); + return 0; +} + +int ab5500_startup_irq_enabled(struct device *dev, unsigned int irq) +{ + struct ab5500 *ab; + bool val; + + ab = get_irq_chip_data(irq); + irq -= ab->irq_base; + val = ((ab->startup_events[irq / 8] & BIT(irq % 8)) != 0); + + return val; +} + +static struct abx500_ops ab5500_ops = { + .get_chip_id = ab5500_get_chip_id, + .get_register = ab5500_get_register_interruptible, + .set_register = ab5500_set_register_interruptible, + .get_register_page = ab5500_get_register_page_interruptible, + .set_register_page = NULL, + .mask_and_set_register = ab5500_mask_and_set_register_interruptible, + .event_registers_startup_state_get = + ab5500_event_registers_startup_state_get, + .startup_irq_enabled = ab5500_startup_irq_enabled, +}; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) +static irqreturn_t ab5500_irq_handler(int irq, void *data) +{ + struct ab5500 *ab = data; + + /* + * Disable the IRQ and dispatch a worker to handle the + * event. Since the chip resides on I2C this is slow + * stuff and we will re-enable the interrupts once the + * worker has finished. + */ + disable_irq_nosync(irq); + schedule_work(&ab->irq_work); + return IRQ_HANDLED; +} + +static void ab5500_irq_work(struct work_struct *work) +{ + struct ab5500 *ab = container_of(work, struct ab5500, irq_work); + u8 i; + u8 *e = 0; + u8 events[AB5500_NUM_EVENT_REG]; + unsigned long flags; + + prcmu_get_abb_event_buf(&e); + + spin_lock_irqsave(&ab->event_lock, flags); + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) + events[i] = e[i] & ~ab->event_mask[i]; + spin_unlock_irqrestore(&ab->event_lock, flags); + + local_irq_disable(); + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { + u8 bit; + u8 event_reg; + + dev_dbg(ab->dev, "IRQ Event[%d]: 0x%2x\n", + i, events[i]); + + event_reg = events[i]; + for (bit = 0; event_reg; bit++, event_reg /= 2) { + if (event_reg % 2) { + unsigned int irq; + struct irq_desc *desc; + + irq = ab->irq_base + (i * 8) + bit; + desc = irq_to_desc(irq); + if (desc->status & IRQ_DISABLED) + note_interrupt(irq, desc, IRQ_NONE); + else + desc->handle_irq(irq, desc); + } + } + } + local_irq_enable(); + /* By now the IRQ should be acked and deasserted so enable it again */ + enable_irq(ab->ab5500_irq); +} + +#else + +static irqreturn_t ab5500_irq_handler(int irq, void *data) +{ + struct ab5500 *ab = data; + u8 i; + u8 *e = 0; + u8 events[AB5500_NUM_EVENT_REG]; + + prcmu_get_abb_event_buf(&e); + + spin_lock(&ab->event_lock); + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) + events[i] = e[i] & ~ab->event_mask[i]; + spin_unlock(&ab->event_lock); + + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { + u8 bit; + u8 event_reg; + + dev_dbg(ab->dev, "IRQ Event[%d]: 0x%2x\n", + i, events[i]); + + event_reg = events[i]; + for (bit = 0; event_reg; bit++, event_reg /= 2) { + if (event_reg % 2) { + unsigned int irq; + + irq = ab->irq_base + (i * 8) + bit; + generic_handle_irq(irq); + } + } + } + + return IRQ_HANDLED; +} +#endif + +#ifdef CONFIG_DEBUG_FS +static struct ab5500_i2c_ranges debug_ranges[AB5500_NUM_BANKS] = { + [AB5500_BANK_LED] = { + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0C, + }, + }, + }, + [AB5500_BANK_ADC] = { + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x1F, + .last = 0x24, + }, + { + .first = 0x26, + .last = 0x2D, + }, + { + .first = 0x2F, + .last = 0x35, + }, + { + .first = 0x37, + .last = 0x58, + }, + }, + }, + [AB5500_BANK_RTC] = { + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x06, + .last = 0x0C, + }, + }, + }, + [AB5500_BANK_STARTUP] = { + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + }, + { + .first = 0x1F, + .last = 0x1F, + }, + { + .first = 0x2E, + .last = 0x30, + }, + { + .first = 0x50, + .last = 0x51, + }, + { + .first = 0x60, + .last = 0x61, + }, + { + .first = 0x66, + .last = 0x8A, + }, + { + .first = 0x8C, + .last = 0x96, + }, + { + .first = 0xAA, + .last = 0xB4, + }, + { + .first = 0xB7, + .last = 0xBF, + }, + { + .first = 0xC1, + .last = 0xCA, + }, + { + .first = 0xD3, + .last = 0xE0, + }, + { + .first = 0xF0, + .last = 0xF8, + }, + }, + }, + [AB5500_BANK_DBI_ECI] = { + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + }, + { + .first = 0x10, + .last = 0x10, + }, + { + .first = 0x13, + .last = 0x13, + }, + }, + }, + [AB5500_BANK_CHG] = { + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x11, + .last = 0x1B, + }, + }, + }, + [AB5500_BANK_FG_BATTCOM_ACC] = { + .nranges = 5, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x10, + }, + { + .first = 0x1A, + .last = 0x1D, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x23, + .last = 0x24, + }, + { + .first = 0xFC, + .last = 0xFE, + }, + }, + }, + [AB5500_BANK_USB] = { + .nranges = 13, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x01, + }, + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8B, + }, + { + .first = 0x91, + .last = 0x94, + }, + { + .first = 0xA8, + .last = 0xB0, + }, + { + .first = 0xB2, + .last = 0xB2, + }, + { + .first = 0xB4, + .last = 0xBC, + }, + { + .first = 0xBF, + .last = 0xBF, + }, + { + .first = 0xC1, + .last = 0xC6, + }, + { + .first = 0xCD, + .last = 0xCD, + }, + { + .first = 0xD6, + .last = 0xDA, + }, + { + .first = 0xDC, + .last = 0xDC, + }, + { + .first = 0xE0, + .last = 0xE4, + }, + }, + }, + [AB5500_BANK_IT] = { + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x02, + }, + { + .first = 0x20, + .last = 0x36, + }, + { + .first = 0x60, + .last = 0x76, + }, + { + .first = 0x80, + .last = 0x80, + }, + }, + }, + [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { + .nranges = 7, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x02, + .last = 0x02, + }, + { + .first = 0x12, + .last = 0x12, + }, + { + .first = 0x30, + .last = 0x34, + }, + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x54, + }, + { + .first = 0x60, + .last = 0x64, + }, + { + .first = 0x70, + .last = 0x74, + }, + }, + }, + [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x02, + }, + { + .first = 0x0D, + .last = 0x0F, + }, + { + .first = 0x1C, + .last = 0x1C, + }, + { + .first = 0x1E, + .last = 0x1E, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x25, + .last = 0x25, + }, + { + .first = 0x28, + .last = 0x2A, + }, + { + .first = 0x30, + .last = 0x33, + }, + { + .first = 0x40, + .last = 0x43, + }, + { + .first = 0x50, + .last = 0x53, + }, + { + .first = 0x60, + .last = 0x63, + }, + { + .first = 0x70, + .last = 0x73, + }, + }, + }, + [AB5500_BANK_VIBRA] = { + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x10, + .last = 0x13, + }, + { + .first = 0xFE, + .last = 0xFE, + }, + }, + }, + [AB5500_BANK_AUDIO_HEADSETUSB] = { + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x48, + }, + { + .first = 0xEB, + .last = 0xFB, + }, + }, + }, +}; + +static int ab5500_registers_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + unsigned int i; + u8 bank = (u8)ab->debug_bank; + + seq_printf(s, AB5500_NAME_STRING " register values:\n"); + + seq_printf(s, " bank %u, %s (0x%x):\n", bank, + bankinfo[bank].name, + bankinfo[bank].slave_addr); + for (i = 0; i < debug_ranges[bank].nranges; i++) { + u8 reg; + int err; + + for (reg = debug_ranges[bank].range[i].first; + reg <= debug_ranges[bank].range[i].last; + reg++) { + u8 value; + + err = get_register_interruptible(ab, bank, reg, + &value); + if (err < 0) { + dev_err(ab->dev, "get_reg failed %d, bank 0x%x" + ", reg 0x%x\n", err, bank, reg); + return err; + } + + err = seq_printf(s, " [%d/0x%02X]: 0x%02X\n", bank, + reg, value); + if (err < 0) { + dev_err(ab->dev, "seq_printf overflow\n"); + /* + * Error is not returned here since + * the output is wanted in any case + */ + return 0; + } + } + } + return 0; +} + +static int ab5500_registers_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_registers_print, inode->i_private); +} + +static const struct file_operations ab5500_registers_fops = { + .open = ab5500_registers_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab5500_bank_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + + seq_printf(s, "%d\n", ab->debug_bank); + return 0; +} + +static int ab5500_bank_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_bank_print, inode->i_private); +} + +static ssize_t ab5500_bank_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_bank; + int err; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_bank); + if (err) + return -EINVAL; + + if (user_bank >= AB5500_NUM_BANKS) { + dev_err(ab->dev, + "debugfs error input > number of banks\n"); + return -EINVAL; + } + + ab->debug_bank = user_bank; + + return buf_size; +} + +static int ab5500_address_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + + seq_printf(s, "0x%02X\n", ab->debug_address); + return 0; +} + +static int ab5500_address_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_address_print, inode->i_private); +} + +static ssize_t ab5500_address_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_address; + int err; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf) - 1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_address); + if (err) + return -EINVAL; + if (user_address > 0xff) { + dev_err(ab->dev, + "debugfs error input > 0xff\n"); + return -EINVAL; + } + ab->debug_address = user_address; + return buf_size; +} + +static int ab5500_val_print(struct seq_file *s, void *p) +{ + struct ab5500 *ab = s->private; + int err; + u8 regvalue; + + err = get_register_interruptible(ab, (u8)ab->debug_bank, + (u8)ab->debug_address, ®value); + if (err) { + dev_err(ab->dev, "get_reg failed %d, bank 0x%x" + ", reg 0x%x\n", err, ab->debug_bank, + ab->debug_address); + return -EINVAL; + } + seq_printf(s, "0x%02X\n", regvalue); + + return 0; +} + +static int ab5500_val_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab5500_val_print, inode->i_private); +} + +static ssize_t ab5500_val_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct ab5500 *ab = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + u8 regvalue; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + if (user_val > 0xff) { + dev_err(ab->dev, + "debugfs error input > 0xff\n"); + return -EINVAL; + } + err = mask_and_set_register_interruptible( + ab, (u8)ab->debug_bank, + (u8)ab->debug_address, 0xFF, (u8)user_val); + if (err) + return -EINVAL; + + get_register_interruptible(ab, (u8)ab->debug_bank, + (u8)ab->debug_address, ®value); + if (err) + return -EINVAL; + + return buf_size; +} + +static const struct file_operations ab5500_bank_fops = { + .open = ab5500_bank_open, + .write = ab5500_bank_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab5500_address_fops = { + .open = ab5500_address_open, + .write = ab5500_address_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab5500_val_fops = { + .open = ab5500_val_open, + .write = ab5500_val_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static struct dentry *ab5500_dir; +static struct dentry *ab5500_reg_file; +static struct dentry *ab5500_bank_file; +static struct dentry *ab5500_address_file; +static struct dentry *ab5500_val_file; + +static inline void ab5500_setup_debugfs(struct ab5500 *ab) +{ + ab->debug_bank = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP; + ab->debug_address = AB5500_CHIP_ID; + + ab5500_dir = debugfs_create_dir(AB5500_NAME_STRING, NULL); + if (!ab5500_dir) + goto exit_no_debugfs; + + ab5500_reg_file = debugfs_create_file("all-bank-registers", + S_IRUGO, ab5500_dir, ab, &ab5500_registers_fops); + if (!ab5500_reg_file) + goto exit_destroy_dir; + + ab5500_bank_file = debugfs_create_file("register-bank", + (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_bank_fops); + if (!ab5500_bank_file) + goto exit_destroy_reg; + + ab5500_address_file = debugfs_create_file("register-address", + (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_address_fops); + if (!ab5500_address_file) + goto exit_destroy_bank; + + ab5500_val_file = debugfs_create_file("register-value", + (S_IRUGO | S_IWUGO), ab5500_dir, ab, &ab5500_val_fops); + if (!ab5500_val_file) + goto exit_destroy_address; + + return; + +exit_destroy_address: + debugfs_remove(ab5500_address_file); +exit_destroy_bank: + debugfs_remove(ab5500_bank_file); +exit_destroy_reg: + debugfs_remove(ab5500_reg_file); +exit_destroy_dir: + debugfs_remove(ab5500_dir); +exit_no_debugfs: + dev_err(ab->dev, "failed to create debugfs entries.\n"); + return; +} + +static inline void ab5500_remove_debugfs(void) +{ + debugfs_remove(ab5500_val_file); + debugfs_remove(ab5500_address_file); + debugfs_remove(ab5500_bank_file); + debugfs_remove(ab5500_reg_file); + debugfs_remove(ab5500_dir); +} + +#else /* !CONFIG_DEBUG_FS */ +static inline void ab5500_setup_debugfs(struct ab5500 *ab) +{ +} +static inline void ab5500_remove_debugfs(void) +{ +} +#endif + +/* + * Basic set-up, datastructure creation/destruction and I2C interface. + * This sets up a default config in the AB5500 chip so that it + * will work as expected. + */ +static int __init ab5500_setup(struct ab5500 *ab, + struct abx500_init_settings *settings, unsigned int size) +{ + int err = 0; + int i; + + for (i = 0; i < size; i++) { + err = mask_and_set_register_interruptible(ab, + settings[i].bank, + settings[i].reg, + 0xFF, settings[i].setting); + if (err) + goto exit_no_setup; + + /* If event mask register update the event mask in ab5500 */ + if ((settings[i].bank == AB5500_BANK_IT) && + (AB5500_MASK_BASE <= settings[i].reg) && + (settings[i].reg <= AB5500_MASK_END)) { + ab->event_mask[settings[i].reg - AB5500_MASK_BASE] = + settings[i].setting; + } + } +exit_no_setup: + return err; +} + +static void ab5500_mask_work(struct work_struct *work) +{ + struct ab5500 *ab = container_of(work, struct ab5500, mask_work); + int i; + int err; + unsigned long flags; + u8 mask[AB5500_NUM_EVENT_REG]; + int call_prcmu_event_readout = 0; + + spin_lock_irqsave(&ab->event_lock, flags); + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) + mask[i] = ab->event_mask[i]; + spin_unlock_irqrestore(&ab->event_lock, flags); + + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { + if (mask[i] != ab->last_event_mask[i]) { + err = mask_and_set_register_interruptible(ab, 0, + (AB5500_MASK_BASE + i), ~0, mask[i]); + if (err) { + dev_err(ab->dev, + "ab5500_mask_work failed 0x%x,0x%x\n", + (AB5500_MASK_BASE + i), mask[i]); + break; + } + + if (mask[i] == 0xFF) { + ab->abb_events &= ~BIT(i); + call_prcmu_event_readout = 1; + } else { + ab->abb_events |= BIT(i); + if (ab->last_event_mask[i] == 0xFF) + call_prcmu_event_readout = 1; + } + + ab->last_event_mask[i] = mask[i]; + } + } + if (call_prcmu_event_readout) { + err = db5500_prcmu_config_abb_event_readout(ab->abb_events); + if (err) + dev_err(ab->dev, + "prcmu_config_abb_event_readout failed\n"); + } +} + +static void ab5500_mask(unsigned int irq) +{ + unsigned long flags; + struct ab5500 *ab; + + ab = get_irq_chip_data(irq); + irq -= ab->irq_base; + + spin_lock_irqsave(&ab->event_lock, flags); + ab->event_mask[irq / 8] |= BIT(irq % 8); + spin_unlock_irqrestore(&ab->event_lock, flags); + + schedule_work(&ab->mask_work); +} + +static void ab5500_unmask(unsigned int irq) +{ + unsigned long flags; + struct ab5500 *ab; + + ab = get_irq_chip_data(irq); + irq -= ab->irq_base; + + spin_lock_irqsave(&ab->event_lock, flags); + ab->event_mask[irq / 8] &= ~BIT(irq % 8); + spin_unlock_irqrestore(&ab->event_lock, flags); + + schedule_work(&ab->mask_work); +} + +static void noop(unsigned int irq) +{ +} + +static struct irq_chip ab5500_irq_chip = { + .name = "ab5500-core", /* Keep the same name as the request */ + .startup = NULL, /* defaults to enable */ + .shutdown = NULL, /* defaults to disable */ + .enable = NULL, /* defaults to unmask */ + .disable = ab5500_mask, /* No default to mask in chip.c */ + .ack = noop, + .mask = ab5500_mask, + .unmask = ab5500_unmask, + .end = NULL, +}; + +struct ab_family_id { + u8 id; + char *name; +}; + +static const struct ab_family_id ids[] __initdata = { + /* AB5500 */ + { + .id = AB5500_1_0, + .name = "1.0" + }, + /* Terminator */ + { + .id = 0x00, + } +}; + +static int __init ab5500_probe(struct platform_device *pdev) +{ + struct ab5500 *ab; + struct ab5500_platform_data *ab5500_plf_data = + pdev->dev.platform_data; + struct resource *res; + int err; + int i; + + ab = kzalloc(sizeof(struct ab5500), GFP_KERNEL); + if (!ab) { + dev_err(&pdev->dev, + "could not allocate " AB5500_NAME_STRING " device\n"); + return -ENOMEM; + } + + /* Initialize data structure */ + mutex_init(&ab->access_mutex); + spin_lock_init(&ab->event_lock); + ab->dev = &pdev->dev; + ab->irq_base = ab5500_plf_data->irq.base; + + platform_set_drvdata(pdev, ab); + + /* Read chip ID register */ + err = get_register_interruptible(ab, AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + AB5500_CHIP_ID, &ab->chip_id); + if (err) { + dev_err(&pdev->dev, "could not communicate with the analog " + "baseband chip\n"); + goto exit_no_detect; + } + + for (i = 0; ids[i].id != 0x0; i++) { + if (ids[i].id == ab->chip_id) { + snprintf(&ab->chip_name[0], sizeof(ab->chip_name) - 1, + AB5500_ID_FORMAT_STRING, ids[i].name); + break; + } + } + + if (ids[i].id == 0x0) { + dev_err(&pdev->dev, "unknown analog baseband chip id: 0x%x\n", + ab->chip_id); + dev_err(&pdev->dev, "driver not started!\n"); + goto exit_no_detect; + } + + dev_info(&pdev->dev, "detected AB chip: %s\n", &ab->chip_name[0]); + + /* Readout ab->starup_events when prcmu driver is in place */ + ab->startup_events[0] = 0; + + err = ab5500_setup(ab, ab5500_plf_data->init_settings, + ab5500_plf_data->init_settings_sz); + if (err) { + dev_err(&pdev->dev, "ab5500_setup error\n"); + goto exit_no_setup; + } + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) + INIT_WORK(&ab->irq_work, ab5500_irq_work); +#endif + INIT_WORK(&ab->mask_work, ab5500_mask_work); + + for (i = 0; i < ab5500_plf_data->irq.count; i++) { + unsigned int irq; + + irq = ab5500_plf_data->irq.base + i; + set_irq_chip_data(irq, ab); + set_irq_chip_and_handler(irq, &ab5500_irq_chip, + handle_simple_irq); +#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 29) + set_irq_nested_thread(irq, 1); +#endif + set_irq_flags(irq, IRQF_VALID); + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + + if (!res) { + dev_err(&pdev->dev, "ab5500_platform_get_resource error\n"); + goto exit_no_irq; + } + ab->ab5500_irq = res->start; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) + /* This really unpredictable IRQ is of course sampled for entropy. */ + err = request_irq(res->start, ab5500_irq_handler, + (IRQF_DISABLED | IRQF_SAMPLE_RANDOM), "ab5500-core", ab); + if (err) { + dev_err(&pdev->dev, "ab5500_request_irq error\n"); + goto exit_no_irq; + } + + /* We probably already got an irq here, but if not, + * we force a first time and save the startup events here.*/ + disable_irq_nosync(res->start); + schedule_work(&ab->irq_work); +#else + err = request_threaded_irq(res->start, ab5500_irq_handler, NULL, + IRQF_SAMPLE_RANDOM, "ab5500-core", ab); + /* This real unpredictable IRQ is of course sampled for entropy */ + rand_initialize_irq(res->start); + + if (err) { + dev_err(&pdev->dev, "ab5500_request_irq error\n"); + goto exit_no_irq; + } +#endif + + err = abx500_register_ops(&pdev->dev, &ab5500_ops); + if (err) { + dev_err(&pdev->dev, "ab5500_register ops error\n"); + goto exit_no_ops; + } + + /* Set up and register the platform devices. */ + for (i = 0; i < AB5500_NUM_DEVICES; i++) { + ab5500_devs[i].platform_data = ab5500_plf_data->dev_data[i]; + ab5500_devs[i].data_size = ab5500_plf_data->dev_data_sz[i]; + } + + err = mfd_add_devices(&pdev->dev, 0, ab5500_devs, + ARRAY_SIZE(ab5500_devs), NULL, + ab5500_plf_data->irq.base); + if (err) { + dev_err(&pdev->dev, "ab5500_mfd_add_device error\n"); + goto exit_no_ops; + } + + ab5500_setup_debugfs(ab); + + return 0; + +exit_no_ops: +exit_no_irq: +exit_no_setup: +exit_no_detect: + kfree(ab); + return err; +} + +static int __exit ab5500_remove(struct platform_device *pdev) +{ + struct ab5500 *ab = platform_get_drvdata(pdev); + struct resource *res; + + /* + * At this point, all subscribers should have unregistered + * their notifiers so deactivate IRQ + */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + free_irq(res->start, ab); + + mfd_remove_devices(&pdev->dev); + ab5500_remove_debugfs(); + + kfree(ab); + return 0; +} + +static struct platform_driver ab5500_driver = { + .driver = { + .name = "ab5500-core", + .owner = THIS_MODULE, + }, + .remove = __exit_p(ab5500_remove), +}; + +static int __init ab5500_core_init(void) +{ + return platform_driver_probe(&ab5500_driver, ab5500_probe); +} + +static void __exit ab5500_core_exit(void) +{ + platform_driver_unregister(&ab5500_driver); +} + +subsys_initcall(ab5500_core_init); +module_exit(ab5500_core_exit); + +MODULE_AUTHOR("Mattias Wallin "); +MODULE_DESCRIPTION("AB5500 core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index 387705e494b..3fac47f9bb5 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -700,6 +700,9 @@ static struct mfd_cell __devinitdata ab8500_devs[] = { { .name = "ab8500-regulator", }, + { + .name = "ab8500-regulator-debug", + }, { .name = "ab8500-gpio", .num_resources = ARRAY_SIZE(ab8500_gpio_resources), diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index 64bdeeb1c11..584c7472fca 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -10,6 +10,9 @@ #include #include #include +#include +#include +#include #include #include @@ -17,6 +20,13 @@ static u32 debug_bank; static u32 debug_address; +static int irq_first; +static int irq_last; +static u32 irq_count[AB8500_NR_IRQS]; + +static struct device_attribute *dev_attr[AB8500_NR_IRQS]; +static char *event_name[AB8500_NR_IRQS]; + /** * struct ab8500_reg_range * @first: the first address of the range @@ -49,7 +59,7 @@ struct ab8500_i2c_ranges { static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { [0x0] = { .num_ranges = 0, - .range = 0, + .range = NULL, }, [AB8500_SYS_CTRL1_BLOCK] = { .num_ranges = 3, @@ -353,6 +363,24 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { }, }; +static irqreturn_t ab8500_debug_handler(int irq, void *data) +{ + char buf[16]; + struct kobject *kobj = (struct kobject *)data; + unsigned int irq_abb = irq - irq_first; + + if (irq_abb < AB8500_NR_IRQS) + irq_count[irq_abb]++; + /* + * This makes it possible to use poll for events (POLLPRI | POLLERR) + * from userspace on sysfs file named + */ + sprintf(buf, "%d", irq); + sysfs_notify(kobj, NULL, buf); + + return IRQ_HANDLED; +} + static int ab8500_registers_print(struct seq_file *s, void *p) { struct device *dev = s->private; @@ -514,6 +542,151 @@ static ssize_t ab8500_val_write(struct file *file, printk(KERN_ERR "abx500_set_reg failed %d, %d", err, __LINE__); return -EINVAL; } + return count; +} + +static int ab8500_subscribe_unsubscribe_print(struct seq_file *s, void *p) +{ + seq_printf(s, "%d\n", irq_first); + + return 0; +} + +static int ab8500_subscribe_unsubscribe_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_subscribe_unsubscribe_print, + inode->i_private); +} + +/* + * Userspace should use poll() on this file. When an event occur + * the blocking poll will be released. + */ +static ssize_t show_irq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned long name; + unsigned int irq_index; + int err; + + err = strict_strtoul(attr->attr.name, 0, &name); + if (err) + return err; + + irq_index = name - irq_first; + if (irq_index >= AB8500_NR_IRQS) + return -EINVAL; + else + return sprintf(buf, "%u\n", irq_count[irq_index]); +} + +static ssize_t ab8500_subscribe_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + unsigned int irq_index; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + if (user_val < irq_first) { + dev_err(dev, "debugfs error input < %d\n", irq_first); + return -EINVAL; + } + if (user_val > irq_last) { + dev_err(dev, "debugfs error input > %d\n", irq_last); + return -EINVAL; + } + + irq_index = user_val - irq_first; + if (irq_index >= AB8500_NR_IRQS) + return -EINVAL; + + /* + * This will create a sysfs file named which userspace can + * use to select or poll and get the AB8500 events + */ + dev_attr[irq_index] = kmalloc(sizeof(struct device_attribute), + GFP_KERNEL); + event_name[irq_index] = kmalloc(buf_size, GFP_KERNEL); + sprintf(event_name[irq_index], "%lu", user_val); + dev_attr[irq_index]->show = show_irq; + dev_attr[irq_index]->store = NULL; + dev_attr[irq_index]->attr.name = event_name[irq_index]; + dev_attr[irq_index]->attr.mode = S_IRUGO; + err = sysfs_create_file(&dev->kobj, &dev_attr[irq_index]->attr); + if (err < 0) { + printk(KERN_ERR "sysfs_create_file failed %d\n", err); + return err; + } + + err = request_threaded_irq(user_val, NULL, ab8500_debug_handler, + IRQF_SHARED | IRQF_NO_SUSPEND, "ab8500-debug", &dev->kobj); + if (err < 0) { + printk(KERN_ERR "request_threaded_irq failed %d, %lu\n", + err, user_val); + sysfs_remove_file(&dev->kobj, &dev_attr[irq_index]->attr); + return err; + } + + return buf_size; +} + +static ssize_t ab8500_unsubscribe_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + char buf[32]; + int buf_size; + unsigned long user_val; + int err; + unsigned int irq_index; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + err = strict_strtoul(buf, 0, &user_val); + if (err) + return -EINVAL; + if (user_val < irq_first) { + dev_err(dev, "debugfs error input < %d\n", irq_first); + return -EINVAL; + } + if (user_val > irq_last) { + dev_err(dev, "debugfs error input > %d\n", irq_last); + return -EINVAL; + } + + irq_index = user_val - irq_first; + if (irq_index >= AB8500_NR_IRQS) + return -EINVAL; + + /* Set irq count to 0 when unsubscribe */ + irq_count[irq_index] = 0; + + if (dev_attr[irq_index]) + sysfs_remove_file(&dev->kobj, &dev_attr[irq_index]->attr); + + + free_irq(user_val, &dev->kobj); + kfree(event_name[irq_index]); + kfree(dev_attr[irq_index]); return count; } @@ -545,17 +718,51 @@ static const struct file_operations ab8500_val_fops = { .owner = THIS_MODULE, }; +static const struct file_operations ab8500_subscribe_fops = { + .open = ab8500_subscribe_unsubscribe_open, + .write = ab8500_subscribe_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static const struct file_operations ab8500_unsubscribe_fops = { + .open = ab8500_subscribe_unsubscribe_open, + .write = ab8500_unsubscribe_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + static struct dentry *ab8500_dir; static struct dentry *ab8500_reg_file; static struct dentry *ab8500_bank_file; static struct dentry *ab8500_address_file; static struct dentry *ab8500_val_file; +static struct dentry *ab8500_subscribe_file; +static struct dentry *ab8500_unsubscribe_file; static int __devinit ab8500_debug_probe(struct platform_device *plf) { debug_bank = AB8500_MISC; debug_address = AB8500_REV_REG & 0x00FF; + irq_first = platform_get_irq_byname(plf, "IRQ_FIRST"); + if (irq_first < 0) { + dev_err(&plf->dev, "First irq not found, err %d\n", + irq_first); + return irq_first; + } + + irq_last = platform_get_irq_byname(plf, "IRQ_LAST"); + if (irq_last < 0) { + dev_err(&plf->dev, "Last irq not found, err %d\n", + irq_last); + return irq_last; + } + ab8500_dir = debugfs_create_dir(AB8500_NAME_STRING, NULL); if (!ab8500_dir) goto exit_no_debugfs; @@ -566,23 +773,38 @@ static int __devinit ab8500_debug_probe(struct platform_device *plf) goto exit_destroy_dir; ab8500_bank_file = debugfs_create_file("register-bank", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, &ab8500_bank_fops); + (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_bank_fops); if (!ab8500_bank_file) goto exit_destroy_reg; ab8500_address_file = debugfs_create_file("register-address", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, + (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_address_fops); if (!ab8500_address_file) goto exit_destroy_bank; ab8500_val_file = debugfs_create_file("register-value", - (S_IRUGO | S_IWUSR), ab8500_dir, &plf->dev, &ab8500_val_fops); + (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_val_fops); if (!ab8500_val_file) goto exit_destroy_address; + ab8500_subscribe_file = debugfs_create_file("irq-subscribe", + (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, + &ab8500_subscribe_fops); + if (!ab8500_subscribe_file) + goto exit_destroy_val; + + ab8500_unsubscribe_file = debugfs_create_file("irq-unsubscribe", + (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, + &ab8500_unsubscribe_fops); + if (!ab8500_unsubscribe_file) + goto exit_destroy_subscribe; return 0; +exit_destroy_subscribe: + debugfs_remove(ab8500_subscribe_file); +exit_destroy_val: + debugfs_remove(ab8500_val_file); exit_destroy_address: debugfs_remove(ab8500_address_file); exit_destroy_bank: diff --git a/drivers/mfd/ab8500-denc.c b/drivers/mfd/ab8500-denc.c new file mode 100644 index 00000000000..06e4b282cbf --- /dev/null +++ b/drivers/mfd/ab8500-denc.c @@ -0,0 +1,515 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * ST-Ericsson AB8500 DENC base driver + * + * Author: Marcel Tunnissen + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AB8500_NAME "ab8500" +#define AB8500_DENC_NAME "ab8500_denc" + +struct device_usage { + struct list_head list; + struct platform_device *pdev; + bool taken; +}; +static LIST_HEAD(device_list); + +/* To get rid of the extra bank parameter: */ +#define AB8500_REG_BANK_NR(__reg) ((0xff00 & (__reg)) >> 8) +static inline u8 ab8500_rreg(struct device *dev, u32 reg) +{ + u8 val; + if (abx500_get_register_interruptible(dev, AB8500_REG_BANK_NR(reg), + reg, &val) < 0) + return 0; + else + return val; +} + +static inline int ab8500_wreg(struct device *dev, u32 reg, u8 val) +{ + return abx500_set_register_interruptible(dev, AB8500_REG_BANK_NR(reg), + reg, val); +} + +/* Only use in the macro below: */ +static inline int _ab8500_wreg_fld(struct device *dev, u32 reg, u8 val, + u8 mask, u8 shift) +{ + int ret; + u8 org_val; + + ret = abx500_get_register_interruptible(dev, AB8500_REG_BANK_NR(reg), + reg, &org_val); + if (ret < 0) + return ret; + else + ab8500_wreg(dev, reg, + (org_val & ~mask) | ((val << shift) & mask)); + return 0; +} + +#define ab8500_wr_fld(__d, __reg, __fld, __val) \ + _ab8500_wreg_fld(__d, __reg, __val, __reg##_##__fld##_MASK, \ + __reg##_##__fld##_SHIFT) + +#define ab8500_set_fld(__cur_val, __reg, __fld, __val) \ + (((__cur_val) & ~__reg##_##__fld##_MASK) | \ + (((__val) << __reg##_##__fld##_SHIFT) & __reg##_##__fld##_MASK)) + +#define AB8500_DENC_TRACE(__pd) dev_dbg(&(__pd)->dev, "%s\n", __func__) + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_ab8500_denc_dir; +static struct dentry *debugfs_ab8500_dump_regs_file; +static void ab8500_denc_conf_ddr(struct platform_device *pdev); +static int debugfs_ab8500_open_file(struct inode *inode, struct file *file); +static ssize_t debugfs_ab8500_dump_regs(struct file *file, char __user *buf, + size_t count, loff_t *f_pos); + +static const struct file_operations debugfs_ab8500_dump_regs_fops = { + .owner = THIS_MODULE, + .open = debugfs_ab8500_open_file, + .read = debugfs_ab8500_dump_regs, +}; +#endif /* CONFIG_DEBUG_FS */ + +static int __devinit ab8500_denc_probe(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_platform_data *ab8500_pdata = + dev_get_platdata(pdev->dev.parent); + struct ab8500_denc_platform_data *pdata; + struct device_usage *device_data; + + AB8500_DENC_TRACE(pdev); + + if (ab8500_pdata == NULL) { + dev_err(&pdev->dev, "AB8500 platform data missing\n"); + return -EINVAL; + } + + pdata = ab8500_pdata->denc; + if (pdata == NULL) { + dev_err(&pdev->dev, "Denc platform data missing\n"); + return -EINVAL; + } + + device_data = kzalloc(sizeof(struct device_usage), GFP_KERNEL); + if (!device_data) { + dev_err(&pdev->dev, "Failed to allocate device data\n"); + return -ENOMEM; + } + device_data->pdev = pdev; + list_add_tail(&device_data->list, &device_list); + +#ifdef CONFIG_DEBUG_FS + debugfs_ab8500_denc_dir = debugfs_create_dir(pdev->name, NULL); + debugfs_ab8500_dump_regs_file = debugfs_create_file( + "dumpregs", S_IRUGO, + debugfs_ab8500_denc_dir, &pdev->dev, + &debugfs_ab8500_dump_regs_fops + ); +#endif /* CONFIG_DEBUG_FS */ + return ret; +} + +static int __devexit ab8500_denc_remove(struct platform_device *pdev) +{ + struct list_head *element; + struct device_usage *device_data; + + AB8500_DENC_TRACE(pdev); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(debugfs_ab8500_dump_regs_file); + debugfs_remove(debugfs_ab8500_denc_dir); +#endif /* CONFIG_DEBUG_FS */ + + list_for_each(element, &device_list) { + device_data = list_entry(element, struct device_usage, list); + if (device_data->pdev == pdev) { + list_del(element); + kzfree(device_data); + } + } + + return 0; +} + +static struct platform_driver ab8500_denc_driver = { + .probe = ab8500_denc_probe, + .remove = ab8500_denc_remove, + .driver = { + .name = "ab8500-denc", + }, +}; + +static void setup_27mhz(struct platform_device *pdev, bool enable) +{ + u8 data = ab8500_rreg(&pdev->dev, AB8500_SYS_ULP_CLK_CONF); + + AB8500_DENC_TRACE(pdev); + /* TODO: check if this field needs to be set */ + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_PD_ENA, + true); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_BUF_ENA, + enable); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_INV, + false); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_DE_IN, + false); + data = ab8500_set_fld(data, AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_STRE, + 1); + ab8500_wreg(&pdev->dev, AB8500_SYS_ULP_CLK_CONF, data); + + data = ab8500_rreg(&pdev->dev, AB8500_SYS_CLK_CTRL); + data = ab8500_set_fld(data, AB8500_SYS_CLK_CTRL, TVOUT_CLK_VALID, + enable); + data = ab8500_set_fld(data, AB8500_SYS_CLK_CTRL, TVOUT_PLL_ENA, + enable); + ab8500_wreg(&pdev->dev, AB8500_SYS_CLK_CTRL, data); +} + +static u32 map_tv_std(enum ab8500_denc_TV_std std) +{ + switch (std) { + case TV_STD_PAL_BDGHI: + return AB8500_DENC_CONF0_STD_PAL_BDGHI; + case TV_STD_PAL_N: + return AB8500_DENC_CONF0_STD_PAL_N; + case TV_STD_PAL_M: + return AB8500_DENC_CONF0_STD_PAL_M; + case TV_STD_NTSC_M: + return AB8500_DENC_CONF0_STD_NTSC_M; + default: + return 0; + } +} + +static u32 map_cr_filter(enum ab8500_denc_cr_filter_bandwidth bw) +{ + switch (bw) { + case TV_CR_NTSC_LOW_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_1MHZ; + case TV_CR_PAL_LOW_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_3MHZ; + case TV_CR_NTSC_HIGH_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_6MHZ; + case TV_CR_PAL_HIGH_DEF_FILTER: + return AB8500_DENC_CONF1_FLT_1_9MHZ; + default: + return 0; + } +} + +static u32 map_phase_rst_mode(enum ab8500_denc_phase_reset_mode mode) +{ + switch (mode) { + case TV_PHASE_RST_MOD_DISABLE: + return AB8500_DENC_CONF8_PH_RST_MODE_DISABLED; + case TV_PHASE_RST_MOD_FROM_PHASE_BUF: + return AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_PHASE_BUF; + case TV_PHASE_RST_MOD_FROM_INC_DFS: + return AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_INC_DFS; + case TV_PHASE_RST_MOD_RST: + return AB8500_DENC_CONF8_PH_RST_MODE_RESET; + default: + return 0; + } +} + +static u32 map_plug_time(enum ab8500_denc_plug_time time) +{ + switch (time) { + case TV_PLUG_TIME_0_5S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_0_5S; + case TV_PLUG_TIME_1S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_1S; + case TV_PLUG_TIME_1_5S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_1_5S; + case TV_PLUG_TIME_2S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_2S; + case TV_PLUG_TIME_2_5S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_2_5S; + case TV_PLUG_TIME_3S: + return AB8500_TVOUT_CTRL_PLUG_TV_TIME_3S; + default: + return 0; + } +} + +struct platform_device *ab8500_denc_get_device(void) +{ + struct list_head *element; + struct device_usage *device_data; + + pr_debug("%s\n", __func__); + list_for_each(element, &device_list) { + device_data = list_entry(element, struct device_usage, list); + if (!device_data->taken) { + device_data->taken = true; + return device_data->pdev; + } + } + return NULL; +} +EXPORT_SYMBOL(ab8500_denc_get_device); + +void ab8500_denc_put_device(struct platform_device *pdev) +{ + struct list_head *element; + struct device_usage *device_data; + + AB8500_DENC_TRACE(pdev); + list_for_each(element, &device_list) { + device_data = list_entry(element, struct device_usage, list); + if (device_data->pdev == pdev) + device_data->taken = false; + } +} +EXPORT_SYMBOL(ab8500_denc_put_device); + +void ab8500_denc_reset(struct platform_device *pdev, bool hard) +{ + AB8500_DENC_TRACE(pdev); + if (hard) { + u8 data = ab8500_rreg(&pdev->dev, AB8500_CTRL3); + /* reset start */ + ab8500_wreg(&pdev->dev, AB8500_CTRL3, + ab8500_set_fld(data, AB8500_CTRL3, RESET_DENC_N, 0) + ); + /* reset done */ + ab8500_wreg(&pdev->dev, AB8500_CTRL3, + ab8500_set_fld(data, AB8500_CTRL3, RESET_DENC_N, 1) + ); + } else { + ab8500_wr_fld(&pdev->dev, AB8500_DENC_CONF6, SOFT_RESET, 1); + mdelay(10); + } +} +EXPORT_SYMBOL(ab8500_denc_reset); + +void ab8500_denc_power_up(struct platform_device *pdev) +{ + setup_27mhz(pdev, true); +} +EXPORT_SYMBOL(ab8500_denc_power_up); + +void ab8500_denc_power_down(struct platform_device *pdev) +{ + setup_27mhz(pdev, false); +} +EXPORT_SYMBOL(ab8500_denc_power_down); + +void ab8500_denc_conf(struct platform_device *pdev, + struct ab8500_denc_conf *conf) +{ + u8 data; + + AB8500_DENC_TRACE(pdev); + + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF0, + AB8500_VAL2REG(AB8500_DENC_CONF0, STD, map_tv_std(conf->TV_std)) + | + AB8500_VAL2REG(AB8500_DENC_CONF0, SYNC, + conf->test_pattern ? AB8500_DENC_CONF0_SYNC_AUTO_TEST : + AB8500_DENC_CONF0_SYNC_F_BASED_SLAVE + ) + ); + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF1, + AB8500_VAL2REG(AB8500_DENC_CONF1, BLK_LI, + !conf->partial_blanking) + | + AB8500_VAL2REG(AB8500_DENC_CONF1, FLT, + map_cr_filter(conf->cr_filter)) + | + AB8500_VAL2REG(AB8500_DENC_CONF1, CO_KI, conf->suppress_col) + | + AB8500_VAL2REG(AB8500_DENC_CONF1, SETUP_MAIN, + conf->black_level_setup) + /* TODO: handle cc field: set to 0 now */ + ); + + data = ab8500_rreg(&pdev->dev, AB8500_DENC_CONF2); + data = ab8500_set_fld(data, AB8500_DENC_CONF2, N_INTRL, + conf->progressive); + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF2, data); + + ab8500_wreg(&pdev->dev, AB8500_DENC_CONF8, + AB8500_VAL2REG(AB8500_DENC_CONF8, PH_RST_MODE, + map_phase_rst_mode(conf->phase_reset_mode)) + | + AB8500_VAL2REG(AB8500_DENC_CONF8, VAL_422_MUX, + conf->act_output) + | + AB8500_VAL2REG(AB8500_DENC_CONF8, BLK_ALL, + conf->blank_all) + ); + data = ab8500_rreg(&pdev->dev, AB8500_TVOUT_CTRL); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, DAC_CTRL0, + conf->dac_enable); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, DAC_CTRL1, + conf->act_dc_output); + ab8500_wreg(&pdev->dev, AB8500_TVOUT_CTRL, data); + + /* no support for DDR in early versions */ + if (AB8500_REG2VAL(AB8500_REV, FULL_MASK, + ab8500_rreg(&pdev->dev, AB8500_REV)) > 0) + ab8500_denc_conf_ddr(pdev); +} +EXPORT_SYMBOL(ab8500_denc_conf); + +void ab8500_denc_conf_plug_detect(struct platform_device *pdev, + bool enable, bool load_RC, + enum ab8500_denc_plug_time time) +{ + u8 data; + + AB8500_DENC_TRACE(pdev); + data = ab8500_rreg(&pdev->dev, AB8500_TVOUT_CTRL); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, TV_PLUG_ON, enable); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, TV_LOAD_RC, load_RC); + data = ab8500_set_fld(data, AB8500_TVOUT_CTRL, PLUG_TV_TIME, + map_plug_time(time)); + ab8500_wreg(&pdev->dev, AB8500_TVOUT_CTRL, data); +} +EXPORT_SYMBOL(ab8500_denc_conf_plug_detect); + +void ab8500_denc_mask_int_plug_det(struct platform_device *pdev, bool plug, + bool unplug) +{ + u8 data = ab8500_rreg(&pdev->dev, AB8500_IT_MASK1); + + AB8500_DENC_TRACE(pdev); + data = ab8500_set_fld(data, AB8500_IT_MASK1, PLUG_TV_DET, plug); + data = ab8500_set_fld(data, AB8500_IT_MASK1, UNPLUG_TV_DET, unplug); + ab8500_wreg(&pdev->dev, AB8500_IT_MASK1, data); +} +EXPORT_SYMBOL(ab8500_denc_mask_int_plug_det); + +static void ab8500_denc_conf_ddr(struct platform_device *pdev) +{ + struct ab8500_platform_data *core_pdata; + struct ab8500_denc_platform_data *denc_pdata; + + AB8500_DENC_TRACE(pdev); + core_pdata = dev_get_platdata(pdev->dev.parent); + denc_pdata = core_pdata->denc; + ab8500_wreg(&pdev->dev, AB8500_TVOUT_CTRL2, + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, + DENC_DDR, denc_pdata->ddr_enable) | + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, SWAP_DDR_DATA_IN, + denc_pdata->ddr_little_endian)); +} + +#ifdef CONFIG_DEBUG_FS +static int debugfs_ab8500_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +#define DEBUG_BUF_SIZE 900 + +static ssize_t debugfs_ab8500_dump_regs(struct file *file, char __user *buf, + size_t count, loff_t *f_pos) +{ + int ret = 0; + size_t data_size = 0; + char buffer[DEBUG_BUF_SIZE]; + struct device *dev = file->private_data; + + data_size += sprintf(buffer + data_size, + "AB8500 DENC registers:\n" + "CTRL3 : 0x%04x = 0x%02x\n" + "SYSULPCLK_CONF: 0x%04x = 0x%02x\n" + "SYSCLK_CTRL : 0x%04x = 0x%02x\n" + "REGU_MISC1 : 0x%04x = 0x%02x\n" + "VAUX12_REGU : 0x%04x = 0x%02x\n" + "VAUX1_SEL1 : 0x%04x = 0x%02x\n" + "DENC_CONF0 : 0x%04x = 0x%02x\n" + "DENC_CONF1 : 0x%04x = 0x%02x\n" + "DENC_CONF2 : 0x%04x = 0x%02x\n" + "DENC_CONF6 : 0x%04x = 0x%02x\n" + "DENC_CONF8 : 0x%04x = 0x%02x\n" + "TVOUT_CTRL : 0x%04x = 0x%02x\n" + "TVOUT_CTRL2 : 0x%04x = 0x%02x\n" + "IT_MASK1 : 0x%04x = 0x%02x\n" + , + AB8500_CTRL3, ab8500_rreg(dev, AB8500_CTRL3), + AB8500_SYS_ULP_CLK_CONF, ab8500_rreg(dev, + AB8500_SYS_ULP_CLK_CONF), + AB8500_SYS_CLK_CTRL, ab8500_rreg(dev, AB8500_SYS_CLK_CTRL), + AB8500_REGU_MISC1, ab8500_rreg(dev, AB8500_REGU_MISC1), + AB8500_VAUX12_REGU, ab8500_rreg(dev, AB8500_VAUX12_REGU), + AB8500_VAUX1_SEL, ab8500_rreg(dev, AB8500_VAUX1_SEL), + AB8500_DENC_CONF0, ab8500_rreg(dev, AB8500_DENC_CONF0), + AB8500_DENC_CONF1, ab8500_rreg(dev, AB8500_DENC_CONF1), + AB8500_DENC_CONF2, ab8500_rreg(dev, AB8500_DENC_CONF2), + AB8500_DENC_CONF6, ab8500_rreg(dev, AB8500_DENC_CONF6), + AB8500_DENC_CONF8, ab8500_rreg(dev, AB8500_DENC_CONF8), + AB8500_TVOUT_CTRL, ab8500_rreg(dev, AB8500_TVOUT_CTRL), + AB8500_TVOUT_CTRL2, ab8500_rreg(dev, AB8500_TVOUT_CTRL2), + AB8500_IT_MASK1, ab8500_rreg(dev, AB8500_IT_MASK1) + ); + if (data_size >= DEBUG_BUF_SIZE) { + printk(KERN_EMERG "AB8500 DENC: Buffer overrun\n"); + ret = -EINVAL; + goto out; + } + + /* check if read done */ + if (*f_pos > data_size) + goto out; + + if (*f_pos + count > data_size) + count = data_size - *f_pos; + + if (copy_to_user(buf, buffer + *f_pos, count)) + ret = -EINVAL; + *f_pos += count; + ret = count; +out: + return ret; +} +#endif /* CONFIG_DEBUG_FS */ + +/* Module init */ +static int __init ab8500_denc_init(void) +{ + return platform_driver_register(&ab8500_denc_driver); +} +module_init(ab8500_denc_init); + +static void __exit ab8500_denc_exit(void) +{ + platform_driver_unregister(&ab8500_denc_driver); +} +module_exit(ab8500_denc_exit); + +MODULE_AUTHOR("Marcel Tunnissen "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ST-Ericsson AB8500 DENC driver"); diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index f16afb234ff..027d5205f23 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -57,7 +57,7 @@ #define SW_AVG_16 0x60 #define ADC_SW_CONV 0x04 #define EN_ICHAR 0x80 -#define BTEMP_PULL_UP 0x08 +#define BATTEMP_PULL_UP 0x04 #define EN_BUF 0x40 #define DIS_ZERO 0x00 #define GPADC_BUSY 0x01 @@ -130,16 +130,12 @@ static LIST_HEAD(ab8500_gpadc_list); * ab8500_gpadc_get() - returns a reference to the primary AB8500 GPADC * (i.e. the first GPADC in the instance list) */ -struct ab8500_gpadc *ab8500_gpadc_get(char *name) +struct ab8500_gpadc *ab8500_gpadc_get(void) { struct ab8500_gpadc *gpadc; + gpadc = list_first_entry(&ab8500_gpadc_list, struct ab8500_gpadc, node); - list_for_each_entry(gpadc, &ab8500_gpadc_list, node) { - if (!strcmp(name, dev_name(gpadc->dev))) - return gpadc; - } - - return ERR_PTR(-ENOENT); + return gpadc; } EXPORT_SYMBOL(ab8500_gpadc_get); diff --git a/drivers/mfd/ab8500-sysctrl.c b/drivers/mfd/ab8500-sysctrl.c index 392185965b3..e6de6a279a5 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -6,12 +6,29 @@ #include #include +#include +#include #include #include #include static struct device *sysctrl_dev; +void ab8500_power_off(void) +{ + sigset_t old; + sigset_t all; + + sigfillset(&all); + + if (!sigprocmask(SIG_BLOCK, &all, &old)) { + (void)ab8500_sysctrl_set(AB8500_STW4500CTRL1, + AB8500_STW4500CTRL1_SWOFF | + AB8500_STW4500CTRL1_SWRESET4500N); + (void)sigprocmask(SIG_SETMASK, &old, NULL); + } +} + static inline bool valid_bank(u8 bank) { return ((bank == AB8500_SYS_CTRL1_BLOCK) || @@ -50,7 +67,12 @@ int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value) static int __devinit ab8500_sysctrl_probe(struct platform_device *pdev) { + struct ab8500_platform_data *plat; + sysctrl_dev = &pdev->dev; + plat = dev_get_platdata(pdev->dev.parent); + if (plat->pm_power_off) + pm_power_off = ab8500_power_off; return 0; } diff --git a/drivers/misc/ab8500-pwm.c b/drivers/misc/ab8500-pwm.c index 35903154ca2..a1a3609c961 100644 --- a/drivers/misc/ab8500-pwm.c +++ b/drivers/misc/ab8500-pwm.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -26,8 +27,10 @@ struct pwm_device { struct device *dev; struct list_head node; + struct clk *clk; const char *label; unsigned int pwm_id; + bool clk_enabled; }; static LIST_HEAD(pwm_list); @@ -66,9 +69,17 @@ int pwm_enable(struct pwm_device *pwm) { int ret; + if (!pwm->clk_enabled) { + ret = clk_enable(pwm->clk); + if (ret < 0) { + dev_err(pwm->dev, "failed to enable sysclk\n"); + return ret; + } + pwm->clk_enabled = true; + } ret = abx500_mask_and_set_register_interruptible(pwm->dev, AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, - 1 << (pwm->pwm_id-1), ENABLE_PWM); + 1 << (pwm->pwm_id-1), 1 << (pwm->pwm_id-1)); if (ret < 0) dev_err(pwm->dev, "%s: Failed to disable PWM, Error %d\n", pwm->label, ret); @@ -83,9 +94,27 @@ void pwm_disable(struct pwm_device *pwm) ret = abx500_mask_and_set_register_interruptible(pwm->dev, AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, 1 << (pwm->pwm_id-1), DISABLE_PWM); + /* + * Workaround to set PWM in disable. + * If enable bit is not toggled the PWM might output 50/50 duty cycle + * even though it should be disabled + */ + ret &= abx500_mask_and_set_register_interruptible(pwm->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (pwm->pwm_id-1), + ENABLE_PWM << (pwm->pwm_id-1)); + ret &= abx500_mask_and_set_register_interruptible(pwm->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (pwm->pwm_id-1), DISABLE_PWM); + if (ret < 0) dev_err(pwm->dev, "%s: Failed to disable PWM, Error %d\n", pwm->label, ret); + if (pwm->clk_enabled) { + clk_disable(pwm->clk); + pwm->clk_enabled = false; + } + return; } EXPORT_SYMBOL(pwm_disable); @@ -115,6 +144,8 @@ EXPORT_SYMBOL(pwm_free); static int __devinit ab8500_pwm_probe(struct platform_device *pdev) { struct pwm_device *pwm; + int ret = 0; + /* * Nothing to be done in probe, this is required to get the * device which is required for ab8500 read and write @@ -128,14 +159,24 @@ static int __devinit ab8500_pwm_probe(struct platform_device *pdev) pwm->pwm_id = pdev->id; list_add_tail(&pwm->node, &pwm_list); platform_set_drvdata(pdev, pwm); + + pwm->clk = clk_get(pwm->dev, "sysclk"); + if (IS_ERR(pwm->clk)) { + dev_err(pwm->dev, "clock request failed\n"); + ret = PTR_ERR(pwm->clk); + kfree(pwm); + return ret; + } + pwm->clk_enabled = false; dev_dbg(pwm->dev, "pwm probe successful\n"); - return 0; + return ret; } static int __devexit ab8500_pwm_remove(struct platform_device *pdev) { struct pwm_device *pwm = platform_get_drvdata(pdev); list_del(&pwm->node); + clk_put(pwm->clk); dev_dbg(&pdev->dev, "pwm driver removed\n"); kfree(pwm); return 0; diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 57de051a74b..e167394bc15 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -235,6 +235,19 @@ config CHARGER_GPIO This driver can be build as a module. If so, the module will be called gpio-charger. +config AB8500_BM + bool "AB8500 Battery Management Driver" + depends on AB8500_CORE && AB8500_GPADC && ARCH_U8500 + help + Say Y to include support for AB8500 battery management. + +config AB8500_BATTERY_THERM_ON_BATCTRL + bool "Thermistor connected on BATCTRL ADC" + depends on AB8500_BM + help + Say Y to enable battery temperature measurements using + thermistor connected on BATCTRL ADC. + config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" depends on MFD_MAX8997 && REGULATOR_MAX8997 diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b4af13dd8b6..7f9f62dcd91 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -36,5 +36,6 @@ obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o +obj-$(CONFIG_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o ab8500_chargalg.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c new file mode 100644 index 00000000000..7d204b19742 --- /dev/null +++ b/drivers/power/ab8500_btemp.c @@ -0,0 +1,1080 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Battery temperature driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson + * Author: Karl Komierowski + * Author: Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VTVOUT_V 1800 + +#define BTEMP_THERMAL_LOW_LIMIT -10 +#define BTEMP_THERMAL_MED_LIMIT 0 +#define BTEMP_THERMAL_HIGH_LIMIT_52 52 +#define BTEMP_THERMAL_HIGH_LIMIT_57 57 +#define BTEMP_THERMAL_HIGH_LIMIT_62 62 + +#define BTEMP_BATCTRL_CURR_SRC_7UA 7 +#define BTEMP_BATCTRL_CURR_SRC_20UA 20 + +#define to_ab8500_btemp_device_info(x) container_of((x), \ + struct ab8500_btemp, btemp_psy); + +/** + * struct ab8500_btemp_interrupts - ab8500 interrupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_btemp_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab8500_btemp_events { + bool batt_rem; + bool btemp_high; + bool btemp_medhigh; + bool btemp_lowmed; + bool btemp_low; + bool ac_conn; + bool usb_conn; +}; + +struct ab8500_btemp_ranges { + int btemp_high_limit; + int btemp_med_limit; + int btemp_low_limit; +}; + +/** + * struct ab8500_btemp - ab8500 BTEMP device information + * @dev: Pointer to the structure device + * @chip_id: Chip-Id of the AB8500 + * @curr_source: What current source we use, in uA + * @bat_temp: Battery temperature in degree Celcius + * @prev_bat_temp Last dispatched battery temperature + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the ab8500_btemp platform data + * @bat: Pointer to the ab8500_bm platform data + * @btemp_psy: Structure for BTEMP specific battery properties + * @events: Structure for information about events triggered + * @btemp_ranges: Battery temperature range structure + * @btemp_wq: Work queue for measuring the temperature periodically + * @btemp_periodic_work: Work for measuring the temperature periodically + */ +struct ab8500_btemp { + struct device *dev; + u8 chip_id; + int curr_source; + int bat_temp; + int prev_bat_temp; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct ab8500_btemp_platform_data *pdata; + struct ab8500_bm_data *bat; + struct power_supply btemp_psy; + struct ab8500_btemp_events events; + struct ab8500_btemp_ranges btemp_ranges; + struct workqueue_struct *btemp_wq; + struct delayed_work btemp_periodic_work; +}; + +/* BTEMP power supply properties */ +static enum power_supply_property ab8500_btemp_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_TEMP, +}; + +/** + * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance + * @di: pointer to the ab8500_btemp structure + * @v_batctrl: measured batctrl voltage + * + * This function returns the battery resistance that is + * derived from the BATCTRL voltage. + * Returns value in Ohms. + */ +static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, + int v_batctrl) +{ + int rbs; + + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + /* + * For ABB cut1.0 and 1.1 BAT_CTRL is internally + * connected to 1.8V through a 450k resistor + */ + rbs = (450000 * (v_batctrl)) / (1800 - v_batctrl); + break; + default: + if (di->bat->adc_therm == ADC_THERM_BATCTRL) { + /* + * If the battery has internal NTC, we use the current + * source to calculate the resistance, 7uA or 20uA + */ + rbs = v_batctrl * 1000 / di->curr_source; + } else { + /* + * BAT_CTRL is internally + * connected to 1.8V through a 80k resistor + */ + rbs = (80000 * (v_batctrl)) / (1800 - v_batctrl); + } + break; + } + + return rbs; +} + +/** + * ab8500_btemp_read_batctrl_voltage() - measure batctrl voltage + * @di: pointer to the ab8500_btemp structure + * + * This function returns the voltage on BATCTRL. Returns value in mV. + */ +static int ab8500_btemp_read_batctrl_voltage(struct ab8500_btemp *di) +{ + int vbtemp; + static int prev; + + vbtemp = ab8500_gpadc_convert(di->gpadc, BAT_CTRL); + if (vbtemp < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value", + __func__); + return prev; + } + prev = vbtemp; + return vbtemp; +} + +/** + * ab8500_btemp_curr_source_enable() - enable/disable batctrl current source + * @di: pointer to the ab8500_btemp structure + * @enable: enable or disable the current source + * + * Enable or disable the current sources for the BatCtrl AD channel + */ +static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, + bool enable) +{ + int curr; + int ret = 0; + + /* + * BATCTRL current sources are included on AB8500 cut2.0 + * and future versions + */ + if (di->chip_id == AB8500_CUT1P0 || di->chip_id == AB8500_CUT1P1) + return 0; + + /* Only do this for batteries with internal NTC */ + if (di->bat->adc_therm == ADC_THERM_BATCTRL && enable) { + if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA) + curr = BAT_CTRL_7U_ENA; + else + curr = BAT_CTRL_20U_ENA; + + dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed setting cmp_force\n", + __func__); + return ret; + } + + /* + * We have to wait one 32kHz cycle before enabling + * the current source, since ForceBatCtrlCmpHigh needs + * to be written in a separate cycle + */ + udelay(32); + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH | curr); + if (ret) { + dev_err(di->dev, "%s failed enabling current source\n", + __func__); + goto disable_curr_source; + } + } else if (di->bat->adc_therm == ADC_THERM_BATCTRL && !enable) { + dev_dbg(di->dev, "Disable BATCTRL curr source\n"); + + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, + ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + goto disable_curr_source; + } + + /* Enable Pull-Up and comparator */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling PU and comp\n", + __func__); + goto enable_pu_comp; + } + + /* + * We have to wait one 32kHz cycle before disabling + * ForceBatCtrlCmpHigh since this needs to be written + * in a separate cycle + */ + udelay(32); + + /* Disable 'force comparator' */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + goto disable_force_comp; + } + } + return ret; + + /* + * We have to try unsetting FORCE_BAT_CTRL_CMP_HIGH one more time + * if we got an error above + */ +disable_curr_source: + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA, + ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA)); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + return ret; + } +enable_pu_comp: + /* Enable Pull-Up and comparator */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA, + BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling PU and comp\n", + __func__); + return ret; + } + +disable_force_comp: + /* + * We have to wait one 32kHz cycle before disabling + * ForceBatCtrlCmpHigh since this needs to be written + * in a separate cycle + */ + udelay(32); + + /* Disable 'force comparator' */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + return ret; + } + + return ret; +} + +/** + * ab8500_btemp_get_batctrl_res() - get battery resistance + * @di: pointer to the ab8500_btemp structure + * + * This function returns the battery pack identification resistance. + * Returns value in Ohms. + */ +static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) +{ + int ret; + int batctrl; + int res; + + /* + * BATCTRL current sources are included on AB8500 cut2.0 + * and future versions + */ + ret = ab8500_btemp_curr_source_enable(di, true); + if (ret) { + dev_err(di->dev, "%s curr source enabled failed\n", __func__); + return ret; + } + + batctrl = ab8500_btemp_read_batctrl_voltage(di); + res = ab8500_btemp_batctrl_volt_to_res(di, batctrl); + + ret = ab8500_btemp_curr_source_enable(di, false); + if (ret) { + dev_err(di->dev, "%s curr source disable failed\n", __func__); + return ret; + } + + dev_dbg(di->dev, "%s batctrl: %d res: %d ", + __func__, batctrl, res); + + return res; +} + +/** + * ab8500_btemp_res_to_temp() - resistance to temperature + * @di: pointer to the ab8500_btemp structure + * @tbl: pointer to the resiatance to temperature table + * @tbl_size: size of the resistance to temperature table + * @res: resistance to calculate the temperature from + * + * This function returns the battery temperature in degrees Celcius + * based on the NTC resistance. + */ +static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di, + const struct res_to_temp *tbl, int tbl_size, int res) +{ + int i, temp; + /* + * Calculate the formula for the straight line + * Simple interpolation if we are within + * the resistance table limits, extrapolate + * if resistance is outside the limits. + */ + if (res > tbl[0].resist) + i = 0; + else if (res <= tbl[tbl_size - 1].resist) + i = tbl_size - 2; + else { + i = 0; + while (!(res <= tbl[i].resist && + res > tbl[i + 1].resist)) + i++; + } + + temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) * + (res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); + return temp; +} + +/** + * ab8500_btemp_measure_temp() - measure battery temperature + * @di: pointer to the ab8500_btemp structure + * + * Returns battery temperature (on success) else the previous temperature + */ +static int ab8500_btemp_measure_temp(struct ab8500_btemp *di) +{ + int temp; + static int prev; + int rbat, rntc, vntc; + u8 id; + + id = di->bat->batt_id; + + if (di->bat->adc_therm == ADC_THERM_BATCTRL && + id != BATTERY_UNKNOWN) { + + rbat = ab8500_btemp_get_batctrl_res(di); + if (rbat < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", + __func__); + /* + * Return out-of-range temperature so that + * charging is stopped + */ + return BTEMP_THERMAL_LOW_LIMIT; + } + + temp = ab8500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rbat); + } else { + vntc = ab8500_gpadc_convert(di->gpadc, BTEMP_BALL); + if (vntc < 0) { + dev_err(di->dev, + "%s gpadc conversion failed," + " using previous value\n", __func__); + return prev; + } + /* + * The PCB NTC is sourced from VTVOUT via a 230kOhm + * resistor. + */ + rntc = 230000 * vntc / (VTVOUT_V - vntc); + + temp = ab8500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rntc); + prev = temp; + } + dev_dbg(di->dev, "Battery temperature is %d\n", temp); + return temp; +} + +/** + * ab8500_btemp_id() - Identify the connected battery + * @di: pointer to the ab8500_btemp structure + * + * This function will try to identify the battery by reading the ID + * resistor. Some brands use a combined ID resistor with a NTC resistor to + * both be able to identify and to read the temperature of it. + */ +static int ab8500_btemp_id(struct ab8500_btemp *di) +{ + int res; + u8 i; + + di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA; + di->bat->batt_id = BATTERY_UNKNOWN; + + res = ab8500_btemp_get_batctrl_res(di); + if (res < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", __func__); + return -ENXIO; + } + + /* BATTERY_UNKNOWN is defined on position 0, skip it! */ + for (i = BATTERY_UNKNOWN + 1; i < di->bat->n_btypes; i++) { + if ((res <= di->bat->bat_type[i].resis_high) && + (res >= di->bat->bat_type[i].resis_low)) { + dev_dbg(di->dev, "Battery detected on %s" + " low %d < res %d < high: %d" + " index: %d\n", + di->bat->adc_therm == ADC_THERM_BATCTRL ? + "BATCTRL" : "BATTEMP", + di->bat->bat_type[i].resis_low, res, + di->bat->bat_type[i].resis_high, i); + + di->bat->batt_id = i; + break; + } + } + + if (di->bat->batt_id == BATTERY_UNKNOWN) { + dev_warn(di->dev, "Battery identified as unknown" + ", resistance %d Ohm\n", res); + return -ENXIO; + } + + /* + * We only have to change current source if the + * detected type is Type 1, else we use the 7uA source + */ + if (di->bat->adc_therm == ADC_THERM_BATCTRL && di->bat->batt_id == 1) { + dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n"); + di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA; + } + + return di->bat->batt_id; +} + +/** + * ab8500_btemp_periodic_work() - Measuring the temperature periodically + * @work: pointer to the work_struct structure + * + * Work function for measuring the temperature periodically + */ +static void ab8500_btemp_periodic_work(struct work_struct *work) +{ + struct ab8500_btemp *di = container_of(work, + struct ab8500_btemp, btemp_periodic_work.work); + + di->bat_temp = ab8500_btemp_measure_temp(di); + + if (di->bat_temp != di->prev_bat_temp) { + di->prev_bat_temp = di->bat_temp; + power_supply_changed(&di->btemp_psy); + } + + /* Schedule a new measurement */ + queue_delayed_work(di->btemp_wq, + &di->btemp_periodic_work, + round_jiffies(20 * HZ)); +} + +/** + * ab8500_btemp_batctrlindb_handler() - battery removal detected + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_batctrlindb_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + dev_err(di->dev, "Battery removal detected!\n"); + + di->events.batt_rem = true; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_templow_handler() - battery temp lower than 10 degrees + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_templow_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + case AB8500_CUT2P0: + dev_dbg(di->dev, "Ignore false btemp low irq" + " for ABB cut 1.0, 1.1 and 2.0\n"); + + break; + default: + dev_crit(di->dev, "Battery temperature lower than -10deg c\n"); + + di->events.btemp_low = true; + di->events.btemp_high = false; + di->events.btemp_medhigh = false; + di->events.btemp_lowmed = false; + power_supply_changed(&di->btemp_psy); + + break; + } + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_temphigh_handler() - battery temp higher than max temp + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_temphigh_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_crit(di->dev, "Battery temperature is higher than MAX temp\n"); + + di->events.btemp_high = true; + di->events.btemp_medhigh = false; + di->events.btemp_lowmed = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_lowmed_handler() - battery temp between low and medium + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_lowmed_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_dbg(di->dev, "Battery temperature is between low and medium\n"); + + di->events.btemp_lowmed = true; + di->events.btemp_medhigh = false; + di->events.btemp_high = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_medhigh_handler() - battery temp between medium and high + * @irq: interrupt number + * @_di: void pointer that has to address of ab8500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_btemp_medhigh_handler(int irq, void *_di) +{ + struct ab8500_btemp *di = _di; + + dev_dbg(di->dev, "Battery temperature is between medium and high\n"); + + di->events.btemp_medhigh = true; + di->events.btemp_lowmed = false; + di->events.btemp_high = false; + di->events.btemp_low = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_btemp_periodic() - Periodic temperature measurements + * @di: pointer to the ab8500_btemp structure + * @enable: enable or disable periodic temperature measurements + * + * Starts of stops periodic temperature measurements. Periodic measurements + * should only be done when a charger is connected. + */ +static void ab8500_btemp_periodic(struct ab8500_btemp *di, + bool enable) +{ + dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n", + enable); + + if (enable) + queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0); + else + cancel_delayed_work_sync(&di->btemp_periodic_work); +} + +/** + * ab8500_btemp_get_temp() - get battery temperature + * @di: pointer to the ab8500_btemp structure + * + * Returns battery temperature + */ +static int ab8500_btemp_get_temp(struct ab8500_btemp *di) +{ + int temp = 0; + + /* + * The BTEMP events are not reliabe on AB8500 cut2.0 + * and prior versions + */ + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + case AB8500_CUT2P0: + temp = di->bat_temp * 10; + + break; + default: + if (di->events.btemp_low) { + if (temp > di->btemp_ranges.btemp_low_limit) + temp = di->btemp_ranges.btemp_low_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_high) { + if (temp < di->btemp_ranges.btemp_high_limit) + temp = di->btemp_ranges.btemp_high_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_lowmed) { + if (temp > di->btemp_ranges.btemp_med_limit) + temp = di->btemp_ranges.btemp_med_limit; + else + temp = di->bat_temp * 10; + } else if (di->events.btemp_medhigh) { + if (temp < di->btemp_ranges.btemp_med_limit) + temp = di->btemp_ranges.btemp_med_limit; + else + temp = di->bat_temp * 10; + } else + temp = di->bat_temp * 10; + + break; + } + return temp; +} + +/** + * ab8500_btemp_get_property() - get the btemp properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the btemp + * properties by reading the sysfs files. + * online: presence of the battery + * present: presence of the battery + * technology: battery technology + * temp: battery temperature + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_btemp_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_btemp *di; + + di = to_ab8500_btemp_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + if (di->events.batt_rem) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = di->bat->bat_type[di->bat->batt_id].name; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = ab8500_btemp_get_temp(di); + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_btemp *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_btemp_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && di->events.ac_conn) { + di->events.ac_conn = false; + if (!di->events.usb_conn) + ab8500_btemp_periodic(di, + false); + } + /* AC connected */ + else if (ret.intval && !di->events.ac_conn) { + di->events.ac_conn = true; + if (!di->events.usb_conn) + ab8500_btemp_periodic(di, true); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && di->events.usb_conn) { + di->events.usb_conn = false; + if (!di->events.ac_conn) + ab8500_btemp_periodic(di, + false); + } + /* USB connected */ + else if (ret.intval && !di->events.usb_conn) { + di->events.usb_conn = true; + if (!di->events.ac_conn) + ab8500_btemp_periodic(di, true); + } + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_btemp_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is pointing to the function pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in the external power + * supply to the btemp. + */ +static void ab8500_btemp_external_power_changed(struct power_supply *psy) +{ + struct ab8500_btemp *di = to_ab8500_btemp_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->btemp_psy, ab8500_btemp_get_ext_psy_data); +} + +/* ab8500 btemp driver interrupts and their respective isr */ +static struct ab8500_btemp_interrupts ab8500_btemp_irq[] = { + {"BAT_CTRL_INDB", ab8500_btemp_batctrlindb_handler}, + {"BTEMP_LOW", ab8500_btemp_templow_handler}, + {"BTEMP_HIGH", ab8500_btemp_temphigh_handler}, + {"BTEMP_LOW_MEDIUM", ab8500_btemp_lowmed_handler}, + {"BTEMP_MEDIUM_HIGH", ab8500_btemp_medhigh_handler}, +}; + +#if defined(CONFIG_PM) +static int ab8500_btemp_resume(struct platform_device *pdev) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + + if (di->events.ac_conn || di->events.usb_conn) + ab8500_btemp_periodic(di, true); + + return 0; +} + +static int ab8500_btemp_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + + if (di->events.ac_conn || di->events.usb_conn) + ab8500_btemp_periodic(di, false); + + return 0; +} +#else +#define ab8500_btemp_suspend NULL +#define ab8500_btemp_resume NULL +#endif + +static int __devexit ab8500_btemp_remove(struct platform_device *pdev) +{ + struct ab8500_btemp *di = platform_get_drvdata(pdev); + int i, irq; + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + free_irq(irq, di); + } + + /* Delete the work queue */ + destroy_workqueue(di->btemp_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->btemp_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab8500_btemp_probe(struct platform_device *pdev) +{ + int irq, i, ret = 0; + u8 val; + struct ab8500_platform_data *plat; + + struct ab8500_btemp *di = + kzalloc(sizeof(struct ab8500_btemp), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get(); + + plat = dev_get_platdata(di->parent->dev); + + /* get btemp specific platform data */ + if (!plat->btemp) { + dev_err(di->dev, "no btemp platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->btemp; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + /* BTEMP supply */ + di->btemp_psy.name = "ab8500_btemp"; + di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->btemp_psy.properties = ab8500_btemp_props; + di->btemp_psy.num_properties = ARRAY_SIZE(ab8500_btemp_props); + di->btemp_psy.get_property = ab8500_btemp_get_property; + di->btemp_psy.supplied_to = di->pdata->supplied_to; + di->btemp_psy.num_supplicants = di->pdata->num_supplicants; + di->btemp_psy.external_power_changed = + ab8500_btemp_external_power_changed; + + + /* Create a work queue for the btemp */ + di->btemp_wq = + create_singlethread_workqueue("ab8500_btemp_wq"); + if (di->btemp_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for measuring temperature periodically */ + INIT_DELAYED_WORK_DEFERRABLE(&di->btemp_periodic_work, + ab8500_btemp_periodic_work); + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(di->dev); + if (ret < 0) { + dev_err(di->dev, "failed to get chip ID\n"); + goto free_btemp_wq; + } + di->chip_id = ret; + dev_dbg(di->dev, "AB8500 CID is: 0x%02x\n", + di->chip_id); + + /* Identify the battery */ + if (ab8500_btemp_id(di) < 0) + dev_warn(di->dev, "failed to identify the battery\n"); + + /* Set BTEMP thermal limits. Low and Med are fixed */ + di->btemp_ranges.btemp_low_limit = BTEMP_THERMAL_LOW_LIMIT; + di->btemp_ranges.btemp_med_limit = BTEMP_THERMAL_MED_LIMIT; + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_BTEMP_HIGH_TH, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + goto free_btemp_wq; + } + switch (val) { + case BTEMP_HIGH_TH_57_0: + case BTEMP_HIGH_TH_57_1: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_57; + break; + case BTEMP_HIGH_TH_52: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_52; + break; + case BTEMP_HIGH_TH_62: + di->btemp_ranges.btemp_high_limit = + BTEMP_THERMAL_HIGH_LIMIT_62; + break; + } + + /* Measure temperature once initially */ + di->bat_temp = ab8500_btemp_measure_temp(di); + + /* Register BTEMP power supply class */ + ret = power_supply_register(di->dev, &di->btemp_psy); + if (ret) { + dev_err(di->dev, "failed to register BTEMP psy\n"); + goto free_btemp_wq; + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_btemp_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_btemp_irq[i].name, di); + + if (ret) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_btemp_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_btemp_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + return ret; + +free_irq: + power_supply_unregister(&di->btemp_psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name); + free_irq(irq, di); + } +free_btemp_wq: + destroy_workqueue(di->btemp_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_btemp_driver = { + .probe = ab8500_btemp_probe, + .remove = __devexit_p(ab8500_btemp_remove), + .suspend = ab8500_btemp_suspend, + .resume = ab8500_btemp_resume, + .driver = { + .name = "ab8500-btemp", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_btemp_init(void) +{ + return platform_driver_register(&ab8500_btemp_driver); +} + +static void __exit ab8500_btemp_exit(void) +{ + platform_driver_unregister(&ab8500_btemp_driver); +} + +subsys_initcall_sync(ab8500_btemp_init); +module_exit(ab8500_btemp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); +MODULE_ALIAS("platform:ab8500-btemp"); +MODULE_DESCRIPTION("AB8500 battery temperature driver"); diff --git a/drivers/power/ab8500_chargalg.c b/drivers/power/ab8500_chargalg.c new file mode 100644 index 00000000000..2137f467064 --- /dev/null +++ b/drivers/power/ab8500_chargalg.c @@ -0,0 +1,1833 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Charging algorithm driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson + * Author: Karl Komierowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Watchdog kick interval */ +#define CHG_WD_INTERVAL (60 * HZ) + +/* End-of-charge criteria counter */ +#define EOC_COND_CNT 10 + +#define to_ab8500_chargalg_device_info(x) container_of((x), \ + struct ab8500_chargalg, chargalg_psy); + +enum ab8500_chargers { + NO_CHG, + AC_CHG, + USB_CHG, +}; + +struct ab8500_chargalg_charger_info { + enum ab8500_chargers conn_chg; + enum ab8500_chargers prev_conn_chg; + enum ab8500_chargers online_chg; + enum ab8500_chargers prev_online_chg; + enum ab8500_chargers charger_type; + bool usb_chg_ok; + bool ac_chg_ok; + int usb_volt; + int usb_curr; + int ac_volt; + int ac_curr; +}; + +struct ab8500_chargalg_suspension_status { + bool suspended_change; + bool ac_suspended; + bool usb_suspended; +}; + +struct ab8500_chargalg_battery_data { + int temp; + int volt; + int avg_curr; + int inst_curr; + int percent; +}; + +enum ab8500_chargalg_states { + STATE_HANDHELD_INIT, + STATE_HANDHELD, + STATE_CHG_NOT_OK_INIT, + STATE_CHG_NOT_OK, + STATE_HW_TEMP_PROTECT_INIT, + STATE_HW_TEMP_PROTECT, + STATE_NORMAL_INIT, + STATE_NORMAL, + STATE_MAINTENANCE_A_INIT, + STATE_MAINTENANCE_A, + STATE_MAINTENANCE_B_INIT, + STATE_MAINTENANCE_B, + STATE_TEMP_UNDEROVER_INIT, + STATE_TEMP_UNDEROVER, + STATE_TEMP_LOWHIGH_INIT, + STATE_TEMP_LOWHIGH, + STATE_SUSPENDED_INIT, + STATE_SUSPENDED, + STATE_OVV_PROTECT_INIT, + STATE_OVV_PROTECT, + STATE_SAFETY_TIMER_EXPIRED_INIT, + STATE_SAFETY_TIMER_EXPIRED, + STATE_BATT_REMOVED_INIT, + STATE_BATT_REMOVED, + STATE_WD_EXPIRED_INIT, + STATE_WD_EXPIRED, +}; + +static const char *states[] = { + "HANDHELD_INIT", + "HANDHELD", + "CHG_NOT_OK_INIT", + "CHG_NOT_OK", + "HW_TEMP_PROTECT_INIT", + "HW_TEMP_PROTECT", + "NORMAL_INIT", + "NORMAL", + "MAINTENANCE_A_INIT", + "MAINTENANCE_A", + "MAINTENANCE_B_INIT", + "MAINTENANCE_B", + "TEMP_UNDEROVER_INIT", + "TEMP_UNDEROVER", + "TEMP_LOWHIGH_INIT", + "TEMP_LOWHIGH", + "SUSPENDED_INIT", + "SUSPENDED", + "OVV_PROTECT_INIT", + "OVV_PROTECT", + "SAFETY_TIMER_EXPIRED_INIT", + "SAFETY_TIMER_EXPIRED", + "BATT_REMOVED_INIT", + "BATT_REMOVED", + "WD_EXPIRED_INIT", + "WD_EXPIRED", +}; + +struct ab8500_chargalg_events { + bool batt_unknown; + bool mainextchnotok; + bool batt_ovv; + bool batt_rem; + bool btemp_underover; + bool btemp_lowhigh; + bool main_thermal_prot; + bool usb_thermal_prot; + bool main_ovv; + bool vbus_ovv; + bool usbchargernotok; + bool safety_timer_expired; + bool maintenance_timer_expired; + bool ac_wd_expired; + bool usb_wd_expired; + bool ac_cv_active; + bool usb_cv_active; +}; + +/** + * struct ab8500_charge_curr_maximization - Charger maximization parameters + * @original_iset: the non optimized/maximised charger current + * @current_iset: the charging current used at this moment + * @test_delta_i: the delta between the current we want to charge and the + current that is really going into the battery + * @condition_cnt: number of iterations needed before a new charger current + is set + * @max_current: maximum charger current + * @level: tells in how many steps the charging current has been + increased + */ +struct ab8500_charge_curr_maximization { + int original_iset; + int current_iset; + int test_delta_i; + int condition_cnt; + int max_current; + u8 level; +}; + +enum maxim_ret { + MAXIM_RET_NOACTION, + MAXIM_RET_CHANGE, + MAXIM_RET_IBAT_TOO_HIGH, +}; + +/** + * struct ab8500_chargalg - ab8500 Charging algorithm device information + * @dev: pointer to the structure device + * @charge_status: battery operating status + * @eoc_cnt: counter used to determine end-of_charge + * @maintenance_chg: indicate if maintenance charge is active + * @t_hyst_norm temperature hysteresis when the temperature has been + * over or under normal limits + * @t_hyst_lowhigh temperature hysteresis when the temperature has been + * over or under the high or low limits + * @charge_state: current state of the charging algorithm + * @ccm charging current maximization parameters + * @chg_info: information about connected charger types + * @batt_data: data of the battery + * @susp_status: current charger suspension status + * @parent: pointer to the struct ab8500 + * @pdata: pointer to the ab8500_chargalg platform data + * @bat: pointer to the ab8500_bm platform data + * @chargalg_psy: structure that holds the battery properties exposed by + * the charging algorithm + * @events: structure for information about events triggered + * @chargalg_wq: work queue for running the charging algorithm + * @chargalg_periodic_work: work to run the charging algorithm periodically + * @chargalg_wd_work: work to kick the charger watchdog periodically + * @chargalg_work: work to run the charging algorithm instantly + * @safety_timer: charging safety timer + * @maintenance_timer: maintenance charging timer + * @chargalg_kobject: structure of type kobject + */ +struct ab8500_chargalg { + struct device *dev; + int charge_status; + int eoc_cnt; + bool maintenance_chg; + int t_hyst_norm; + int t_hyst_lowhigh; + enum ab8500_chargalg_states charge_state; + struct ab8500_charge_curr_maximization ccm; + struct ab8500_chargalg_charger_info chg_info; + struct ab8500_chargalg_battery_data batt_data; + struct ab8500_chargalg_suspension_status susp_status; + struct ab8500 *parent; + struct ab8500_chargalg_platform_data *pdata; + struct ab8500_bm_data *bat; + struct power_supply chargalg_psy; + struct ux500_charger *ac_chg; + struct ux500_charger *usb_chg; + struct ab8500_chargalg_events events; + struct workqueue_struct *chargalg_wq; + struct delayed_work chargalg_periodic_work; + struct delayed_work chargalg_wd_work; + struct work_struct chargalg_work; + struct timer_list safety_timer; + struct timer_list maintenance_timer; + struct kobject chargalg_kobject; +}; + +/* Main battery properties */ +static enum power_supply_property ab8500_chargalg_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, +}; + +/** + * ab8500_chargalg_safety_timer_expired() - Expiration of the safety timer + * @data: pointer to the ab8500_chargalg structure + * + * This function gets called when the safety timer for the charger + * expires + */ +static void ab8500_chargalg_safety_timer_expired(unsigned long data) +{ + struct ab8500_chargalg *di = (struct ab8500_chargalg *) data; + dev_err(di->dev, "Safety timer expired\n"); + di->events.safety_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_maintenance_timer_expired() - Expiration of + * the maintenance timer + * @i: pointer to the ab8500_chargalg structure + * + * This function gets called when the maintenence timer + * expires + */ +static void ab8500_chargalg_maintenance_timer_expired(unsigned long data) +{ + + struct ab8500_chargalg *di = (struct ab8500_chargalg *) data; + dev_dbg(di->dev, "Maintenance timer expired\n"); + di->events.maintenance_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_state_to() - Change charge state + * @di: pointer to the ab8500_chargalg structure + * + * This function gets called when a charge state change should occur + */ +static void ab8500_chargalg_state_to(struct ab8500_chargalg *di, + enum ab8500_chargalg_states state) +{ + dev_dbg(di->dev, + "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n", + di->charge_state == state ? "NO" : "YES", + di->charge_state, + states[di->charge_state], + state, + states[state]); + + di->charge_state = state; +} + +/** + * ab8500_chargalg_check_charger_connection() - Check charger connection change + * @di: pointer to the ab8500_chargalg structure + * + * This function will check if there is a change in the charger connection + * and change charge state accordingly. AC has precedence over USB. + */ +static int ab8500_chargalg_check_charger_connection(struct ab8500_chargalg *di) +{ + if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg || + di->susp_status.suspended_change) { + /* + * Charger state changed or suspension + * has changed since last update + */ + if ((di->chg_info.conn_chg & AC_CHG) && + !di->susp_status.ac_suspended) { + dev_dbg(di->dev, "Charging source is AC\n"); + if (di->chg_info.charger_type != AC_CHG) { + di->chg_info.charger_type = AC_CHG; + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + } else if ((di->chg_info.conn_chg & USB_CHG) && + !di->susp_status.usb_suspended) { + dev_dbg(di->dev, "Charging source is USB\n"); + di->chg_info.charger_type = USB_CHG; + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else if (di->chg_info.conn_chg && + (di->susp_status.ac_suspended || + di->susp_status.usb_suspended)) { + dev_dbg(di->dev, "Charging is suspended\n"); + di->chg_info.charger_type = NO_CHG; + ab8500_chargalg_state_to(di, STATE_SUSPENDED_INIT); + } else { + dev_dbg(di->dev, "Charging source is OFF\n"); + di->chg_info.charger_type = NO_CHG; + ab8500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + di->chg_info.prev_conn_chg = di->chg_info.conn_chg; + di->susp_status.suspended_change = false; + } + return di->chg_info.conn_chg; +} + +/** + * ab8500_chargalg_start_safety_timer() - Start charging safety timer + * @di: pointer to the ab8500_chargalg structure + * + * The safety timer is used to avoid overcharging of old or bad batteries. + * There are different timers for AC and USB + */ +static void ab8500_chargalg_start_safety_timer(struct ab8500_chargalg *di) +{ + unsigned long timer_expiration = 0; + + switch (di->chg_info.charger_type) { + case AC_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->main_safety_tmr_h * 3600 * HZ)); + break; + + case USB_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->usb_safety_tmr_h * 3600 * HZ)); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } + + di->events.safety_timer_expired = false; + di->safety_timer.expires = timer_expiration; + if (!timer_pending(&di->safety_timer)) + add_timer(&di->safety_timer); + else + mod_timer(&di->safety_timer, timer_expiration); +} + +/** + * ab8500_chargalg_stop_safety_timer() - Stop charging safety timer + * @di: pointer to the ab8500_chargalg structure + * + * The safety timer is stopped whenever the NORMAL state is exited + */ +static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di) +{ + di->events.safety_timer_expired = false; + del_timer(&di->safety_timer); +} + +/** + * ab8500_chargalg_start_maintenance_timer() - Start charging maintenance timer + * @di: pointer to the ab8500_chargalg structure + * @duration: duration of ther maintenance timer in hours + * + * The maintenance timer is used to maintain the charge in the battery once + * the battery is considered full. These timers are chosen to match the + * discharge curve of the battery + */ +static void ab8500_chargalg_start_maintenance_timer(struct ab8500_chargalg *di, + int duration) +{ + unsigned long timer_expiration; + + /* Convert from hours to jiffies */ + timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ)); + + di->events.maintenance_timer_expired = false; + di->maintenance_timer.expires = timer_expiration; + if (!timer_pending(&di->maintenance_timer)) + add_timer(&di->maintenance_timer); + else + mod_timer(&di->maintenance_timer, timer_expiration); +} + +/** + * ab8500_chargalg_stop_maintenance_timer() - Stop maintenance timer + * @di: pointer to the ab8500_chargalg structure + * + * The maintenance timer is stopped whenever maintenance ends or when another + * state is entered + */ +static void ab8500_chargalg_stop_maintenance_timer(struct ab8500_chargalg *di) +{ + di->events.maintenance_timer_expired = false; + del_timer(&di->maintenance_timer); +} + +/** + * ab8500_chargalg_kick_watchdog() - Kick charger watchdog + * @di: pointer to the ab8500_chargalg structure + * + * The charger watchdog have to be kicked periodically whenever the charger is + * on, else the ABB will reset the system + */ +static int ab8500_chargalg_kick_watchdog(struct ab8500_chargalg *di) +{ + /* Check if charger exists and kick watchdog if charging */ + if (di->ac_chg && di->ac_chg->ops.kick_wd && + di->chg_info.online_chg & AC_CHG) + return di->ac_chg->ops.kick_wd(di->ac_chg); + else if (di->usb_chg && di->usb_chg->ops.kick_wd && + di->chg_info.online_chg & USB_CHG) + return di->usb_chg->ops.kick_wd(di->usb_chg); + + return -ENXIO; +} + +/** + * ab8500_chargalg_ac_en() - Turn on/off the AC charger + * @di: pointer to the ab8500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The AC charger will be turned on/off with the requested charge voltage and + * current + */ +static int ab8500_chargalg_ac_en(struct ab8500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->ac_chg || !di->ac_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->ac_chg->max_out_volt) + vset = min(vset, di->ac_chg->max_out_volt); + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset); +} + +/** + * ab8500_chargalg_usb_en() - Turn on/off the USB charger + * @di: pointer to the ab8500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The USB charger will be turned on/off with the requested charge voltage and + * current + */ +static int ab8500_chargalg_usb_en(struct ab8500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->usb_chg || !di->usb_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->usb_chg->max_out_volt) + vset = min(vset, di->usb_chg->max_out_volt); + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset); +} + +/** + * ab8500_chargalg_update_chg_curr() - Update charger current + * @di: pointer to the ab8500_chargalg structure + * @iset: requested charger output current + * + * The charger output current will be updated for the charger + * that is currently in use + */ +static int ab8500_chargalg_update_chg_curr(struct ab8500_chargalg *di, + int iset) +{ + /* Check if charger exists and update current if charging */ + if (di->ac_chg && di->ac_chg->ops.update_curr && + di->chg_info.charger_type & AC_CHG) + return di->ac_chg->ops.update_curr(di->ac_chg, iset); + else if (di->usb_chg && di->usb_chg->ops.update_curr && + di->chg_info.charger_type & USB_CHG) + return di->usb_chg->ops.update_curr(di->usb_chg, iset); + + return -ENXIO; +} + +/** + * ab8500_chargalg_stop_charging() - Stop charging + * @di: pointer to the ab8500_chargalg structure + * + * This function is called from any state where charging should be stopped. + * All charging is disabled and all status parameters and timers are changed + * accordingly + */ +static void ab8500_chargalg_stop_charging(struct ab8500_chargalg *di) +{ + ab8500_chargalg_ac_en(di, false, 0, 0); + ab8500_chargalg_usb_en(di, false, 0, 0); + ab8500_chargalg_stop_safety_timer(di); + ab8500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(&di->chargalg_psy); +} + +/** + * ab8500_chargalg_start_charging() - Start the charger + * @di: pointer to the ab8500_chargalg structure + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * A charger will be enabled depending on the requested charger type that was + * detected previously. + */ +static void ab8500_chargalg_start_charging(struct ab8500_chargalg *di, + int vset, int iset) +{ + switch (di->chg_info.charger_type) { + case AC_CHG: + dev_dbg(di->dev, + "AC parameters: Vset %d, Ich %d\n", vset, iset); + ab8500_chargalg_usb_en(di, false, 0, 0); + ab8500_chargalg_ac_en(di, true, vset, iset); + break; + + case USB_CHG: + dev_dbg(di->dev, + "USB parameters: Vset %d, Ich %d\n", vset, iset); + ab8500_chargalg_ac_en(di, false, 0, 0); + ab8500_chargalg_usb_en(di, true, vset, iset); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } +} + +/** + * ab8500_chargalg_check_temp() - Check battery temperature ranges + * @di: pointer to the ab8500_chargalg structure + * + * The battery temperature is checked against the predefined limits and the + * charge state is changed accordingly + */ +static void ab8500_chargalg_check_temp(struct ab8500_chargalg *di) +{ + if (di->batt_data.temp > (di->bat->temp_low + di->t_hyst_norm) && + di->batt_data.temp < (di->bat->temp_high - di->t_hyst_norm)) { + /* Temp OK! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = 0; + } else { + if (((di->batt_data.temp >= di->bat->temp_high) && + (di->batt_data.temp < + (di->bat->temp_over - di->t_hyst_lowhigh))) || + ((di->batt_data.temp > + (di->bat->temp_under + di->t_hyst_lowhigh)) && + (di->batt_data.temp <= di->bat->temp_low))) { + /* TEMP minor!!!!! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = true; + di->t_hyst_norm = di->bat->temp_hysteresis; + di->t_hyst_lowhigh = 0; + } else if (di->batt_data.temp <= di->bat->temp_under || + di->batt_data.temp >= di->bat->temp_over) { + /* TEMP major!!!!! */ + di->events.btemp_underover = true; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = di->bat->temp_hysteresis; + } else { + /* Within hysteresis */ + dev_dbg(di->dev, "Within hysteresis limit temp: %d " + "hyst_lowhigh %d, hyst normal %d\n", + di->batt_data.temp, di->t_hyst_lowhigh, + di->t_hyst_norm); + } + } +} + +/** + * ab8500_chargalg_check_charger_health() - Check charger health + * @di: pointer to the ab8500_chargalg structure + * + * Charger voltage and current is checked against maximum limits + */ +static void ab8500_chargalg_check_charger_health(struct ab8500_chargalg *di) +{ + if (di->chg_info.usb_volt > di->bat->chg_params->usb_volt_max || + di->chg_info.usb_curr > di->bat->chg_params->usb_curr_max) + di->chg_info.usb_chg_ok = false; + else + di->chg_info.usb_chg_ok = true; + + if (di->chg_info.ac_volt > di->bat->chg_params->ac_volt_max || + di->chg_info.ac_curr > di->bat->chg_params->ac_curr_max) + di->chg_info.ac_chg_ok = false; + else + di->chg_info.ac_chg_ok = true; + +} + +/** + * ab8500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled + * @di: pointer to the ab8500_chargalg structure + * + * End-of-charge criteria is fulfilled when the battery voltage is above a + * certain limit and the battery current is below a certain limit for a + * predefined number of consecutive seconds. If true, the battery is full + */ +static void ab8500_chargalg_end_of_charge(struct ab8500_chargalg *di) +{ + if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING && + di->charge_state == STATE_NORMAL && + !di->maintenance_chg && (di->batt_data.volt >= + di->bat->bat_type[di->bat->batt_id].termination_vol || + di->events.usb_cv_active || di->events.ac_cv_active) && + di->batt_data.avg_curr < + di->bat->bat_type[di->bat->batt_id].termination_curr && + di->batt_data.avg_curr > 0) { + if (++di->eoc_cnt >= EOC_COND_CNT) { + di->eoc_cnt = 0; + di->charge_status = POWER_SUPPLY_STATUS_FULL; + di->maintenance_chg = true; + dev_dbg(di->dev, "EOC reached!\n"); + power_supply_changed(&di->chargalg_psy); + } else { + dev_dbg(di->dev, + " EOC limit reached for the %d" + " time, out of %d before EOC\n", + di->eoc_cnt, + EOC_COND_CNT); + } + } else { + di->eoc_cnt = 0; + } +} + +static void init_maxim_chg_curr(struct ab8500_chargalg *di) +{ + di->ccm.original_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.current_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.test_delta_i = di->bat->maxi->charger_curr_step; + di->ccm.max_current = di->bat->maxi->chg_curr; + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.level = 0; +} + +/** + * ab8500_chargalg_chg_curr_maxim - increases the charger current to + * compensate for the system load + * @di pointer to the ab8500_chargalg structure + * + * This maximization function is used to raise the charger current to get the + * battery current as close to the optimal value as possible. The battery + * current during charging is affected by the system load + */ +static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) +{ + int delta_i; + + if (!di->bat->maxi->ena_maxi) + return MAXIM_RET_NOACTION; + + delta_i = di->ccm.original_iset - di->batt_data.inst_curr; + dev_dbg(di->dev, "[CHARGALG_OPTI_DATA], Ucv ,%d, Dcv ,%d, cnt ,%d," + " cur iset ,%d, avg i ,%d, Ibat inst: ,%d,mA, Orig iset: ,%d, " + "delta_i ,%d, Max ,%d,mA" + " AC chg curr: ,%d,mA" + " USB chg curr: ,%d,mA\n", + di->events.usb_cv_active, + di->events.ac_cv_active, + di->ccm.condition_cnt, + di->ccm.current_iset, + di->batt_data.avg_curr, + di->batt_data.inst_curr, + di->ccm.original_iset, + delta_i, + di->ccm.max_current, + di->chg_info.ac_curr, + di->chg_info.usb_curr); + + if ((di->batt_data.inst_curr > di->ccm.original_iset)) { + dev_dbg(di->dev, " Maximization Ibat (%dmA) too high" + " (limit %dmA) (current iset: %dmA)!\n", + di->batt_data.inst_curr, di->ccm.original_iset, + di->ccm.current_iset); + + if (di->ccm.current_iset == di->ccm.original_iset) + return MAXIM_RET_NOACTION; + + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset = di->ccm.original_iset; + di->ccm.level = 0; + + return MAXIM_RET_IBAT_TOO_HIGH; + } + + /* Make sure first we are actually charging.. */ + if (di->charge_status != POWER_SUPPLY_STATUS_CHARGING || + (di->events.usb_cv_active || di->events.ac_cv_active)) { + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.level = 0; + di->ccm.current_iset = di->ccm.original_iset; + return MAXIM_RET_NOACTION; + } + + if (delta_i > di->ccm.test_delta_i && + (di->ccm.current_iset + di->ccm.test_delta_i) < + di->ccm.max_current) { + if (di->ccm.condition_cnt-- == 0) { + /* Increse the iset with cco.test_delta_i */ + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset += di->ccm.test_delta_i; + di->ccm.level++; + dev_dbg(di->dev, " Maximization needed, increase" + " with %d mA to %dmA (Optimal ibat: %d)" + " Level %d\n", + di->ccm.test_delta_i, + di->ccm.current_iset, + di->ccm.original_iset, + di->ccm.level); + return MAXIM_RET_CHANGE; + } else { + dev_dbg(di->dev, " Maximization, wait for" + " counter, to go %d\n", di->ccm.condition_cnt); + return MAXIM_RET_NOACTION; + } + } else { + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + return MAXIM_RET_NOACTION; + } +} + +static void handle_maxim_chg_curr(struct ab8500_chargalg *di) +{ + enum maxim_ret ret; + + ret = ab8500_chargalg_chg_curr_maxim(di); + switch (ret) { + case MAXIM_RET_CHANGE: + ab8500_chargalg_update_chg_curr(di, di->ccm.current_iset); + break; + case MAXIM_RET_IBAT_TOO_HIGH: + ab8500_chargalg_update_chg_curr(di, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + break; + + case MAXIM_RET_NOACTION: + default: + /* Do nothing..*/ + break; + } +} + +static int ab8500_chargalg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_chargalg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_chargalg_device_info(psy); + + /* For all psy where the driver name appears in any supplied_to */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + /* Initialize chargers if not already done */ + if (!di->ac_chg && + ext->type == POWER_SUPPLY_TYPE_MAINS) + di->ac_chg = psy_to_ux500_charger(ext); + else if (!di->usb_chg && + ext->type == POWER_SUPPLY_TYPE_USB) + di->usb_chg = psy_to_ux500_charger(ext); + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + /* Battery present */ + if (ret.intval) + di->events.batt_rem = false; + /* Battery removed */ + else + di->events.batt_rem = true; + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~AC_CHG; + } + /* AC connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= AC_CHG; + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~USB_CHG; + } + /* USB connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= USB_CHG; + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_ONLINE: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC offline */ + if (!ret.intval && + (di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~AC_CHG; + } + /* AC online */ + else if (ret.intval && + !(di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= AC_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB offline */ + if (!ret.intval && + (di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~USB_CHG; + } + /* USB online */ + else if (ret.intval && + !(di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= USB_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_HEALTH: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.mainextchnotok = true; + di->events.main_thermal_prot = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.ac_wd_expired = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.main_thermal_prot = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.main_thermal_prot = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.main_ovv = true; + di->events.mainextchnotok = false; + di->events.main_thermal_prot = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.main_thermal_prot = false; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + default: + break; + } + break; + + case POWER_SUPPLY_TYPE_USB: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.usbchargernotok = true; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.usb_wd_expired = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.usb_thermal_prot = true; + di->events.usbchargernotok = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.vbus_ovv = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + default: + break; + } + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_volt = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.ac_cv_active = true; + else + di->events.ac_cv_active = false; + + break; + case POWER_SUPPLY_TYPE_USB: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.usb_cv_active = true; + else + di->events.usb_cv_active = false; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->events.batt_unknown = false; + else + di->events.batt_unknown = true; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TEMP: + di->batt_data.temp = ret.intval / 10; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.inst_curr = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + di->batt_data.avg_curr = ret.intval / 1000; + break; + case POWER_SUPPLY_PROP_CAPACITY: + di->batt_data.percent = ret.intval; + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_chargalg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void ab8500_chargalg_external_power_changed(struct power_supply *psy) +{ + struct ab8500_chargalg *di = to_ab8500_chargalg_device_info(psy); + + /* + * Trigger execution of the algorithm instantly and read + * all power_supply properties there instead + */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_algorithm() - Main function for the algorithm + * @di: pointer to the ab8500_chargalg structure + * + * This is the main control function for the charging algorithm. + * It is called periodically or when something happens that will + * trigger a state change + */ +static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) +{ + int charger_status; + + /* Collect data from all power_supply class devices */ + class_for_each_device(power_supply_class, NULL, + &di->chargalg_psy, ab8500_chargalg_get_ext_psy_data); + + ab8500_chargalg_end_of_charge(di); + ab8500_chargalg_check_temp(di); + ab8500_chargalg_check_charger_health(di); + charger_status = ab8500_chargalg_check_charger_connection(di); + + /* + * First check if we have a charger connected. + * Also we don't allow charging of unknown batteries if configured + * this way + */ + if (!charger_status || + (di->events.batt_unknown && !di->bat->chg_unknown_bat)) { + if (di->charge_state != STATE_HANDHELD) { + di->events.safety_timer_expired = false; + ab8500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + } + + /* If suspended, we should not continue checking the flags */ + else if (di->charge_state == STATE_SUSPENDED_INIT || + di->charge_state == STATE_SUSPENDED) { + /* We don't do anything here, just don,t continue */ + } + + /* Safety timer expiration */ + else if (di->events.safety_timer_expired) { + if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED) + ab8500_chargalg_state_to(di, + STATE_SAFETY_TIMER_EXPIRED_INIT); + } + /* + * Check if any interrupts has occured + * that will prevent us from charging + */ + + /* Battery removed */ + else if (di->events.batt_rem) { + if (di->charge_state != STATE_BATT_REMOVED) + ab8500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT); + } + /* Main or USB charger not ok. */ + else if (di->events.mainextchnotok || di->events.usbchargernotok) { + if (di->charge_state != STATE_CHG_NOT_OK) + ab8500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT); + } + /* VBUS, Main or VBAT OVV. */ + else if (di->events.vbus_ovv || + di->events.main_ovv || + di->events.batt_ovv || + !di->chg_info.usb_chg_ok || + !di->chg_info.ac_chg_ok) { + if (di->charge_state != STATE_OVV_PROTECT) + ab8500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT); + } + /* USB Thermal, stop charging */ + else if (di->events.main_thermal_prot || + di->events.usb_thermal_prot) { + if (di->charge_state != STATE_HW_TEMP_PROTECT) + ab8500_chargalg_state_to(di, + STATE_HW_TEMP_PROTECT_INIT); + } + /* Battery temp over/under */ + else if (di->events.btemp_underover) { + if (di->charge_state != STATE_TEMP_UNDEROVER) + ab8500_chargalg_state_to(di, + STATE_TEMP_UNDEROVER_INIT); + } + /* Watchdog expired */ + else if (di->events.ac_wd_expired || + di->events.usb_wd_expired) { + if (di->charge_state != STATE_WD_EXPIRED) + ab8500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT); + } + /* Battery temp high/low */ + else if (di->events.btemp_lowhigh) { + if (di->charge_state != STATE_TEMP_LOWHIGH) + ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT); + } + + dev_dbg(di->dev, + "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d " + "State %s Active_chg %d Chg_status %d AC %d USB %d " + "AC_online %d USB_online %d " + "AC_CV %d USB_CV %d\n", + di->batt_data.volt, + di->batt_data.avg_curr, + di->batt_data.inst_curr, + di->batt_data.temp, + di->batt_data.percent, + di->maintenance_chg, + states[di->charge_state], + di->chg_info.charger_type, + di->charge_status, + di->chg_info.conn_chg & AC_CHG, + di->chg_info.conn_chg & USB_CHG, + di->chg_info.online_chg & AC_CHG, + di->chg_info.online_chg & USB_CHG, + di->events.ac_cv_active, + di->events.usb_cv_active); + + switch (di->charge_state) { + case STATE_HANDHELD_INIT: + ab8500_chargalg_stop_charging(di); + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + ab8500_chargalg_state_to(di, STATE_HANDHELD); + /* Intentional fallthrough */ + + case STATE_HANDHELD: + break; + + case STATE_SUSPENDED_INIT: + if (di->susp_status.ac_suspended) + ab8500_chargalg_ac_en(di, false, 0, 0); + if (di->susp_status.usb_suspended) + ab8500_chargalg_usb_en(di, false, 0, 0); + ab8500_chargalg_stop_safety_timer(di); + ab8500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + ab8500_chargalg_state_to(di, STATE_SUSPENDED); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_SUSPENDED: + /* CHARGING is suspended */ + break; + + case STATE_BATT_REMOVED_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_BATT_REMOVED); + /* Intentional fallthrough */ + + case STATE_BATT_REMOVED: + if (!di->events.batt_rem) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_HW_TEMP_PROTECT_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT); + /* Intentional fallthrough */ + + case STATE_HW_TEMP_PROTECT: + if (!di->events.main_thermal_prot && + !di->events.usb_thermal_prot) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_OVV_PROTECT_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_OVV_PROTECT); + /* Intentional fallthrough */ + + case STATE_OVV_PROTECT: + if (!di->events.vbus_ovv && + !di->events.main_ovv && + !di->events.batt_ovv && + di->chg_info.usb_chg_ok && + di->chg_info.ac_chg_ok) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_CHG_NOT_OK_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_CHG_NOT_OK); + /* Intentional fallthrough */ + + case STATE_CHG_NOT_OK: + if (!di->events.mainextchnotok && + !di->events.usbchargernotok) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_SAFETY_TIMER_EXPIRED_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED); + /* Intentional fallthrough */ + + case STATE_SAFETY_TIMER_EXPIRED: + /* We exit this state when charger is removed */ + break; + + case STATE_NORMAL_INIT: + ab8500_chargalg_start_charging(di, + di->bat->bat_type[di->bat->batt_id].normal_vol_lvl, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + ab8500_chargalg_state_to(di, STATE_NORMAL); + ab8500_chargalg_start_safety_timer(di); + ab8500_chargalg_stop_maintenance_timer(di); + init_maxim_chg_curr(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->eoc_cnt = 0; + di->maintenance_chg = false; + power_supply_changed(&di->chargalg_psy); + + break; + + case STATE_NORMAL: + handle_maxim_chg_curr(di); + + if (di->charge_status == POWER_SUPPLY_STATUS_FULL && + di->maintenance_chg) + ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A_INIT); + + break; + + case STATE_MAINTENANCE_A_INIT: + ab8500_chargalg_stop_safety_timer(di); + ab8500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_chg_timer_h); + ab8500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_a_cur_lvl); + ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_A: + if (di->events.maintenance_timer_expired) { + ab8500_chargalg_stop_maintenance_timer(di); + ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT); + } + break; + + case STATE_MAINTENANCE_B_INIT: + ab8500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_chg_timer_h); + ab8500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_b_cur_lvl); + ab8500_chargalg_state_to(di, STATE_MAINTENANCE_B); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_B: + if (di->events.maintenance_timer_expired) { + ab8500_chargalg_stop_maintenance_timer(di); + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + break; + + case STATE_TEMP_LOWHIGH_INIT: + ab8500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].low_high_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].low_high_cur_lvl); + ab8500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + ab8500_chargalg_state_to(di, STATE_TEMP_LOWHIGH); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_TEMP_LOWHIGH: + if (!di->events.btemp_lowhigh) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_WD_EXPIRED_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_WD_EXPIRED); + /* Intentional fallthrough */ + + case STATE_WD_EXPIRED: + if (!di->events.ac_wd_expired && + !di->events.usb_wd_expired) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_TEMP_UNDEROVER_INIT: + ab8500_chargalg_stop_charging(di); + ab8500_chargalg_state_to(di, STATE_TEMP_UNDEROVER); + /* Intentional fallthrough */ + + case STATE_TEMP_UNDEROVER: + if (!di->events.btemp_underover) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + } + + /* Start charging directly if the new state is a charge state */ + if (di->charge_state == STATE_NORMAL_INIT || + di->charge_state == STATE_MAINTENANCE_A_INIT || + di->charge_state == STATE_MAINTENANCE_B_INIT) + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * ab8500_chargalg_periodic_work() - Periodic work for the algorithm + * @work: pointer to the work_struct structure + * + * Work queue function for the charging algorithm + */ +static void ab8500_chargalg_periodic_work(struct work_struct *work) +{ + struct ab8500_chargalg *di = container_of(work, + struct ab8500_chargalg, chargalg_periodic_work.work); + + ab8500_chargalg_algorithm(di); + + /* + * If a charger is connected then the battery has to be monitored + * frequently, else the work can be delayed. + */ + if (di->chg_info.conn_chg) + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_charging * HZ); + else + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_not_charging * HZ); +} + +/** + * ab8500_chargalg_wd_work() - periodic work to kick the charger watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog + */ +static void ab8500_chargalg_wd_work(struct work_struct *work) +{ + int ret; + struct ab8500_chargalg *di = container_of(work, + struct ab8500_chargalg, chargalg_wd_work.work); + + dev_dbg(di->dev, "ab8500_chargalg_wd_work\n"); + + ret = ab8500_chargalg_kick_watchdog(di); + if (ret < 0) + dev_err(di->dev, "failed to kick watchdog\n"); + + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, CHG_WD_INTERVAL); +} + +/** + * ab8500_chargalg_work() - Work to run the charging algorithm instantly + * @work: pointer to the work_struct structure + * + * Work queue function for calling the charging algorithm + */ +static void ab8500_chargalg_work(struct work_struct *work) +{ + struct ab8500_chargalg *di = container_of(work, + struct ab8500_chargalg, chargalg_work); + + ab8500_chargalg_algorithm(di); +} + +/** + * ab8500_chargalg_get_property() - get the chargalg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * chargalg properties by reading the sysfs files. + * status: charging/discharging/full/unknown + * health: health of the battery + * Returns error code in case of failure else 0 on success + */ +static int ab8500_chargalg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_chargalg *di; + + di = to_ab8500_chargalg_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (di->events.batt_ovv) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (di->events.btemp_underover) { + if (di->batt_data.temp <= di->bat->temp_under) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + } else { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } + break; + default: + return -EINVAL; + } + return 0; +} + +/* Exposure to the sysfs interface */ + +/** + * ab8500_chargalg_sysfs_charger() - sysfs store operations + * @kobj: pointer to the struct kobject + * @attr: pointer to the struct attribute + * @buf: buffer that holds the parameter passed from userspace + * @length: length of the parameter passed + * + * Returns length of the buffer(input taken from user space) on success + * else error code on failure + * The operation to be performed on passing the parameters from the user space. + */ +static ssize_t ab8500_chargalg_sysfs_charger(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t length) +{ + struct ab8500_chargalg *di = container_of(kobj, + struct ab8500_chargalg, chargalg_kobject); + long int param; + int ac_usb; + int ret; + char entry = *attr->name; + + switch (entry) { + case 'c': + ret = strict_strtol(buf, 10, ¶m); + if (ret < 0) + return ret; + + ac_usb = param; + switch (ac_usb) { + case 0: + /* Disable charging */ + di->susp_status.ac_suspended = true; + di->susp_status.usb_suspended = true; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 1: + /* Enable AC Charging */ + di->susp_status.ac_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 2: + /* Enable USB charging */ + di->susp_status.usb_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + default: + dev_info(di->dev, "Wrong input\n" + "Enter 0. Disable AC/USB Charging\n" + "1. Enable AC charging\n" + "2. Enable USB Charging\n"); + }; + break; + }; + return strlen(buf); +} + +static struct attribute ab8500_chargalg_en_charger = \ +{ + .name = "chargalg", + .mode = S_IWUGO, +}; + +static struct attribute *ab8500_chargalg_chg[] = { + &ab8500_chargalg_en_charger, + NULL +}; + +const struct sysfs_ops ab8500_chargalg_sysfs_ops = { + .store = ab8500_chargalg_sysfs_charger, +}; + +static struct kobj_type ab8500_chargalg_ktype = { + .sysfs_ops = &ab8500_chargalg_sysfs_ops, + .default_attrs = ab8500_chargalg_chg, +}; + +/** + * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function removes the entry in sysfs. + */ +static void ab8500_chargalg_sysfs_exit(struct ab8500_chargalg *di) +{ + kobject_del(&di->chargalg_kobject); +} + +/** + * ab8500_chargalg_sysfs_init() - init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function adds an entry in sysfs. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_chargalg_sysfs_init(struct ab8500_chargalg *di) +{ + int ret = 0; + + ret = kobject_init_and_add(&di->chargalg_kobject, + &ab8500_chargalg_ktype, + NULL, "ab8500_chargalg"); + if (ret < 0) + dev_err(di->dev, "failed to create sysfs entry\n"); + + return ret; +} +/* Exposure to the sysfs interface <> */ + +#if defined(CONFIG_PM) +static int ab8500_chargalg_resume(struct platform_device *pdev) +{ + struct ab8500_chargalg *di = platform_get_drvdata(pdev); + + /* Kick charger watchdog if charging (any charger online) */ + if (di->chg_info.online_chg) + queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0); + + /* + * Run the charging algorithm directly to be sure we don't + * do it too seldom + */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + return 0; +} + +static int ab8500_chargalg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_chargalg *di = platform_get_drvdata(pdev); + + if (di->chg_info.online_chg) + cancel_delayed_work_sync(&di->chargalg_wd_work); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + + return 0; +} +#else +#define ab8500_chargalg_suspend NULL +#define ab8500_chargalg_resume NULL +#endif + +static int __devexit ab8500_chargalg_remove(struct platform_device *pdev) +{ + struct ab8500_chargalg *di = platform_get_drvdata(pdev); + + /* sysfs interface to enable/disbale charging from user space */ + ab8500_chargalg_sysfs_exit(di); + + /* Delete the work queue */ + destroy_workqueue(di->chargalg_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->chargalg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab8500_chargalg_probe(struct platform_device *pdev) +{ + struct ab8500_platform_data *plat; + int ret = 0; + + struct ab8500_chargalg *di = + kzalloc(sizeof(struct ab8500_chargalg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + + plat = dev_get_platdata(di->parent->dev); + + /* get chargalg specific platform data */ + if (!plat->chargalg) { + dev_err(di->dev, "no chargalg platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->chargalg; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + /* chargalg supply */ + di->chargalg_psy.name = "ab8500_chargalg"; + di->chargalg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->chargalg_psy.properties = ab8500_chargalg_props; + di->chargalg_psy.num_properties = ARRAY_SIZE(ab8500_chargalg_props); + di->chargalg_psy.get_property = ab8500_chargalg_get_property; + di->chargalg_psy.supplied_to = di->pdata->supplied_to; + di->chargalg_psy.num_supplicants = di->pdata->num_supplicants; + di->chargalg_psy.external_power_changed = + ab8500_chargalg_external_power_changed; + + /* Initilialize safety timer */ + init_timer(&di->safety_timer); + di->safety_timer.function = ab8500_chargalg_safety_timer_expired; + di->safety_timer.data = (unsigned long) di; + + /* Initilialize maintenance timer */ + init_timer(&di->maintenance_timer); + di->maintenance_timer.function = + ab8500_chargalg_maintenance_timer_expired; + di->maintenance_timer.data = (unsigned long) di; + + /* Create a work queue for the chargalg */ + di->chargalg_wq = + create_singlethread_workqueue("ab8500_chargalg_wq"); + if (di->chargalg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for chargalg */ + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_periodic_work, + ab8500_chargalg_periodic_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_wd_work, + ab8500_chargalg_wd_work); + + /* Init work for chargalg */ + INIT_WORK(&di->chargalg_work, ab8500_chargalg_work); + + /* To detect charger at startup */ + di->chg_info.prev_conn_chg = -1; + + /* Register chargalg power supply class */ + ret = power_supply_register(di->dev, &di->chargalg_psy); + if (ret) { + dev_err(di->dev, "failed to register chargalg psy\n"); + goto free_chargalg_wq; + } + + platform_set_drvdata(pdev, di); + + /* sysfs interface to enable/disable charging from user space */ + ret = ab8500_chargalg_sysfs_init(di); + if (ret) { + dev_err(di->dev, "failed to create sysfs entry\n"); + goto free_psy; + } + + /* Run the charging algorithm */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + return ret; + +free_psy: + power_supply_unregister(&di->chargalg_psy); +free_chargalg_wq: + destroy_workqueue(di->chargalg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_chargalg_driver = { + .probe = ab8500_chargalg_probe, + .remove = __devexit_p(ab8500_chargalg_remove), + .suspend = ab8500_chargalg_suspend, + .resume = ab8500_chargalg_resume, + .driver = { + .name = "ab8500-chargalg", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_chargalg_init(void) +{ + return platform_driver_register(&ab8500_chargalg_driver); +} + +static void __exit ab8500_chargalg_exit(void) +{ + platform_driver_unregister(&ab8500_chargalg_driver); +} + +module_init(ab8500_chargalg_init); +module_exit(ab8500_chargalg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab8500-chargalg"); +MODULE_DESCRIPTION("AB8500 battery temperature driver"); diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c new file mode 100644 index 00000000000..194ff9d23d7 --- /dev/null +++ b/drivers/power/ab8500_charger.c @@ -0,0 +1,2368 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Charger driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson + * Author: Karl Komierowski + * Author: Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Charger constants */ +#define NO_PW_CONN 0 +#define AC_PW_CONN 1 +#define USB_PW_CONN 2 + +#define MAIN_WDOG_ENA 0x01 +#define MAIN_WDOG_KICK 0x02 +#define MAIN_WDOG_DIS 0x00 +#define CHARG_WD_KICK 0x01 +#define MAIN_CH_ENA 0x01 +#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02 +#define USB_CH_ENA 0x01 +#define USB_CHG_NO_OVERSHOOT_ENA_N 0x02 +#define MAIN_CH_DET 0x01 +#define MAIN_CH_CV_ON 0x04 +#define USB_CH_CV_ON 0x08 +#define VBUS_DET_DBNC100 0x02 +#define VBUS_DET_DBNC1 0x01 +#define OTP_ENABLE_WD 0x01 + +#define LED_INDICATOR_PWM_ENA 0x01 +#define LED_INDICATOR_PWM_DIS 0x00 +#define LED_IND_CUR_5MA 0x04 +#define LED_INDICATOR_PWM_DUTY_252_256 0xBF + +/* HW failure constants */ +#define MAIN_CH_TH_PROT 0x02 +#define VBUS_CH_NOK 0x08 +#define USB_CH_TH_PROT 0x02 +#define VBUS_OVV_TH 0x01 +#define MAIN_CH_NOK 0x01 +#define VBUS_DET 0x80 + +/* UsbLineStatus register bit masks */ +#define AB8500_USB_LINK_STATUS 0x78 +#define AB8500_STD_HOST_SUSP 0x18 + +/* Watchdog timeout constant */ +#define WD_TIMER 0x30 /* 4min */ +#define WD_KICK_INTERVAL (60 * HZ) + +/* Lowest charger voltage is 3.39V -> 0x4E */ +#define LOW_VOLT_REG 0x4E + +/* UsbLineStatus register - usb types */ +enum ab8500_charger_link_status { + USB_STAT_NOT_CONFIGURED, + USB_STAT_STD_HOST_NC, + USB_STAT_STD_HOST_C_NS, + USB_STAT_STD_HOST_C_S, + USB_STAT_HOST_CHG_NM, + USB_STAT_HOST_CHG_HS, + USB_STAT_HOST_CHG_HS_CHIRP, + USB_STAT_DEDICATED_CHG, + USB_STAT_ACA_RID_A, + USB_STAT_ACA_RID_B, + USB_STAT_ACA_RID_C_NM, + USB_STAT_ACA_RID_C_HS, + USB_STAT_ACA_RID_C_HS_CHIRP, + USB_STAT_HM_IDGND, + USB_STAT_RESERVED, + USB_STAT_NOT_VALID_LINK, +}; + +enum ab8500_usb_state { + AB8500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */ + AB8500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */ + AB8500_BM_USB_STATE_CONFIGURED, + AB8500_BM_USB_STATE_SUSPEND, + AB8500_BM_USB_STATE_RESUME, + AB8500_BM_USB_STATE_MAX, +}; + +#define to_ab8500_charger_usb_device_info(x) container_of((x), \ + struct ab8500_charger, usb_chg) +#define to_ab8500_charger_ac_device_info(x) container_of((x), \ + struct ab8500_charger, ac_chg) + +/** + * struct ab8500_charger_interrupts - ab8500 interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_charger_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab8500_charger_info { + int charger_connected; + int charger_online; + int charger_voltage; + int cv_active; + bool wd_expired; +}; + +struct ab8500_charger_event_flags { + bool mainextchnotok; + bool main_thermal_prot; + bool usb_thermal_prot; + bool vbus_ovv; + bool usbchargernotok; + bool chgwdexp; +}; + +struct ab8500_charger_usb_state { + bool usb_changed; + int usb_current; + enum ab8500_usb_state state; + spinlock_t usb_lock; +}; + +/** + * struct ab8500_charger - ab8500 Charger device information + * @dev: Pointer to the structure device + * @chip_id: Chip-Id of the AB8500 + * @max_usb_in_curr: Max USB charger input current + * @vbus_detected: VBUS detected + * @vbus_detected_start: + * VBUS detected during startup + * @ac_conn: This will be true when the AC charger has been plugged + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the ab8500_charger platform data + * @bat: Pointer to the ab8500_bm platform data + * @flags: Structure for information about events triggered + * @usb_state: Structure for usb stack information + * @ac_chg: AC charger power supply + * @usb_chg: USB charger power supply + * @ac: Structure that holds the AC charger properties + * @usb: Structure that holds the USB charger properties + * @charger_wq: Work queue for the IRQs and checking HW state + * @check_hw_failure_work: Work for checking HW state + * @kick_wd_work: Work for kicking the charger watchdog in case + * of ABB rev 1.* due to the watchog logic bug + * @ac_work: Work for checking AC charger connection + * @detect_usb_type_work: Work for detecting the USB type connected + * @usb_link_status_work: Work for checking the new USB link status + * @usb_state_changed_work: Work for checking USB state + * @check_usbchgnotok_work: Work for checking USB charger not ok status + * @check_main_thermal_prot_work: + * Work for checking Main thermal status + * @check_usb_thermal_prot_work: + * Work for checking USB thermal status + */ +struct ab8500_charger { + struct device *dev; + u8 chip_id; + int max_usb_in_curr; + bool vbus_detected; + bool vbus_detected_start; + bool ac_conn; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct ab8500_charger_platform_data *pdata; + struct ab8500_bm_data *bat; + struct ab8500_charger_event_flags flags; + struct ab8500_charger_usb_state usb_state; + struct ux500_charger ac_chg; + struct ux500_charger usb_chg; + struct ab8500_charger_info ac; + struct ab8500_charger_info usb; + struct workqueue_struct *charger_wq; + struct delayed_work check_hw_failure_work; + struct delayed_work kick_wd_work; + struct work_struct ac_work; + struct work_struct detect_usb_type_work; + struct work_struct usb_link_status_work; + struct work_struct usb_state_changed_work; + struct work_struct check_usbchgnotok_work; + struct work_struct check_main_thermal_prot_work; + struct work_struct check_usb_thermal_prot_work; +}; + +/* + * TODO: This variable is static in order to get information + * about maximum current and USB state from the USB driver + * This should be solved in a better way + */ +static struct ab8500_charger *static_di; + +/* AC properties */ +static enum power_supply_property ab8500_charger_ac_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/* USB properties */ +static enum power_supply_property ab8500_charger_usb_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/** + * ab8500_charger_get_ac_voltage() - get ac charger voltage + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger voltage (on success) + */ +static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->ac.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed,\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_ac_cv() - check if the main charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_ac_cv(struct ab8500_charger *di) +{ + u8 val; + int ret = 0; + + /* Only check CV mode if the charger is online */ + if (di->ac.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & MAIN_CH_CV_ON) + ret = 1; + else + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_get_vbus_voltage() - get vbus voltage + * @di: pointer to the ab8500_charger structure + * + * This function returns the vbus voltage. + * Returns vbus voltage (on success) + */ +static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->usb.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, VBUS_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_get_usb_current() - get usb charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the usb charger current. + * Returns usb current (on success) and error code on failure + */ +static int ab8500_charger_get_usb_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->usb.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, USB_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_get_ac_current() - get ac charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the ac charger current. + * Returns ac current (on success) and error code on failure. + */ +static int ab8500_charger_get_ac_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->ac.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_usb_cv() - check if the usb charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_usb_cv(struct ab8500_charger *di) +{ + int ret; + u8 val; + + /* Only check CV mode if the charger is online */ + if (di->usb.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & USB_CH_CV_ON) + ret = 1; + else + ret = 0; + } else { + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_detect_chargers() - Detect the connected chargers + * @di: pointer to the ab8500_charger structure + * + * Returns the type of charger connected. + * For USB it will not mean we can actually charge from it + * but that there is a USB cable connected that we have to + * identify. This is used during startup when we don't get + * interrupts of the charger detection + * + * Returns an integer value, that means, + * NO_PW_CONN no power supply is connected + * AC_PW_CONN if the AC power supply is connected + * USB_PW_CONN if the USB power supply is connected + * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected + */ +static int ab8500_charger_detect_chargers(struct ab8500_charger *di) +{ + int result = NO_PW_CONN; + int ret; + u8 val; + + /* Check for AC charger */ + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + if (val & MAIN_CH_DET) + result = AC_PW_CONN; + + /* Check for USB charger */ + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + if (val & (VBUS_DET_DBNC100 | VBUS_DET_DBNC1)) + result |= USB_PW_CONN; + + return result; +} + +/** + * ab8500_charger_max_usb_curr() - get the max curr for the USB type + * @di: pointer to the ab8500_charger structure + * @link_status: the identified USB type + * + * Get the maximum current that is allowed to be drawn from the host + * based on the USB type. + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, + enum ab8500_charger_link_status link_status) +{ + int ret = 0; + + switch (link_status) { + case USB_STAT_STD_HOST_NC: + case USB_STAT_STD_HOST_C_NS: + case USB_STAT_STD_HOST_C_S: + dev_dbg(di->dev, "USB Type - Standard host is " + "detected through USB driver\n"); + ret = -1; + break; + case USB_STAT_HOST_CHG_HS_CHIRP: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + case USB_STAT_HOST_CHG_HS: + case USB_STAT_ACA_RID_C_HS: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9; + break; + case USB_STAT_ACA_RID_A: + /* + * Dedicated charger level minus maximum current accessory + * can consume (300mA). Closest level is 1100mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P1; + break; + case USB_STAT_ACA_RID_B: + /* + * Dedicated charger level minus 120mA (20mA for ACA and + * 100mA for potential accessory). Closest level is 1300mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3; + break; + case USB_STAT_DEDICATED_CHG: + case USB_STAT_HOST_CHG_NM: + case USB_STAT_ACA_RID_C_HS_CHIRP: + case USB_STAT_ACA_RID_C_NM: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5; + break; + case USB_STAT_HM_IDGND: + case USB_STAT_NOT_CONFIGURED: + case USB_STAT_RESERVED: + case USB_STAT_NOT_VALID_LINK: + dev_err(di->dev, "USB Type - Charging not allowed\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + default: + dev_err(di->dev, "USB Type - Unknown\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + }; + + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: 0x%02x", + link_status, di->max_usb_in_curr); + + return ret; +} + +/** + * ab8500_charger_read_usb_type() - read the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_read_usb_type(struct ab8500_charger *di) +{ + int ret; + u8 val; + + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + /* get the USB type */ + val = (val & AB8500_USB_LINK_STATUS) >> 3; + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/** + * ab8500_charger_detect_usb_type() - get the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_detect_usb_type(struct ab8500_charger *di) +{ + int i, ret; + u8 val; + + /* + * On getting the VBUS rising edge detect interrupt there + * is a 250ms delay after which the register UsbLineStatus + * is filled with valid data. + */ + for (i = 0; i < 10; i++) { + msleep(250); + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, + &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + /* + * Until the IT source register is read the UsbLineStatus + * register is not updated, hence doing the same + * Revisit this: + */ + + /* get the USB type */ + val = (val & AB8500_USB_LINK_STATUS) >> 3; + if (val) + break; + } + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/* + * This array maps the raw hex value to charger voltage used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_voltage_map[] = { + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3875 , + 3900 , + 3925 , + 3950 , + 3975 , + 4000 , + 4025 , + 4050 , + 4060 , + 4070 , + 4080 , + 4090 , + 4100 , + 4110 , + 4120 , + 4130 , + 4140 , + 4150 , + 4160 , + 4170 , + 4180 , + 4190 , + 4200 , + 4210 , + 4220 , + 4230 , + 4240 , + 4250 , + 4260 , + 4270 , + 4280 , + 4290 , + 4300 , + 4310 , + 4320 , + 4330 , + 4340 , + 4350 , + 4360 , + 4370 , + 4380 , + 4390 , + 4400 , + 4410 , + 4420 , + 4430 , + 4440 , + 4450 , + 4460 , + 4470 , + 4480 , + 4490 , + 4500 , + 4510 , + 4520 , + 4530 , + 4540 , + 4550 , + 4560 , + 4570 , + 4580 , + 4590 , + 4600 , +}; + +/* + * This array maps the raw hex value to charger current used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_current_map[] = { + 100 , + 200 , + 300 , + 400 , + 500 , + 600 , + 700 , + 800 , + 900 , + 1000 , + 1100 , + 1200 , + 1300 , + 1400 , + 1500 , +}; + +static int ab8500_voltage_to_regval(int voltage) +{ + int i; + + /* Special case for voltage below 3.5V */ + if (voltage < ab8500_charger_voltage_map[0]) + return LOW_VOLT_REG; + + for (i = 1; i < ARRAY_SIZE(ab8500_charger_voltage_map); i++) { + if (voltage < ab8500_charger_voltage_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_voltage_map) - 1; + if (voltage == ab8500_charger_voltage_map[i]) + return i; + else + return -1; +} + +static int ab8500_current_to_regval(int curr) +{ + int i; + + if (curr < ab8500_charger_current_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_charger_current_map); i++) { + if (curr < ab8500_charger_current_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_current_map) - 1; + if (curr == ab8500_charger_current_map[i]) + return i; + else + return -1; +} + +/** + * ab8500_charger_get_usb_cur() - get usb current + * @di: pointer to the ab8500_charger structre + * + * The usb stack provides the maximum current that can be drawn from + * the standard usb host. This will be in mA. + * This function converts current in mA to a value that can be written + * to the register. Returns -1 if charging is not allowed + */ +static int ab8500_charger_get_usb_cur(struct ab8500_charger *di) +{ + switch (di->usb_state.usb_current) { + case 100: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09; + break; + case 200: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P19; + break; + case 300: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P29; + break; + case 400: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P38; + break; + case 500: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + default: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + return -1; + break; + }; + return 0; +} + +/** + * ab8500_charger_led_en() - turn on/off chargign led + * @di: pointer to the ab8500_charger structure + * @on: flag to turn on/off the chargign led + * + * Power ON/OFF charging LED indication + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_led_en(struct ab8500_charger *di, int on) +{ + int ret; + + if (on) { + /* Power ON charging LED indicator, set LED current to 5mA */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_CTRL, + (LED_IND_CUR_5MA | LED_INDICATOR_PWM_ENA)); + if (ret) { + dev_err(di->dev, "Power ON LED failed\n"); + return ret; + } + /* LED indicator PWM duty cycle 252/256 */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_DUTY, + LED_INDICATOR_PWM_DUTY_252_256); + if (ret) { + dev_err(di->dev, "Set LED PWM duty cycle failed\n"); + return ret; + } + } else { + /* Power off charging LED indicator */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_LED_INDICATOR_PWM_CTRL, + LED_INDICATOR_PWM_DIS); + if (ret) { + dev_err(di->dev, "Power-off LED failed\n"); + return ret; + } + } + + return ret; +} + +/** + * ab8500_charger_ac_en() - enable or disable ac charging + * @di: pointer to the ab8500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @iset: charging current + * + * Enable/Disable AC/Mains charging and turns on/off the charging led + * respectively. + **/ +static int ab8500_charger_ac_en(struct ux500_charger *charger, + int enable, int vset, int iset) +{ + int ret; + int volt_index; + int curr_index; + u8 overshoot = 0; + + struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger); + + if (enable) { + /* Check if AC is connected */ + if (!di->ac.charger_connected) { + dev_err(di->dev, "AC charger not connected\n"); + return -ENXIO; + } + + /* Enable AC charging */ + dev_dbg(di->dev, "Enable AC: %dmV %dmA\n", vset, iset); + + /* Check if the requested voltage or current is valid */ + volt_index = ab8500_voltage_to_regval(vset); + curr_index = ab8500_current_to_regval(iset); + if (volt_index < 0 || curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + /* ChVoltLevel: maximum battery charging voltage */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* MainChInputCurr: current that can be drawn from the charger*/ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_MCH_IPT_CURLVL_REG, MAIN_CH_IP_CUR_1P5A); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Check if VBAT overshoot control should be enabled */ + if (!di->bat->enable_overshoot) + overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N; + + /* Enable Main Charger */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_MCH_CTRL1, MAIN_CH_ENA | overshoot); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Power on charging LED indication */ + ret = ab8500_charger_led_en(di, true); + if (ret < 0) + dev_err(di->dev, "failed to enable LED\n"); + + di->ac.charger_online = 1; + } else { + /* Disable AC charging */ + + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + /* + * For ABB revision 1.0 and 1.1 there is a bug in the + * watchdog logic. That means we have to continously + * kick the charger watchdog even when no charger is + * connected. This is only valid once the AC charger + * has been enabled. This is a bug that is not handled + * by the algorithm and the watchdog have to be kicked + * by the charger driver when the AC charger + * is disabled + */ + if (di->ac_conn) { + queue_delayed_work(di->charger_wq, + &di->kick_wd_work, + round_jiffies(WD_KICK_INTERVAL)); + } + + /* + * We can't turn off charging completely + * due to a bug in AB8500 cut1. + * If we do, charging will not start again. + * That is why we set the lowest voltage + * and current possible + */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, CH_VOL_LVL_3P5); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, CH_OP_CUR_LVL_0P1); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + break; + + case AB8500_CUT2P0: + default: + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_MCH_CTRL1, 0); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + break; + } + + ret = ab8500_charger_led_en(di, false); + if (ret < 0) + dev_err(di->dev, "failed to disable LED\n"); + + di->ac.charger_online = 0; + di->ac.wd_expired = false; + dev_dbg(di->dev, "%s Disabled AC charging\n", __func__); + } + power_supply_changed(&di->ac_chg.psy); + + return ret; +} + +/** + * ab8500_charger_usb_en() - enable usb charging + * @di: pointer to the ab8500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @ich_out: charger output current + * + * Enable/Disable USB charging and turns on/off the charging led respectively. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_usb_en(struct ux500_charger *charger, + int enable, int vset, int ich_out) +{ + int ret; + int volt_index; + int curr_index; + u8 overshoot = 0; + + struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger); + + if (enable) { + /* Check if USB is connected */ + if (!di->usb.charger_connected) { + dev_err(di->dev, "USB charger not connected\n"); + return -ENXIO; + } + + /* Enable USB charging */ + dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out); + + /* Check if the requested voltage or current is valid */ + volt_index = ab8500_voltage_to_regval(vset); + curr_index = ab8500_current_to_regval(ich_out); + if (volt_index < 0 || curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + /* ChVoltLevel: max voltage upto which battery can be charged */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_VOLT_LVL_REG, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* USBChInputCurr: current that can be drawn from the usb */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_IPT_CRNTLVL_REG, + di->max_usb_in_curr); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + /* Check if VBAT overshoot control should be enabled */ + if (!di->bat->enable_overshoot) + overshoot = USB_CHG_NO_OVERSHOOT_ENA_N; + + /* Enable USB Charger */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, USB_CH_ENA | overshoot); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* If success power on charging LED indication */ + ret = ab8500_charger_led_en(di, true); + if (ret < 0) + dev_err(di->dev, "failed to enable LED\n"); + + di->usb.charger_online = 1; + } else { + /* Disable USB charging */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, 0); + if (ret) { + dev_err(di->dev, + "%s write failed\n", __func__); + return ret; + } + + ret = ab8500_charger_led_en(di, false); + if (ret < 0) + dev_err(di->dev, "failed to disable LED\n"); + + di->usb.charger_online = 0; + di->usb.wd_expired = false; + dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); + } + power_supply_changed(&di->usb_chg.psy); + + return ret; +} + +/** + * ab8500_charger_watchdog_kick() - kick charger watchdog + * @di: pointer to the ab8500_charger structure + * + * Kick charger watchdog + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_watchdog_kick(struct ux500_charger *charger) +{ + int ret; + struct ab8500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + return ret; +} + +/** + * ab8500_charger_update_charger_current() - update charger current + * @di: pointer to the ab8500_charger structure + * + * Update the charger output current for the specified charger + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_update_charger_current(struct ux500_charger *charger, + int ich_out) +{ + int ret; + int curr_index; + struct ab8500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + curr_index = ab8500_current_to_regval(ich_out); + if (curr_index < 0) { + dev_err(di->dev, + "Charger voltage or current too high, " + "charging not started\n"); + return -ENXIO; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); + if (ret) + dev_err(di->dev, "%s write failed\n", __func__); + + return ret; +} + +/** + * ab8500_charger_check_hw_failure_work() - check main charger failure + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab8500_charger_check_hw_failure_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_hw_failure_work.work); + + /* Check if the status bits for HW failure is still active */ + if (di->flags.mainextchnotok) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (!(reg_value & MAIN_CH_NOK)) { + di->flags.mainextchnotok = false; + power_supply_changed(&di->ac_chg.psy); + } + } + if (di->flags.vbus_ovv) { + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, + ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (!(reg_value & VBUS_OVV_TH)) { + di->flags.vbus_ovv = false; + power_supply_changed(&di->usb_chg.psy); + } + } + /* If we still have a failure, schedule a new check */ + if (di->flags.mainextchnotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, round_jiffies(HZ)); + } +} + +/** + * ab8500_charger_kick_watchdog_work() - kick the watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog. + * + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ +static void ab8500_charger_kick_watchdog_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, kick_wd_work.work); + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + /* Schedule a new watchdog kick */ + queue_delayed_work(di->charger_wq, + &di->kick_wd_work, round_jiffies(WD_KICK_INTERVAL)); +} + +/** + * ab8500_charger_ac_work() - work to get and set main charger status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab8500_charger_ac_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, ac_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if the main charger is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (ret & AC_PW_CONN) { + di->ac.charger_connected = 1; + di->ac_conn = true; + } else { + di->ac.charger_connected = 0; + } + + power_supply_changed(&di->ac_chg.psy); +} + +/** + * ab8500_charger_detect_usb_type_work() - work to detect USB type + * @work: Pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +void ab8500_charger_detect_usb_type_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, detect_usb_type_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } else { + di->vbus_detected = 1; + + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + ret = ab8500_charger_detect_usb_type(di); + if (!ret) { + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + break; + + case AB8500_CUT2P0: + default: + /* For ABB cut2.0 and onwards we have an IRQ, + * USB_LINK_STATUS that will be triggered when the USB + * link status changes. The exception is USB connected + * during startup. Then we don't get a + * USB_LINK_STATUS IRQ + */ + if (di->vbus_detected_start) { + di->vbus_detected_start = false; + ret = ab8500_charger_detect_usb_type(di); + if (!ret) { + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + } + break; + } + } +} + +/** + * ab8500_charger_usb_link_status_work() - work to detect USB type + * @work: pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +static void ab8500_charger_usb_link_status_work(struct work_struct *work) +{ + int ret; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, usb_link_status_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab8500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } else { + di->vbus_detected = 1; + ret = ab8500_charger_read_usb_type(di); + if (!ret) { + /* Update maximum input current */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_USBCH_IPT_CRNTLVL_REG, + di->max_usb_in_curr); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return; + } + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } else if (ret == -ENXIO) { + /* No valid charger type detected */ + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } + } +} + +static void ab8500_charger_usb_state_changed_work(struct work_struct *work) +{ + int ret; + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, usb_state_changed_work); + + if (!di->vbus_detected) + return; + + spin_lock(&di->usb_state.usb_lock); + di->usb_state.usb_changed = false; + spin_unlock(&di->usb_state.usb_lock); + + /* + * wait for some time until you get updates from the usb stack + * and negotiations are completed + */ + msleep(250); + + if (di->usb_state.usb_changed) + return; + + dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n", + __func__, di->usb_state.state, di->usb_state.usb_current); + + switch (di->usb_state.state) { + case AB8500_BM_USB_STATE_RESET_HS: + case AB8500_BM_USB_STATE_RESET_FS: + case AB8500_BM_USB_STATE_SUSPEND: + case AB8500_BM_USB_STATE_MAX: + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + break; + + case AB8500_BM_USB_STATE_RESUME: + /* + * when suspend->resume there should be delay + * of 1sec for enabling charging + */ + msleep(1000); + /* Intentional fall through */ + case AB8500_BM_USB_STATE_CONFIGURED: + /* + * USB is configured, enable charging with the charging + * input current obtained from USB driver + */ + if (!ab8500_charger_get_usb_cur(di)) { + /* Update maximum input current */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_USBCH_IPT_CRNTLVL_REG, + di->max_usb_in_curr); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return; + } + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + break; + + default: + break; + }; +} + +/** + * ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB charger Not OK status + */ +static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_usbchgnotok_work); + + /* Check if the status bit for usbchargernotok is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & VBUS_CH_NOK) + di->flags.usbchargernotok = true; + else + di->flags.usbchargernotok = false; + + power_supply_changed(&di->usb_chg.psy); +} + +/** + * ab8500_charger_check_main_thermal_prot_work() - check main thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the Main thermal prot status + */ +static void ab8500_charger_check_main_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_main_thermal_prot_work); + + /* Check if the status bit for main_thermal_prot is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_STATUS2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & MAIN_CH_TH_PROT) + di->flags.main_thermal_prot = true; + else + di->flags.main_thermal_prot = false; + + power_supply_changed(&di->ac_chg.psy); +} + +/** + * ab8500_charger_check_usb_thermal_prot_work() - check usb thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB thermal prot status + */ +static void ab8500_charger_check_usb_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_usb_thermal_prot_work); + + /* Check if the status bit for usb_thermal_prot is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return; + } + if (reg_value & USB_CH_TH_PROT) + di->flags.usb_thermal_prot = true; + else + di->flags.usb_thermal_prot = false; + + power_supply_changed(&di->usb_chg.psy); +} + +/** + * ab8500_charger_mainchunplugdet_handler() - main charger unplugged + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger unplugged\n"); + queue_work(di->charger_wq, &di->ac_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchplugdet_handler() - main charger plugged + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger plugged\n"); + queue_work(di->charger_wq, &di->ac_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainextchnotok_handler() - main charger not ok + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainextchnotok_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Main charger not ok\n"); + di->flags.mainextchnotok = true; + power_supply_changed(&di->ac_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchthprotr_handler() - Die temp is above main charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchthprotr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above Main charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_main_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_mainchthprotf_handler() - Die temp is below main charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_mainchthprotf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp ok for Main charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_main_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusdetf_handler() - VBUS falling detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusdetf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "VBUS falling detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusdetr_handler() - VBUS rising detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusdetr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + di->vbus_detected = true; + dev_dbg(di->dev, "VBUS rising detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usblinkstatus_handler() - USB link status has changed + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usblinkstatus_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "USB link status changed\n"); + + queue_work(di->charger_wq, &di->usb_link_status_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchthprotr_handler() - Die temp is above usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchthprotr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchthprotf_handler() - Die temp is below usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchthprotf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp ok for USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchargernotokf_handler() - USB charger ok detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchargernotokf_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Allowed USB charger detected\n"); + queue_work(di->charger_wq, &di->check_usbchgnotok_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_usbchargernotokr_handler() - USB charger not ok detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_usbchargernotokr_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Not allowed USB charger detected\n"); + queue_work(di->charger_wq, &di->check_usbchgnotok_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_chwdexp_handler() - Charger watchdog expired + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_chwdexp_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "Charger watchdog expired\n"); + + /* + * The charger that was online when the watchdog expired + * needs to be restarted for charging to start again + */ + if (di->ac.charger_online) { + di->ac.wd_expired = true; + power_supply_changed(&di->ac_chg.psy); + } + if (di->usb.charger_online) { + di->usb.wd_expired = true; + power_supply_changed(&di->usb_chg.psy); + } + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_vbusovv_handler() - VBUS overvoltage detected + * @irq: interrupt number + * @_di: pointer to the ab8500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_charger_vbusovv_handler(int irq, void *_di) +{ + struct ab8500_charger *di = _di; + + dev_dbg(di->dev, "VBUS overvoltage detected\n"); + di->flags.vbus_ovv = true; + power_supply_changed(&di->usb_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab8500_charger_ac_get_property() - get the ac/mains properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the ac/mains + * properties by reading the sysfs files. + * AC/Mains properties are online, present and voltage. + * online: ac/mains charging is in progress or not + * present: presence of the ac/mains + * voltage: AC/Mains voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_charger *di; + + di = to_ab8500_charger_ac_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.mainextchnotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->ac.wd_expired || di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.main_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->ac.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->ac.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + di->ac.charger_voltage = ab8500_charger_get_ac_voltage(di); + val->intval = di->ac.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* + * This property is used to indicate when CV mode is entered + * for the AC charger + */ + di->ac.cv_active = ab8500_charger_ac_cv(di); + val->intval = di->ac.cv_active; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ab8500_charger_get_ac_current(di) * 1000; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab8500_charger_usb_get_property() - get the usb properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the usb + * properties by reading the sysfs files. + * USB properties are online, present and voltage. + * online: usb charging is in progress or not + * present: presence of the usb + * voltage: vbus voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_charger *di; + + di = to_ab8500_charger_usb_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.usbchargernotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->ac.wd_expired || di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.usb_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (di->flags.vbus_ovv) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->usb.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->usb.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + di->usb.charger_voltage = ab8500_charger_get_vbus_voltage(di); + val->intval = di->usb.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + /* + * This property is used to indicate when CV mode is entered + * for the USB charger + */ + di->usb.cv_active = ab8500_charger_usb_cv(di); + val->intval = di->usb.cv_active; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ab8500_charger_get_usb_current(di) * 1000; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab8500_charger_init_hw_registers() - Set up charger related registers + * @di: pointer to the ab8500_charger structure + * + * Set up charger OVV, watchdog and maximum voltage registers as well as + * charging of the backup battery + */ +static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) +{ + int ret = 0; + + /* Setup maximum charger current and voltage for ABB cut2.0 */ + switch (di->chip_id) { + case AB8500_CUT1P0: + case AB8500_CUT1P1: + break; + case AB8500_CUT2P0: + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_VOLT_LVL_MAX_REG, CH_VOL_LVL_4P6); + if (ret) { + dev_err(di->dev, + "failed to set CH_VOLT_LVL_MAX_REG\n"); + goto out; + } + + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_CH_OPT_CRNTLVL_MAX_REG, CH_OP_CUR_LVL_1P6); + if (ret) { + dev_err(di->dev, + "failed to set CH_OPT_CRNTLVL_MAX_REG\n"); + goto out; + } + + break; + default: + goto out; + } + + /* VBUS OVV set to 6.3V */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_USBCH_CTRL2_REG, 0x78); + if (ret) { + dev_err(di->dev, "failed to set VBUS OVV\n"); + goto out; + } + + /* Enable main watchdog in OTP */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_OTP_EMUL, AB8500_OTP_CONF_15, OTP_ENABLE_WD); + if (ret) { + dev_err(di->dev, "failed to enable main WD in OTP\n"); + goto out; + } + + /* Enable main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_ENA); + if (ret) { + dev_err(di->dev, "faile to enable main watchdog\n"); + goto out; + } + + /* + * Due to internal synchronisation, Enable and Kick watchdog bits + * cannot be enabled in a single write. + * A minimum delay of 2*32 kHz period (62.5µs) must be inserted + * between writing Enable then Kick bits. + */ + udelay(63); + + /* Kick main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, + (MAIN_WDOG_ENA | MAIN_WDOG_KICK)); + if (ret) { + dev_err(di->dev, "failed to kick main watchdog\n"); + goto out; + } + + /* Disable main watchdog */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_DIS); + if (ret) { + dev_err(di->dev, "failed to disable main watchdog\n"); + goto out; + } + + /* Set watchdog timeout */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_WD_TIMER_REG, WD_TIMER); + if (ret) { + dev_err(di->dev, "failed to set charger watchdog timeout\n"); + goto out; + } + + /* Backup battery voltage and current */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_RTC, + AB8500_RTC_BACKUP_CHG_REG, + di->bat->bkup_bat_v | + di->bat->bkup_bat_i); + if (ret) { + dev_err(di->dev, "failed to setup backup battery charging\n"); + goto out; + } + + /* Enable backup battery charging */ + abx500_mask_and_set_register_interruptible(di->dev, + AB8500_RTC, AB8500_RTC_CTRL_REG, + RTC_BUP_CH_ENA, RTC_BUP_CH_ENA); + if (ret < 0) + dev_err(di->dev, "%s mask and set failed\n", __func__); + +out: + return ret; +} + +/* + * ab8500 charger driver interrupts and their respective isr + */ +static struct ab8500_charger_interrupts ab8500_charger_irq[] = { + {"MAIN_CH_UNPLUG_DET", ab8500_charger_mainchunplugdet_handler}, + {"MAIN_CHARGE_PLUG_DET", ab8500_charger_mainchplugdet_handler}, + {"MAIN_EXT_CH_NOT_OK", ab8500_charger_mainextchnotok_handler}, + {"MAIN_CH_TH_PROT_R", ab8500_charger_mainchthprotr_handler}, + {"MAIN_CH_TH_PROT_F", ab8500_charger_mainchthprotf_handler}, + {"VBUS_DET_F", ab8500_charger_vbusdetf_handler}, + {"VBUS_DET_R", ab8500_charger_vbusdetr_handler}, + {"USB_LINK_STATUS", ab8500_charger_usblinkstatus_handler}, + {"USB_CH_TH_PROT_R", ab8500_charger_usbchthprotr_handler}, + {"USB_CH_TH_PROT_F", ab8500_charger_usbchthprotf_handler}, + {"USB_CHARGER_NOT_OKF", ab8500_charger_usbchargernotokf_handler}, + {"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler}, + {"VBUS_OVV", ab8500_charger_vbusovv_handler}, + {"CH_WD_EXP", ab8500_charger_chwdexp_handler}, +}; + +void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) +{ + struct ab8500_charger *di = static_di; + + dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n", + __func__, bm_usb_state, mA); + + spin_lock(&di->usb_state.usb_lock); + di->usb_state.usb_changed = true; + spin_unlock(&di->usb_state.usb_lock); + + di->usb_state.state = bm_usb_state; + di->usb_state.usb_current = mA; + + queue_work(di->charger_wq, &di->usb_state_changed_work); + + return; +} +EXPORT_SYMBOL(ab8500_charger_usb_state_changed); + +#if defined(CONFIG_PM) +static int ab8500_charger_resume(struct platform_device *pdev) +{ + int ret; + struct ab8500_charger *di = platform_get_drvdata(pdev); + + /* + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ + if (di->ac_conn && (di->chip_id == AB8500_CUT1P0 || + di->chip_id == AB8500_CUT1P1)) { + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARG_WD_CTRL, CHARG_WD_KICK); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + /* If not already pending start a new timer */ + if (!delayed_work_pending( + &di->kick_wd_work)) { + queue_delayed_work(di->charger_wq, &di->kick_wd_work, + round_jiffies(WD_KICK_INTERVAL)); + } + } + + /* If we still have a HW failure, schedule a new check */ + if (di->flags.mainextchnotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, 0); + } + + return 0; +} + +static int ab8500_charger_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_charger *di = platform_get_drvdata(pdev); + + /* Cancel any pending HW failure check */ + if (delayed_work_pending(&di->check_hw_failure_work)) + cancel_delayed_work(&di->check_hw_failure_work); + + return 0; +} +#else +#define ab8500_charger_suspend NULL +#define ab8500_charger_resume NULL +#endif + +static int __devexit ab8500_charger_remove(struct platform_device *pdev) +{ + struct ab8500_charger *di = platform_get_drvdata(pdev); + int i, irq, ret; + + /* Disable AC charging */ + ab8500_charger_ac_en(&di->ac_chg, false, 0, 0); + + /* Disable USB charging */ + ab8500_charger_usb_en(&di->usb_chg, false, 0, 0); + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + free_irq(irq, di); + } + + /* Backup battery voltage and current disable */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0); + if (ret < 0) + dev_err(di->dev, "%s mask and set failed\n", __func__); + + /* Delete the work queue */ + destroy_workqueue(di->charger_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->usb_chg.psy); + power_supply_unregister(&di->ac_chg.psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab8500_charger_probe(struct platform_device *pdev) +{ + int irq, i, charger_status, ret = 0; + struct ab8500_platform_data *plat; + + struct ab8500_charger *di = + kzalloc(sizeof(struct ab8500_charger), GFP_KERNEL); + if (!di) + return -ENOMEM; + + static_di = di; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get(); + + /* initialize lock */ + spin_lock_init(&di->usb_state.usb_lock); + + plat = dev_get_platdata(di->parent->dev); + + /* get charger specific platform data */ + if (!plat->charger) { + dev_err(di->dev, "no charger platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->charger; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + /* AC supply */ + /* power_supply base class */ + di->ac_chg.psy.name = "ab8500_ac"; + di->ac_chg.psy.type = POWER_SUPPLY_TYPE_MAINS; + di->ac_chg.psy.properties = ab8500_charger_ac_props; + di->ac_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_ac_props); + di->ac_chg.psy.get_property = ab8500_charger_ac_get_property; + di->ac_chg.psy.supplied_to = di->pdata->supplied_to; + di->ac_chg.psy.num_supplicants = di->pdata->num_supplicants; + /* ux500_charger sub-class */ + di->ac_chg.ops.enable = &ab8500_charger_ac_en; + di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; + di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->ac_chg.max_out_volt = ab8500_charger_voltage_map[ + ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; + di->ac_chg.max_out_curr = ab8500_charger_current_map[ + ARRAY_SIZE(ab8500_charger_current_map) - 1]; + + /* USB supply */ + /* power_supply base class */ + di->usb_chg.psy.name = "ab8500_usb"; + di->usb_chg.psy.type = POWER_SUPPLY_TYPE_USB; + di->usb_chg.psy.properties = ab8500_charger_usb_props; + di->usb_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_usb_props); + di->usb_chg.psy.get_property = ab8500_charger_usb_get_property; + di->usb_chg.psy.supplied_to = di->pdata->supplied_to; + di->usb_chg.psy.num_supplicants = di->pdata->num_supplicants; + /* ux500_charger sub-class */ + di->usb_chg.ops.enable = &ab8500_charger_usb_en; + di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; + di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->usb_chg.max_out_volt = ab8500_charger_voltage_map[ + ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; + di->usb_chg.max_out_curr = ab8500_charger_current_map[ + ARRAY_SIZE(ab8500_charger_current_map) - 1]; + + + /* Create a work queue for the charger */ + di->charger_wq = + create_singlethread_workqueue("ab8500_charger_wq"); + if (di->charger_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for HW failure check */ + INIT_DELAYED_WORK_DEFERRABLE(&di->check_hw_failure_work, + ab8500_charger_check_hw_failure_work); + + /* + * For ABB revision 1.0 and 1.1 there is a bug in the watchdog + * logic. That means we have to continously kick the charger + * watchdog even when no charger is connected. This is only + * valid once the AC charger has been enabled. This is + * a bug that is not handled by the algorithm and the + * watchdog have to be kicked by the charger driver + * when the AC charger is disabled + */ + INIT_DELAYED_WORK_DEFERRABLE(&di->kick_wd_work, + ab8500_charger_kick_watchdog_work); + + /* Init work for charger detection */ + INIT_WORK(&di->usb_link_status_work, + ab8500_charger_usb_link_status_work); + INIT_WORK(&di->ac_work, ab8500_charger_ac_work); + INIT_WORK(&di->detect_usb_type_work, + ab8500_charger_detect_usb_type_work); + + INIT_WORK(&di->usb_state_changed_work, + ab8500_charger_usb_state_changed_work); + + /* Init work for checking HW status */ + INIT_WORK(&di->check_usbchgnotok_work, + ab8500_charger_check_usbchargernotok_work); + INIT_WORK(&di->check_main_thermal_prot_work, + ab8500_charger_check_main_thermal_prot_work); + INIT_WORK(&di->check_usb_thermal_prot_work, + ab8500_charger_check_usb_thermal_prot_work); + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(di->dev); + if (ret < 0) { + dev_err(di->dev, "failed to get chip ID\n"); + goto free_charger_wq; + } + di->chip_id = ret; + dev_dbg(di->dev, "AB8500 CID is: 0x%02x\n", di->chip_id); + + /* Initialize OVV, and other registers */ + ret = ab8500_charger_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize ABB registers\n"); + goto free_charger_wq; + } + + /* Register AC charger class */ + ret = power_supply_register(di->dev, &di->ac_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register AC charger\n"); + goto free_charger_wq; + } + + /* Register USB charger class */ + ret = power_supply_register(di->dev, &di->usb_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register USB charger\n"); + goto free_ac; + } + + /* Identify the connected charger types during startup */ + charger_status = ab8500_charger_detect_chargers(di); + if (charger_status & AC_PW_CONN) { + di->ac.charger_connected = 1; + di->ac_conn = true; + power_supply_changed(&di->ac_chg.psy); + } + + if (charger_status & USB_PW_CONN) { + dev_dbg(di->dev, "VBUS Detect during startup\n"); + di->vbus_detected = true; + di->vbus_detected_start = true; + queue_work(di->charger_wq, + &di->detect_usb_type_work); + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_charger_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_charger_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_charger_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_charger_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + di->parent->charger = di; + + return ret; + +free_irq: + power_supply_unregister(&di->usb_chg.psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); + free_irq(irq, di); + } +free_ac: + power_supply_unregister(&di->ac_chg.psy); +free_charger_wq: + destroy_workqueue(di->charger_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_charger_driver = { + .probe = ab8500_charger_probe, + .remove = __devexit_p(ab8500_charger_remove), + .suspend = ab8500_charger_suspend, + .resume = ab8500_charger_resume, + .driver = { + .name = "ab8500-charger", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_charger_init(void) +{ + return platform_driver_register(&ab8500_charger_driver); +} + +static void __exit ab8500_charger_exit(void) +{ + platform_driver_unregister(&ab8500_charger_driver); +} + +subsys_initcall_sync(ab8500_charger_init); +module_exit(ab8500_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy"); +MODULE_ALIAS("platform:ab8500-charger"); +MODULE_DESCRIPTION("AB8500 charger management driver"); diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c new file mode 100644 index 00000000000..d141f44d2cd --- /dev/null +++ b/drivers/power/ab8500_fg.c @@ -0,0 +1,1859 @@ +/* + * Copyright (C) ST-Ericsson AB 2010 + * + * Main and Back-up battery management driver. + * + * Note: Backup battery management is required in case of Li-Ion battery and not + * for capacitive battery. HREF boards have capacitive battery and hence backup + * battery management is not used and the supported code is available in this + * driver. + * + * License Terms: GNU General Public License v2 + * Author: Johan Palsson + * Author: Karl Komierowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MILLI_TO_MICRO 1000 +#define FG_LSB_IN_MA 1627 +#define QLSB_NANO_AMP_HOURS_X10 1129 + +#define SEC_TO_SAMPLE(S) (S * 4) + +#define NBR_AVG_SAMPLES 20 + +#define LOW_BAT_CHECK_INTERVAL (2 * HZ) + +#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */ + +#define interpolate(x, x1, y1, x2, y2) \ + ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1)))); + +#define to_ab8500_fg_device_info(x) container_of((x), \ + struct ab8500_fg, fg_psy); + +/** + * struct ab8500_fg_interrupts - ab8500 fg interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_fg_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +enum ab8500_fg_discharge_state { + AB8500_FG_DISCHARGE_INIT, + AB8500_FG_DISCHARGE_INITMEASURING, + AB8500_FG_DISCHARGE_INIT_RECOVERY, + AB8500_FG_DISCHARGE_RECOVERY, + AB8500_FG_DISCHARGE_READOUT, + AB8500_FG_DISCHARGE_WAKEUP, +}; + +static char *discharge_state[] = { + "DISCHARGE_INIT", + "DISCHARGE_INITMEASURING", + "DISCHARGE_INIT_RECOVERY", + "DISCHARGE_RECOVERY", + "DISCHARGE_READOUT", + "DISCHARGE_WAKEUP", +}; + +enum ab8500_fg_charge_state { + AB8500_FG_CHARGE_INIT, + AB8500_FG_CHARGE_READOUT, +}; + +static char *charge_state[] = { + "CHARGE_INIT", + "CHARGE_READOUT", +}; + +struct ab8500_fg_avg_cap { + int avg; + int samples[NBR_AVG_SAMPLES]; + __kernel_time_t time_stamps[NBR_AVG_SAMPLES]; + int pos; + int nbr_samples; + int sum; +}; + +struct ab8500_fg_battery_capacity { + int max_mah_design; + int max_mah; + int mah; + int permille; + int level; + int prev_mah; + int prev_percent; + int prev_level; +}; + +struct ab8500_fg_flags { + bool fg_enabled; + bool conv_done; + bool charging; + bool fully_charged; + bool low_bat_delay; + bool low_bat; + bool bat_ovv; + bool batt_unknown; +}; + +/** + * struct ab8500_fg - ab8500 FG device information + * @dev: Pointer to the structure device + * @vbat: Battery voltage in mV + * @vbat_nom: Nominal battery voltage in mV + * @inst_curr: Instantenous battery current in mA + * @avg_curr: Average battery current in mA + * @fg_samples: Number of samples used in the FG accumulation + * @accu_charge: Accumulated charge from the last conversion + * @recovery_cnt: Counter for recovery mode + * @high_curr_cnt: Counter for high current mode + * @init_cnt: Counter for init mode + * @recovery_needed: Indicate if recovery is needed + * @high_curr_mode: Indicate if we're in high current mode + * @init_capacity: Indicate if initial capacity measuring should be done + * @discharge_state: Current discharge state + * @charge_state: Current charge state + * @flags: Structure for information about events triggered + * @bat_cap: Structure for battery capacity specific parameters + * @avg_cap: Average capacity filter + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the ab8500_fg platform data + * @bat: Pointer to the ab8500_bm platform data + * @fg_psy: Structure that holds the FG specific battery properties + * @fg_wq: Work queue for running the FG algorithm + * @fg_periodic_work: Work to run the FG algorithm periodically + * @fg_low_bat_work: Work to check low bat condition + * @fg_work: Work to run the FG algorithm instantly + * @fg_acc_cur_work: Work to read the FG accumulator + * @cc_lock: Mutex for locking the CC + */ +struct ab8500_fg { + struct device *dev; + int vbat; + int vbat_nom; + int inst_curr; + int avg_curr; + int fg_samples; + int accu_charge; + int recovery_cnt; + int high_curr_cnt; + int init_cnt; + bool recovery_needed; + bool high_curr_mode; + bool init_capacity; + enum ab8500_fg_discharge_state discharge_state; + enum ab8500_fg_charge_state charge_state; + struct ab8500_fg_flags flags; + struct ab8500_fg_battery_capacity bat_cap; + struct ab8500_fg_avg_cap avg_cap; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct ab8500_fg_platform_data *pdata; + struct ab8500_bm_data *bat; + struct power_supply fg_psy; + struct workqueue_struct *fg_wq; + struct delayed_work fg_periodic_work; + struct delayed_work fg_low_bat_work; + struct work_struct fg_work; + struct work_struct fg_acc_cur_work; + struct mutex cc_lock; +}; + +/* Main battery properties */ +static enum power_supply_property ab8500_fg_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, +}; + +/* + * This array maps the raw hex value to lowbat voltage used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_fg_lowbat_voltage_map[] = { + 2300 , + 2325 , + 2350 , + 2375 , + 2400 , + 2425 , + 2450 , + 2475 , + 2500 , + 2525 , + 2550 , + 2575 , + 2600 , + 2625 , + 2650 , + 2675 , + 2700 , + 2725 , + 2750 , + 2775 , + 2800 , + 2825 , + 2850 , + 2875 , + 2900 , + 2925 , + 2950 , + 2975 , + 3000 , + 3025 , + 3050 , + 3075 , + 3100 , + 3125 , + 3150 , + 3175 , + 3200 , + 3225 , + 3250 , + 3275 , + 3300 , + 3325 , + 3350 , + 3375 , + 3400 , + 3425 , + 3450 , + 3475 , + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3850 , +}; + +static u8 ab8500_volt_to_regval(int voltage) +{ + int i; + + if (voltage < ab8500_fg_lowbat_voltage_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_fg_lowbat_voltage_map); i++) { + if (voltage < ab8500_fg_lowbat_voltage_map[i]) + return (u8) i - 1; + } + + /* If not captured above, return index of last element */ + return (u8) ARRAY_SIZE(ab8500_fg_lowbat_voltage_map) - 1; +} + +/** + * ab8500_fg_is_low_curr() - Low or high current mode + * @di: pointer to the ab8500_fg structure + * @curr: the current to base or our decision on + * + * Low current mode if the current consumption is below a certain threshold + */ +static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr) +{ + /* + * We want to know if we're in low current mode + */ + if (curr > -di->bat->fg_params->high_curr_threshold) + return true; + else + return false; +} + +/** + * ab8500_fg_add_cap_sample() - Add capacity to average filter + * @di: pointer to the ab8500_fg structure + * @sample: the capacity in mAh to add to the filter + * + * A capacity is added to the filter and a new mean capacity is calculated and + * returned + */ +static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample) +{ + struct timespec ts; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + do { + avg->sum += sample - avg->samples[avg->pos]; + avg->samples[avg->pos] = sample; + avg->time_stamps[avg->pos] = ts.tv_sec; + avg->pos++; + + if (avg->pos == NBR_AVG_SAMPLES) + avg->pos = 0; + + if (avg->nbr_samples < NBR_AVG_SAMPLES) + avg->nbr_samples++; + + /* + * Check the time stamp for each sample. If too old, + * replace with latest sample + */ + } while (ts.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]); + + avg->avg = avg->sum / avg->nbr_samples; + + return avg->avg; +} + +/** + * ab8500_fg_fill_cap_sample() - Fill average filter + * @di: pointer to the ab8500_fg structure + * @sample: the capacity in mAh to fill the filter with + * + * The capacity filter is filled with a capacity in mAh + */ +static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample) +{ + int i; + struct timespec ts; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = sample; + avg->time_stamps[i] = ts.tv_sec; + } + + avg->pos = 0; + avg->nbr_samples = NBR_AVG_SAMPLES; + avg->sum = sample * NBR_AVG_SAMPLES; + avg->avg = sample; +} + +/** + * ab8500_fg_coulomb_counter() - enable coulomb counter + * @di: pointer to the ab8500_fg structure + * @enable: enable/disable + * + * Enable/Disable coulomb counter. + * On failure returns negative value. + */ +static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable) +{ + int ret = 0; + mutex_lock(&di->cc_lock); + if (enable) { + /* To be able to reprogram the number of samples, we have to + * first stop the CC and then enable it again */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0x00); + if (ret) + goto cc_err; + + /* Program the samples */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, + di->fg_samples); + if (ret) + goto cc_err; + + /* Start the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, + (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + if (ret) + goto cc_err; + + di->flags.fg_enabled = true; + } else { + /* Clear any pending read requests */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0); + if (ret) + goto cc_err; + + /* Stop the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0); + if (ret) + goto cc_err; + + di->flags.fg_enabled = false; + + } + dev_dbg(di->dev, " CC enabled: %d Samples: %d\n", + enable, di->fg_samples); + + mutex_unlock(&di->cc_lock); + + return ret; +cc_err: + dev_err(di->dev, "%s Enabling coulomb counter failed\n", __func__); + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_inst_curr() - battery instantaneous current + * @di: pointer to the ab8500_fg structure + * + * Returns battery instantenous current(on success) else error code + */ +static int ab8500_fg_inst_curr(struct ab8500_fg *di) +{ + u8 low, high, reg_val; + static int val; + int ret = 0; + bool fg_off = false; + + mutex_lock(&di->cc_lock); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, ®_val); + if (ret < 0) + goto inst_curr_err; + + if (!(reg_val & CC_PWR_UP_ENA)) { + dev_dbg(di->dev, "%s Enable FG\n", __func__); + fg_off = true; + + /* Program the samples */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, + SEC_TO_SAMPLE(10)); + if (ret) + goto inst_curr_err; + + /* Start the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, + (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); + if (ret) + goto inst_curr_err; + } + + /* Reset counter and Read request */ + ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_CTRL_REG, (RESET_ACCU | READ_REQ)); + if (ret) + goto inst_curr_err; + + /* + * Since there is no interrupt for this, just wait for 250ms + * 250ms is one sample conversion time with 32.768 Khz RTC clock + */ + msleep(250); + + /* Read CC Sample conversion value Low and high */ + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_SMPL_CNVL_REG, &low); + if (ret < 0) + goto inst_curr_err; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_SMPL_CNVH_REG, &high); + if (ret < 0) + goto inst_curr_err; + + /* + * negative value for Discharging + * convert 2's compliment into decimal + */ + if (high & 0x10) + val = (low | (high << 8) | 0xFFFFE000); + else + val = (low | (high << 8)); + + /* + * Convert to unit value in mA + * Full scale input voltage is + * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA + * resistance is in mOhm + */ + val = ((val * 66660) / (4096 * di->bat->fg_res)); + + if (fg_off) { + dev_dbg(di->dev, "%s Disable FG\n", __func__); + + /* Clear any pending read requests */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0); + if (ret) + goto inst_curr_err; + + /* Stop the CC */ + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8500_RTC_CC_CONF_REG, 0); + if (ret) + goto inst_curr_err; + } + + mutex_unlock(&di->cc_lock); + + return val; + +inst_curr_err: + dev_err(di->dev, "%s Get instanst current failed\n", __func__); + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab8500_fg_acc_cur_work() - average battery current + * @work: pointer to the work_struct structure + * + * Updated the average battery current obtained from the + * coulomb counter. + */ +static void ab8500_fg_acc_cur_work(struct work_struct *work) +{ + int val; + int ret; + u8 low, med, high; + + struct ab8500_fg *di = container_of(work, + struct ab8500_fg, fg_acc_cur_work); + + mutex_lock(&di->cc_lock); + ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_CTRL, RD_NCONV_ACCU_REQ); + if (ret) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_LOW, &low); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_MED, &med); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, + AB8500_GASG_CC_NCOV_ACCU_HIGH, &high); + if (ret < 0) + goto exit; + + /* Check for sign bit in case of negative value, 2's compliment */ + if (high & 0x10) + val = (low | (med << 8) | (high << 16) | 0xFFE00000); + else + val = (low | (med << 8) | (high << 16)); + + di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10)/10000; + + di->avg_curr = (val * FG_LSB_IN_MA) / (di->fg_samples * 1000); + di->flags.conv_done = true; + + mutex_unlock(&di->cc_lock); + + queue_work(di->fg_wq, + &di->fg_work); + + return; +exit: + dev_err(di->dev, + "Failed to read or write gas gauge registers\n"); + mutex_unlock(&di->cc_lock); + queue_work(di->fg_wq, + &di->fg_work); +} + +/** + * ab8500_fg_bat_voltage() - get battery voltage + * @di: pointer to the ab8500_fg structure + * + * Returns battery voltage(on success) else error code + */ +static int ab8500_fg_bat_voltage(struct ab8500_fg *di) +{ + int vbat; + static int prev; + + vbat = ab8500_gpadc_convert(di->gpadc, MAIN_BAT_V); + if (vbat < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value\n", + __func__); + return prev; + } + + prev = vbat; + return vbat; +} + +/** + * ab8500_fg_volt_to_capacity() - Voltage based capacity + * @di: pointer to the ab8500_fg structure + * @voltage: The voltage to convert to a capacity + * + * Returns battery capacity in per mille based on voltage + */ +static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage) +{ + int i, tbl_size; + struct v_to_cap *tbl; + int cap = 0; + + tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl, + tbl_size = di->bat->bat_type[di->bat->batt_id].n_v_cap_tbl_elements; + + for (i = 0; i < tbl_size; ++i) { + if (voltage > tbl[i].voltage) + break; + } + + if ((i > 0) && (i < tbl_size)) { + cap = interpolate(voltage, + tbl[i].voltage, + tbl[i].capacity * 10, + tbl[i-1].voltage, + tbl[i-1].capacity * 10); + } else if (i == 0) { + cap = 1000; + } else { + cap = 0; + } + + dev_dbg(di->dev, "%s Vbat: %d, Cap: %d per mille", + __func__, voltage, cap); + + return cap; +} + +/** + * ab8500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity + * @di: pointer to the ab8500_fg structure + * + * Returns battery capacity based on battery voltage that is not compensated + * for the voltage drop due to the load + */ +static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di) +{ + di->vbat = ab8500_fg_bat_voltage(di); + return ab8500_fg_volt_to_capacity(di, di->vbat); +} + +/** + * ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity + * @di: pointer to the ab8500_fg structure + * + * Returns battery capacity based on battery voltage that is load compensated + * for the voltage drop + */ +static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) +{ + int vbat_comp; + + di->inst_curr = ab8500_fg_inst_curr(di); + di->vbat = ab8500_fg_bat_voltage(di); + + /* Use Ohms law to get the load compensated voltage */ + vbat_comp = di->vbat - (di->inst_curr * + di->bat->bat_type[di->bat->batt_id].battery_resistance) / 1000; + + dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, " + "R: %dmOhm, Current: %dmA\n", + __func__, + di->vbat, + vbat_comp, + di->bat->bat_type[di->bat->batt_id].battery_resistance, + di->inst_curr); + + return ab8500_fg_volt_to_capacity(di, vbat_comp); +} + +/** + * ab8500_fg_convert_mah_to_permille() - Capacity in mAh to permille + * @di: pointer to the ab8500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in permille + */ +static int ab8500_fg_convert_mah_to_permille(struct ab8500_fg *di, int cap_mah) +{ + return (cap_mah * 1000) / di->bat_cap.max_mah_design; +} + +/** + * ab8500_fg_convert_permille_to_mah() - Capacity in permille to mAh + * @di: pointer to the ab8500_fg structure + * @cap_pm: capacity in permille + * + * Converts capacity in permille to capacity in mAh + */ +static int ab8500_fg_convert_permille_to_mah(struct ab8500_fg *di, int cap_pm) +{ + return cap_pm * di->bat_cap.max_mah_design / 1000; +} + +/** + * ab8500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh + * @di: pointer to the ab8500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in uWh + */ +static int ab8500_fg_convert_mah_to_uwh(struct ab8500_fg *di, int cap_mah) +{ + u64 div_res; + u32 div_rem; + + div_res = ((u64) cap_mah) * ((u64) di->vbat_nom); + div_rem = do_div(div_res, 1000); + + /* Make sure to round upwards if necessary */ + if (div_rem >= 1000 / 2) + div_res++; + + return (int) div_res; +} + +/** + * ab8500_fg_calc_cap_charging() - Calculate remaining capacity while charging + * @di: pointer to the ab8500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. The filter is filled with this capacity + */ +static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di) +{ + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + /* + * We force capacity to 100% as long as the algorithm + * reports that it's full. + */ + if (di->bat_cap.mah >= di->bat_cap.max_mah_design || + di->flags.fully_charged) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + /* We need to update battery voltage and inst current when charging */ + di->vbat = ab8500_fg_bat_voltage(di); + di->inst_curr = ab8500_fg_inst_curr(di); + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage + * @di: pointer to the ab8500_fg structure + * @comp: if voltage should be load compensated before capacity calc + * + * Return the capacity in mAh based on the battery voltage. The voltage can + * either be load compensated or not. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di, bool comp) +{ + int permille, mah; + + if (comp) + permille = ab8500_fg_load_comp_volt_to_capacity(di); + else + permille = ab8500_fg_uncomp_volt_to_capacity(di); + + mah = ab8500_fg_convert_permille_to_mah(di, permille); + + di->bat_cap.mah = ab8500_fg_add_cap_sample(di, mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG + * @di: pointer to the ab8500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab8500_fg_calc_cap_discharge_fg(struct ab8500_fg *di) +{ + int permille_volt, permille; + + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + if (di->bat_cap.mah >= di->bat_cap.max_mah_design) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + /* + * Check against voltage based capacity. It can not be lower + * than what the uncompensated voltage says + */ + permille = ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + permille_volt = ab8500_fg_uncomp_volt_to_capacity(di); + + if (permille < permille_volt) { + di->bat_cap.permille = permille_volt; + di->bat_cap.mah = ab8500_fg_convert_permille_to_mah(di, + di->bat_cap.permille); + + dev_dbg(di->dev, "%s voltage based: perm %d perm_volt %d\n", + __func__, + permille, + permille_volt); + + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + } else { + ab8500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + } + + return di->bat_cap.mah; +} + +/** + * ab8500_fg_capacity_level() - Get the battery capacity level + * @di: pointer to the ab8500_fg structure + * + * Get the battery capacity level based on the capacity in percent + */ +static int ab8500_fg_capacity_level(struct ab8500_fg *di) +{ + int ret, percent; + + percent = di->bat_cap.permille / 10; + + if (percent <= di->bat->cap_levels->critical || + di->flags.low_bat) + ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else if (percent <= di->bat->cap_levels->low) + ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (percent <= di->bat->cap_levels->normal) + ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (percent <= di->bat->cap_levels->high) + ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else + ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + + return ret; +} + +/** + * ab8500_fg_check_capacity_limits() - Check if capacity has changed + * @di: pointer to the ab8500_fg structure + * @init: capacity is allowed to go up in init mode + * + * Check if capacity or capacity limit has changed and notify the system + * about it using the power_supply framework + */ +static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init) +{ + bool changed = false; + + di->bat_cap.level = ab8500_fg_capacity_level(di); + + if (di->bat_cap.level != di->bat_cap.prev_level) { + /* + * We do not allow reported capacity level to go up + * unless we're charging or if we're in init + */ + if (!(!di->flags.charging && di->bat_cap.level > + di->bat_cap.prev_level) || init) { + dev_dbg(di->dev, "level changed from %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + di->bat_cap.prev_level = di->bat_cap.level; + changed = true; + } else { + dev_dbg(di->dev, "level not allowed to go up " + "since no charger is connected: %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + } + } + + /* + * If we have received the LOW_BAT IRQ, set capacity to 0 to initiate + * shutdown + */ + if (di->flags.low_bat) { + dev_dbg(di->dev, "Battery low, set capacity to 0\n"); + di->bat_cap.prev_percent = 0; + di->bat_cap.permille = 0; + di->bat_cap.prev_mah = 0; + di->bat_cap.mah = 0; + changed = true; + } else if (di->bat_cap.prev_percent != di->bat_cap.permille / 10) { + if (di->bat_cap.permille / 10 == 0) { + /* + * We will not report 0% unless we've got + * the LOW_BAT IRQ, no matter what the FG + * algorithm says. + */ + di->bat_cap.prev_percent = 1; + di->bat_cap.permille = 1; + di->bat_cap.prev_mah = 1; + di->bat_cap.mah = 1; + + changed = true; + } else if (!(!di->flags.charging && + (di->bat_cap.permille / 10) > + di->bat_cap.prev_percent) || init) { + /* + * We do not allow reported capacity to go up + * unless we're charging or if we're in init + */ + dev_dbg(di->dev, + "capacity changed from %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + di->bat_cap.prev_percent = di->bat_cap.permille / 10; + di->bat_cap.prev_mah = di->bat_cap.mah; + + changed = true; + } else { + dev_dbg(di->dev, "capacity not allowed to go up since " + "no charger is connected: %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + } + } + + if (changed) + power_supply_changed(&di->fg_psy); + +} + +static void ab8500_fg_charge_state_to(struct ab8500_fg *di, + enum ab8500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Charge state from %d [%s] to %d [%s]\n", + di->charge_state, + charge_state[di->charge_state], + new_state, + charge_state[new_state]); + + di->charge_state = new_state; +} + +static void ab8500_fg_discharge_state_to(struct ab8500_fg *di, + enum ab8500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Disharge state from %d [%s] to %d [%s]\n", + di->discharge_state, + discharge_state[di->discharge_state], + new_state, + discharge_state[new_state]); + + di->discharge_state = new_state; +} + +/** + * ab8500_fg_algorithm_charging() - FG algorithm for when charging + * @di: pointer to the ab8500_fg structure + * + * Battery capacity calculation state machine for when we're charging + */ +static void ab8500_fg_algorithm_charging(struct ab8500_fg *di) +{ + /* + * If we change to discharge mode + * we should start with recovery + */ + if (di->discharge_state != AB8500_FG_DISCHARGE_INIT_RECOVERY) + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_INIT_RECOVERY); + + switch (di->charge_state) { + case AB8500_FG_CHARGE_INIT: + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_charging); + + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_READOUT); + + break; + + case AB8500_FG_CHARGE_READOUT: + /* + * Read the FG and calculate the new capacity + */ + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + ab8500_fg_calc_cap_charging(di); + + break; + + default: + break; + } + + /* Check capacity limits */ + ab8500_fg_check_capacity_limits(di, false); +} + +/** + * ab8500_fg_algorithm_discharging() - FG algorithm for when discharging + * @di: pointer to the ab8500_fg structure + * + * Battery capacity calculation state machine for when we're discharging + */ +static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) +{ + int sleep_time; + + /* If we change to charge mode we should start with init */ + if (di->charge_state != AB8500_FG_CHARGE_INIT) + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + + switch (di->discharge_state) { + case AB8500_FG_DISCHARGE_INIT: + /* We use the FG IRQ to work on */ + di->init_cnt = 0; + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->init_timer); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_INITMEASURING); + + /* Intentional fallthrough */ + + case AB8500_FG_DISCHARGE_INITMEASURING: + /* + * Discard a number of samples during startup. + * After that, use compensated voltage for a few + * samples to get an initial capacity. + * Then go to READOUT + */ + sleep_time = di->bat->fg_params->init_timer; + + /* Discard the first [x] seconds */ + if (di->init_cnt > + di->bat->fg_params->init_discard_time) { + + ab8500_fg_calc_cap_discharge_voltage(di, true); + + ab8500_fg_check_capacity_limits(di, true); + } + + di->init_cnt += sleep_time; + if (di->init_cnt > + di->bat->fg_params->init_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + } + + break; + + case AB8500_FG_DISCHARGE_INIT_RECOVERY: + di->recovery_cnt = 0; + di->recovery_needed = true; + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_RECOVERY); + + /* Intentional fallthrough */ + + case AB8500_FG_DISCHARGE_RECOVERY: + sleep_time = di->bat->fg_params->recovery_sleep_timer; + + /* + * We should check the power consumption + * If low, go to READOUT (after x min) or + * RECOVERY_SLEEP if time left. + * If high, go to READOUT + */ + di->inst_curr = ab8500_fg_inst_curr(di); + + if (ab8500_fg_is_low_curr(di, di->inst_curr)) { + if (di->recovery_cnt > + di->bat->fg_params->recovery_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + di->recovery_needed = false; + } else { + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, + sleep_time * HZ); + } + di->recovery_cnt += sleep_time; + } else { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_READOUT); + } + + break; + + case AB8500_FG_DISCHARGE_READOUT: + di->inst_curr = ab8500_fg_inst_curr(di); + + if (ab8500_fg_is_low_curr(di, di->inst_curr)) { + /* Detect mode change */ + if (di->high_curr_mode) { + di->high_curr_mode = false; + di->high_curr_cnt = 0; + } + + if (di->recovery_needed) { + ab8500_fg_discharge_state_to(di, + AB8500_FG_DISCHARGE_RECOVERY); + + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, + 0); + + break; + } + + ab8500_fg_calc_cap_discharge_voltage(di, true); + } else { + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + /* Detect mode change */ + if (!di->high_curr_mode) { + di->high_curr_mode = true; + di->high_curr_cnt = 0; + } + + di->high_curr_cnt += + di->bat->fg_params->accu_high_curr; + if (di->high_curr_cnt > + di->bat->fg_params->high_curr_time) + di->recovery_needed = true; + + ab8500_fg_calc_cap_discharge_fg(di); + } + + ab8500_fg_check_capacity_limits(di, false); + + break; + + case AB8500_FG_DISCHARGE_WAKEUP: + ab8500_fg_coulomb_counter(di, true); + di->inst_curr = ab8500_fg_inst_curr(di); + + ab8500_fg_calc_cap_discharge_voltage(di, true); + + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + /* Re-program number of samples set above */ + ab8500_fg_coulomb_counter(di, true); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_READOUT); + + ab8500_fg_check_capacity_limits(di, false); + + break; + + default: + break; + } +} + +/** + * ab8500_fg_algorithm() - Entry point for the FG algorithm + * @di: pointer to the ab8500_fg structure + * + * Entry point for the battery capacity calculation state machine + */ +static void ab8500_fg_algorithm(struct ab8500_fg *di) +{ + if (di->flags.charging) + ab8500_fg_algorithm_charging(di); + else + ab8500_fg_algorithm_discharging(di); + + dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d " + "%d %d %d %d %d %d %d\n", + di->bat_cap.max_mah_design, + di->bat_cap.mah, + di->bat_cap.permille, + di->bat_cap.level, + di->bat_cap.prev_mah, + di->bat_cap.prev_percent, + di->bat_cap.prev_level, + di->vbat, + di->inst_curr, + di->avg_curr, + di->accu_charge, + di->flags.charging, + di->charge_state, + di->discharge_state, + di->high_curr_mode, + di->recovery_needed); +} + +/** + * ab8500_fg_periodic_work() - Run the FG state machine periodically + * @work: pointer to the work_struct structure + * + * Work queue function for periodic work + */ +static void ab8500_fg_periodic_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_periodic_work.work); + + if (di->init_capacity) { + /* A dummy read that will return 0 */ + di->inst_curr = ab8500_fg_inst_curr(di); + /* Get an initial capacity calculation */ + ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_check_capacity_limits(di, true); + di->init_capacity = false; + } else { + ab8500_fg_algorithm(di); + } +} + +/** + * ab8500_fg_low_bat_work() - Check LOW_BAT condition + * @work: pointer to the work_struct structure + * + * Work queue function for checking the LOW_BAT condition + */ +static void ab8500_fg_low_bat_work(struct work_struct *work) +{ + int vbat; + + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_low_bat_work.work); + + vbat = ab8500_fg_bat_voltage(di); + + /* Check if LOW_BAT still fulfilled */ + if (vbat < di->bat->fg_params->lowbat_threshold) { + di->flags.low_bat = true; + dev_warn(di->dev, "Battery voltage still LOW\n"); + + /* + * We need to re-schedule this check to be able to detect + * if the voltage increases again during charging + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } else { + di->flags.low_bat = false; + dev_warn(di->dev, "Battery voltage OK again\n"); + } + + /* This is needed to dispatch LOW_BAT */ + ab8500_fg_check_capacity_limits(di, false); + + /* Set this flag to check if LOW_BAT IRQ still occurs */ + di->flags.low_bat_delay = false; +} + +/** + * ab8500_fg_instant_work() - Run the FG state machine instantly + * @work: pointer to the work_struct structure + * + * Work queue function for instant work + */ +static void ab8500_fg_instant_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, fg_work); + + ab8500_fg_algorithm(di); +} + +/** + * ab8500_fg_cc_convend_handler() - isr to get battery avg current. + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_cc_convend_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + queue_work(di->fg_wq, &di->fg_acc_cur_work); + + return IRQ_HANDLED; +} + +/** + * ab8500_fg_batt_ovv_handler() - Battery OVV occured + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_batt_ovv_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + dev_dbg(di->dev, "Battery OVV\n"); + di->flags.bat_ovv = true; + + power_supply_changed(&di->fg_psy); + + return IRQ_HANDLED; +} + +/** + * ab8500_fg_lowbatf_handler() - Battery voltage is below LOW threshold + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_lowbatf_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + + if (!di->flags.low_bat_delay) { + dev_warn(di->dev, "Battery voltage is below LOW threshold\n"); + di->flags.low_bat_delay = true; + /* + * Start a timer to check LOW_BAT again after some time + * This is done to avoid shutdown on single voltage dips + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } + return IRQ_HANDLED; +} + +/** + * ab8500_fg_get_property() - get the fg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * fg properties by reading the sysfs files. + * voltage_now: battery voltage + * current_now: battery instant current + * current_avg: battery average current + * charge_full_design: capacity where battery is considered full + * charge_now: battery capacity in nAh + * capacity: capacity in percent + * capacity_level: capacity level + * + * Returns error code in case of failure else 0 on success + */ +static int ab8500_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + /* + * If battery is identified as unknown and charging of unknown + * batteries is disabled, we always report 100% capacity and + * capacity level UNKNOWN, since we can't calculate + * remaining capacity + */ + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (di->flags.bat_ovv) + val->intval = 47500000; + else + val->intval = di->vbat * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->inst_curr * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = di->avg_curr * 1000; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah_design); + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + else + val->intval = ab8500_fg_convert_mah_to_uwh(di, + di->bat_cap.prev_mah); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = di->bat_cap.max_mah_design; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = di->bat_cap.max_mah; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = di->bat_cap.max_mah; + else + val->intval = di->bat_cap.prev_mah; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = 100; + else + val->intval = di->bat_cap.prev_percent; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + else + val->intval = di->bat_cap.prev_level; + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_fg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_fg_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + switch (ret.intval) { + case POWER_SUPPLY_STATUS_UNKNOWN: + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (!di->flags.charging) + break; + di->flags.charging = false; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_FULL: + if (di->flags.fully_charged) + break; + di->flags.fully_charged = true; + /* Save current capacity as maximum */ + di->bat_cap.max_mah = di->bat_cap.mah; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (di->flags.charging) + break; + di->flags.charging = true; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + }; + default: + break; + }; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->flags.batt_unknown = false; + else + di->flags.batt_unknown = true; + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_fg_init_hw_registers() - Set up FG related registers + * @di: pointer to the ab8500_fg structure + * + * Set up battery OVV, low battery voltage registers + */ +static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) +{ + int ret; + + /* Set up VBAT OVV register */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, + AB8500_BATT_OVV, + (BATT_OVV_ENA | BATT_OVV_TH_4P75)); + if (ret) { + dev_err(di->dev, "failed to set BATT_OVV\n"); + goto out; + } + + /* Low Battery Voltage */ + ret = abx500_set_register_interruptible(di->dev, + AB8500_SYS_CTRL2_BLOCK, + AB8500_LOW_BAT_REG, + ab8500_volt_to_regval( + di->bat->fg_params->lowbat_threshold) << 1 | + LOW_BAT_ENABLE); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + goto out; + } + +out: + return ret; +} + +/** + * ab8500_fg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void ab8500_fg_external_power_changed(struct power_supply *psy) +{ + struct ab8500_fg *di = to_ab8500_fg_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->fg_psy, ab8500_fg_get_ext_psy_data); +} + +#if defined(CONFIG_PM) +static int ab8500_fg_resume(struct platform_device *pdev) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + /* + * Change state if we're not charging. If we're charging we will wake + * up on the FG IRQ + */ + if (!di->flags.charging) { + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_WAKEUP); + queue_work(di->fg_wq, &di->fg_work); + } + + return 0; +} + +static int ab8500_fg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + flush_delayed_work(&di->fg_periodic_work); + + /* + * If the FG is enabled we will disable it before going to suspend + * only if we're not charging + */ + if (di->flags.fg_enabled && !di->flags.charging) + ab8500_fg_coulomb_counter(di, false); + + return 0; +} +#else +#define ab8500_fg_suspend NULL +#define ab8500_fg_resume NULL +#endif + +static int __devexit ab8500_fg_remove(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_fg *di = platform_get_drvdata(pdev); + + /* Disable coulomb counter */ + ret = ab8500_fg_coulomb_counter(di, false); + if (ret) + dev_err(di->dev, "failed to disable coulomb counter\n"); + + destroy_workqueue(di->fg_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->fg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + return ret; +} + +/* ab8500 fg driver interrupts and their respective isr */ +static struct ab8500_fg_interrupts ab8500_fg_irq[] = { + {"NCONV_ACCU", ab8500_fg_cc_convend_handler}, + {"BATT_OVV", ab8500_fg_batt_ovv_handler}, + {"LOW_BAT_F", ab8500_fg_lowbatf_handler}, +}; + +static int __devinit ab8500_fg_probe(struct platform_device *pdev) +{ + int i, irq; + struct ab8500_platform_data *plat; + int ret = 0; + + struct ab8500_fg *di = + kzalloc(sizeof(struct ab8500_fg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + mutex_init(&di->cc_lock); + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab8500_gpadc_get(); + + plat = dev_get_platdata(di->parent->dev); + + /* get fg specific platform data */ + if (!plat->fg) { + dev_err(di->dev, "no fg platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->pdata = plat->fg; + + /* get battery specific platform data */ + if (!plat->battery) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + di->bat = plat->battery; + + di->fg_psy.name = "ab8500_fg"; + di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->fg_psy.properties = ab8500_fg_props; + di->fg_psy.num_properties = ARRAY_SIZE(ab8500_fg_props); + di->fg_psy.get_property = ab8500_fg_get_property; + di->fg_psy.supplied_to = di->pdata->supplied_to; + di->fg_psy.num_supplicants = di->pdata->num_supplicants; + di->fg_psy.external_power_changed = ab8500_fg_external_power_changed; + + di->bat_cap.max_mah_design = MILLI_TO_MICRO * + di->bat->bat_type[di->bat->batt_id].charge_full_design; + + di->bat_cap.max_mah = di->bat_cap.max_mah_design; + + di->vbat_nom = di->bat->bat_type[di->bat->batt_id].nominal_voltage; + + di->init_capacity = true; + + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); + + /* Create a work queue for running the FG algorithm */ + di->fg_wq = create_singlethread_workqueue("ab8500_fg_wq"); + if (di->fg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for running the fg algorithm instantly */ + INIT_WORK(&di->fg_work, ab8500_fg_instant_work); + + /* Init work for getting the battery accumulated current */ + INIT_WORK(&di->fg_acc_cur_work, ab8500_fg_acc_cur_work); + + /* Work delayed Queue to run the state machine */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_periodic_work, + ab8500_fg_periodic_work); + + /* Work to check low battery condition */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_low_bat_work, + ab8500_fg_low_bat_work); + + /* Initialize OVV, and other registers */ + ret = ab8500_fg_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize registers\n"); + goto free_fg_wq; + } + + /* Consider battery unknown until we're informed otherwise */ + di->flags.batt_unknown = true; + + /* Register FG power supply class */ + ret = power_supply_register(di->dev, &di->fg_psy); + if (ret) { + dev_err(di->dev, "failed to register FG psy\n"); + goto free_fg_wq; + } + + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); + ab8500_fg_coulomb_counter(di, true); + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_fg_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_fg_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_fg_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_fg_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + /* Run the FG algorithm */ + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + + return ret; + +free_irq: + power_supply_unregister(&di->fg_psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); + free_irq(irq, di); + } +free_fg_wq: + destroy_workqueue(di->fg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab8500_fg_driver = { + .probe = ab8500_fg_probe, + .remove = __devexit_p(ab8500_fg_remove), + .suspend = ab8500_fg_suspend, + .resume = ab8500_fg_resume, + .driver = { + .name = "ab8500-fg", + .owner = THIS_MODULE, + }, +}; + +static int __init ab8500_fg_init(void) +{ + return platform_driver_register(&ab8500_fg_driver); +} + +static void __exit ab8500_fg_exit(void) +{ + platform_driver_unregister(&ab8500_fg_driver); +} + +subsys_initcall_sync(ab8500_fg_init); +module_exit(ab8500_fg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab8500-fg"); +MODULE_DESCRIPTION("AB8500 Fuel Gauge driver"); diff --git a/include/linux/mfd/ab8500.h b/include/linux/mfd/ab8500.h index 838c6b487cc..6219e1139e8 100644 --- a/include/linux/mfd/ab8500.h +++ b/include/linux/mfd/ab8500.h @@ -139,6 +139,9 @@ #define AB8500_NR_IRQS 112 #define AB8500_NUM_IRQ_REGS 14 +/* Forward declaration */ +struct ab8500_charger; + /** * struct ab8500 - ab8500 internal structure * @dev: parent device @@ -152,12 +155,12 @@ * @tx_buf: tx buf for SPI * @mask: cache of IRQ regs for bus lock * @oldmask: cache of previous IRQ regs for bus lock + * @charger: pointer to the charger driver device information. */ struct ab8500 { struct device *dev; struct mutex lock; struct mutex irq_lock; - int irq_base; int irq; u8 chip_id; @@ -170,10 +173,14 @@ struct ab8500 { u8 mask[AB8500_NUM_IRQ_REGS]; u8 oldmask[AB8500_NUM_IRQ_REGS]; + + struct ab8500_charger *charger; }; struct regulator_reg_init; struct regulator_init_data; +struct ab8500_denc_platform_data; +struct ab8500_audio_platform_data; struct ab8500_gpio_platform_data; /** @@ -184,14 +191,25 @@ struct ab8500_gpio_platform_data; * @regulator_reg_init: regulator init registers * @num_regulator: number of regulators * @regulator: machine-specific constraints for regulators + * @battery: machine-specific battery management data + * @charger: machine-specific charger data + * @btemp: machine-specific battery temp data */ struct ab8500_platform_data { int irq_base; + bool pm_power_off; void (*init) (struct ab8500 *); int num_regulator_reg_init; struct ab8500_regulator_reg_init *regulator_reg_init; int num_regulator; struct regulator_init_data *regulator; + struct ab8500_bm_data *battery; + struct ab8500_denc_platform_data *denc; + struct ab8500_audio_platform_data *audio; + struct ab8500_charger_platform_data *charger; + struct ab8500_btemp_platform_data *btemp; + struct ab8500_fg_platform_data *fg; + struct ab8500_chargalg_platform_data *chargalg; struct ab8500_gpio_platform_data *gpio; }; diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h new file mode 100644 index 00000000000..aacba93b912 --- /dev/null +++ b/include/linux/mfd/ab8500/bm.h @@ -0,0 +1,456 @@ +/* + * Copyright ST-Ericsson 2009. + * + * Author: Arun Murthy + * Licensed under GPLv2. + */ + +#ifndef _AB8500_BM_H +#define _AB8500_BM_H + +/* + * System control 2 register offsets. + * bank = 0x02 + */ +#define AB8500_MAIN_WDOG_CTRL_REG 0x01 +#define AB8500_LOW_BAT_REG 0x03 + +/* + * USB/ULPI register offsets + * Bank : 0x5 + */ +#define AB8500_USB_LINE_STAT_REG 0x80 + +/* + * Charger / status register offfsets + * Bank : 0x0B + */ +#define AB8500_CH_STATUS1_REG 0x00 +#define AB8500_CH_STATUS2_REG 0x01 +#define AB8500_CH_USBCH_STAT1_REG 0x02 +#define AB8500_CH_USBCH_STAT2_REG 0x03 +#define AB8500_CH_FSM_STAT_REG 0x04 +#define AB8500_CH_STAT_REG 0x05 + +/* + * Charger / control register offfsets + * Bank : 0x0B + */ +#define AB8500_CH_VOLT_LVL_REG 0x40 +#define AB8500_CH_VOLT_LVL_MAX_REG 0x41 /*Only in Cut2.0*/ +#define AB8500_CH_OPT_CRNTLVL_REG 0x42 +#define AB8500_CH_OPT_CRNTLVL_MAX_REG 0x43 /*Only in Cut2.0*/ +#define AB8500_CH_WD_TIMER_REG 0x50 +#define AB8500_CHARG_WD_CTRL 0x51 +#define AB8500_BTEMP_HIGH_TH 0x52 +#define AB8500_LED_INDICATOR_PWM_CTRL 0x53 +#define AB8500_LED_INDICATOR_PWM_DUTY 0x54 +#define AB8500_BATT_OVV 0x55 +#define AB8500_BAT_CTRL_CURRENT_SOURCE 0x60 /*Only in Cut2.0*/ + +/* + * Charger / main control register offsets + * Bank : 0x0B + */ +#define AB8500_MCH_CTRL1 0x80 +#define AB8500_MCH_CTRL2 0x81 +#define AB8500_MCH_IPT_CURLVL_REG 0x82 +#define AB8500_CH_WD_REG 0x83 + +/* + * Charger / USB control register offsets + * Bank : 0x0B + */ +#define AB8500_USBCH_CTRL1_REG 0xC0 +#define AB8500_USBCH_CTRL2_REG 0xC1 +#define AB8500_USBCH_IPT_CRNTLVL_REG 0xC2 + +/* + * Gas Gauge register offsets + * Bank : 0x0C + */ +#define AB8500_GASG_CC_CTRL_REG 0x00 +#define AB8500_GASG_CC_ACCU1_REG 0x01 +#define AB8500_GASG_CC_ACCU2_REG 0x02 +#define AB8500_GASG_CC_ACCU3_REG 0x03 +#define AB8500_GASG_CC_ACCU4_REG 0x04 +#define AB8500_GASG_CC_SMPL_CNTRL_REG 0x05 +#define AB8500_GASG_CC_SMPL_CNTRH_REG 0x06 +#define AB8500_GASG_CC_SMPL_CNVL_REG 0x07 +#define AB8500_GASG_CC_SMPL_CNVH_REG 0x08 +#define AB8500_GASG_CC_CNTR_AVGOFF_REG 0x09 +#define AB8500_GASG_CC_OFFSET_REG 0x0A +#define AB8500_GASG_CC_NCOV_ACCU 0x10 +#define AB8500_GASG_CC_NCOV_ACCU_CTRL 0x11 +#define AB8500_GASG_CC_NCOV_ACCU_LOW 0x12 +#define AB8500_GASG_CC_NCOV_ACCU_MED 0x13 +#define AB8500_GASG_CC_NCOV_ACCU_HIGH 0x14 + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB8500_IT_SOURCE2_REG 0x01 +#define AB8500_IT_SOURCE21_REG 0x14 + +/* + * RTC register offsets + * Bank: 0x0F + */ +#define AB8500_RTC_BACKUP_CHG_REG 0x0C +#define AB8500_RTC_CC_CONF_REG 0x01 +#define AB8500_RTC_CTRL_REG 0x0B + +/* + * OTP register offsets + * Bank : 0x15 + */ +#define AB8500_OTP_CONF_15 0x0E + +/* GPADC constants from AB8500 spec, UM0836 */ +#define ADC_RESOLUTION 1024 +#define ADC_CH_MAIN_MIN 0 +#define ADC_CH_MAIN_MAX 20030 +#define ADC_CH_VBUS_MIN 0 +#define ADC_CH_VBUS_MAX 20030 +#define ADC_CH_VBAT_MIN 2300 +#define ADC_CH_VBAT_MAX 4800 +#define ADC_CH_BKBAT_MIN 0 +#define ADC_CH_BKBAT_MAX 3200 + +/* Main charge i/p current */ +#define MAIN_CH_IP_CUR_0P9A 0x80 +#define MAIN_CH_IP_CUR_1P0A 0x90 +#define MAIN_CH_IP_CUR_1P1A 0xA0 +#define MAIN_CH_IP_CUR_1P2A 0xB0 +#define MAIN_CH_IP_CUR_1P3A 0xC0 +#define MAIN_CH_IP_CUR_1P4A 0xD0 +#define MAIN_CH_IP_CUR_1P5A 0xE0 + +/* ChVoltLevel */ +#define CH_VOL_LVL_3P5 0x00 +#define CH_VOL_LVL_4P0 0x14 +#define CH_VOL_LVL_4P05 0x16 +#define CH_VOL_LVL_4P1 0x1B +#define CH_VOL_LVL_4P15 0x20 +#define CH_VOL_LVL_4P2 0x25 +#define CH_VOL_LVL_4P6 0x4D + +/* ChOutputCurrentLevel */ +#define CH_OP_CUR_LVL_0P1 0x00 +#define CH_OP_CUR_LVL_0P2 0x01 +#define CH_OP_CUR_LVL_0P3 0x02 +#define CH_OP_CUR_LVL_0P4 0x03 +#define CH_OP_CUR_LVL_0P5 0x04 +#define CH_OP_CUR_LVL_0P6 0x05 +#define CH_OP_CUR_LVL_0P7 0x06 +#define CH_OP_CUR_LVL_0P8 0x07 +#define CH_OP_CUR_LVL_0P9 0x08 +#define CH_OP_CUR_LVL_1P4 0x0D +#define CH_OP_CUR_LVL_1P5 0x0E +#define CH_OP_CUR_LVL_1P6 0x0F + +/* BTEMP High thermal limits */ +#define BTEMP_HIGH_TH_57_0 0x00 +#define BTEMP_HIGH_TH_52 0x01 +#define BTEMP_HIGH_TH_57_1 0x02 +#define BTEMP_HIGH_TH_62 0x03 + +/* current is mA */ +#define USB_0P1A 100 +#define USB_0P2A 200 +#define USB_0P3A 300 +#define USB_0P4A 400 +#define USB_0P5A 500 + +/* UsbChCurrLevel */ +#define USB_CH_IP_CUR_LVL_0P05 0x00 +#define USB_CH_IP_CUR_LVL_0P09 0x10 +#define USB_CH_IP_CUR_LVL_0P19 0x20 +#define USB_CH_IP_CUR_LVL_0P29 0x30 +#define USB_CH_IP_CUR_LVL_0P38 0x40 +#define USB_CH_IP_CUR_LVL_0P45 0x50 +#define USB_CH_IP_CUR_LVL_0P5 0x60 +#define USB_CH_IP_CUR_LVL_0P9 0xA0 +#define USB_CH_IP_CUR_LVL_1P0 0xB0 +#define USB_CH_IP_CUR_LVL_1P1 0xC0 +#define USB_CH_IP_CUR_LVL_1P3 0xD0 +#define USB_CH_IP_CUR_LVL_1P4 0xE0 +#define USB_CH_IP_CUR_LVL_1P5 0xF0 + +#define LOW_BAT_3P1V 0x20 +#define LOW_BAT_2P3V 0x00 +#define LOW_BAT_RESET 0x01 +#define LOW_BAT_ENABLE 0x01 + +/* Backup battery constants */ +#define BUP_ICH_SEL_50UA 0x00 +#define BUP_ICH_SEL_150UA 0x04 +#define BUP_ICH_SEL_300UA 0x08 +#define BUP_ICH_SEL_700UA 0x0C + +#define BUP_VCH_SEL_2P5V 0x00 +#define BUP_VCH_SEL_2P6V 0x01 +#define BUP_VCH_SEL_2P8V 0x02 +#define BUP_VCH_SEL_3P1V 0x03 + +/* Battery OVV constants */ +#define BATT_OVV_ENA 0x02 +#define BATT_OVV_TH_3P7 0x00 +#define BATT_OVV_TH_4P75 0x01 + +/* Fuel Gauge constants */ +#define RESET_ACCU 0x02 +#define READ_REQ 0x01 +#define CC_DEEP_SLEEP_ENA 0x02 +#define CC_PWR_UP_ENA 0x01 +#define CC_SAMPLES_40 0x28 +#define RD_NCONV_ACCU_REQ 0x01 + +/* RTC constants */ +#define RTC_BUP_CH_ENA 0x10 + +/* BatCtrl Current Source Constants */ +#define BAT_CTRL_7U_ENA 0x01 +#define BAT_CTRL_20U_ENA 0x02 +#define BAT_CTRL_CMP_ENA 0x04 +#define FORCE_BAT_CTRL_CMP_HIGH 0x08 +#define BAT_CTRL_PULL_UP_ENA 0x10 + +/* Battery type */ +#define BATTERY_UNKNOWN 00 + +/* + * ADC for the battery thermistor. + * When using the ADC_THERM_BATCTRL the battery ID resistor is combined with + * a NTC resistor to both identify the battery and to measure its temperature. + * Different phone manufactures uses different techniques to both identify the + * battery and to read its temperature. + */ +enum adc_therm { + ADC_THERM_BATCTRL, + ADC_THERM_BATTEMP, +}; + +/** + * struct res_to_temp - defines one point in a temp to res curve. To + * be used in battery packs that combines the identification resistor with a + * NTC resistor. + * @temp: battery pack temperature in Celcius + * @resist: NTC resistor net total resistance + */ +struct res_to_temp { + int temp; + int resist; +}; + +/** + * struct v_to_cap - Table for translating voltage to capacity + * @voltage: Voltage in mV + * @capacity: Capacity in percent + */ +struct v_to_cap { + int voltage; + int capacity; +}; + +/* Forward declaration */ +struct ab8500_fg; + +/** + * struct ab8500_fg_parameters - Fuel gauge algorithm parameters, in seconds + * if not specified + * @recovery_sleep_timer: Time between measurements while recovering + * @recovery_total_time: Total recovery time + * @init_timer: Measurement interval during startup + * @init_discard_time: Time we discard voltage measurement at startup + * @init_total_time: Total init time during startup + * @high_curr_time: Time current has to be high to go to recovery + * @accu_charging: FG accumulation time while charging + * @accu_high_curr: FG accumulation time in high current mode + * @high_curr_threshold: High current threshold, in mA + * @lowbat_threshold: Low battery threshold, in mV + */ +struct ab8500_fg_parameters { + int recovery_sleep_timer; + int recovery_total_time; + int init_timer; + int init_discard_time; + int init_total_time; + int high_curr_time; + int accu_charging; + int accu_high_curr; + int high_curr_threshold; + int lowbat_threshold; +}; + +/** + * struct ab8500_charger_maximization - struct used by the board config. + * @use_maxi: Enable maximization for this battery type + * @maxi_chg_curr: Maximum charger current allowed + * @maxi_wait_cycles: cycles to wait before setting charger current + * @charger_curr_step delta between two charger current settings (mA) + */ +struct ab8500_maxim_parameters { + bool ena_maxi; + int chg_curr; + int wait_cycles; + int charger_curr_step; +}; + +/** + * struct battery_type - different batteries supported + * @name: battery technology + * @resis_high: battery upper resistance limit + * @resis_low: battery lower resistance limit + * @charge_full_design: Maximum battery capacity in mAh + * @nominal_voltage: Nominal voltage of the battery in mV + * @termination_vol: max voltage upto which battery can be charged + * @normal_cur_lvl: charger current in normal state in mA + * @normal_vol_lvl: charger voltage in normal state in mV + * @maint_a_cur_lvl: charger current in maintenance A state in mA + * @maint_a_vol_lvl: charger voltage in maintenance A state in mV + * @maint_a_chg_timer_h: charge time in maintenance A state + * @maint_b_cur_lvl: charger current in maintenance B state in mA + * @maint_b_vol_lvl: charger voltage in maintenance B state in mV + * @maint_b_chg_timer_h: charge time in maintenance B state + * @low_high_cur_lvl: charger current in temp low/high state in mA + * @low_high_vol_lvl: charger voltage in temp low/high state in mV' + * @battery_resistance: battery inner resistance in mOhm. + * @n_r_t_tbl_elements: number of elements in r_to_t_tbl + * @r_to_t_tbl: table containing resistance to temp points + * @n_v_cap_tbl_elements: number of elements in v_to_cap_tbl + * @v_to_cap_tbl: Voltage to capacity (in %) table + */ +struct battery_type { + int name; + int resis_high; + int resis_low; + int charge_full_design; + int nominal_voltage; + int termination_vol; + int termination_curr; + int normal_cur_lvl; + int normal_vol_lvl; + int maint_a_cur_lvl; + int maint_a_vol_lvl; + int maint_a_chg_timer_h; + int maint_b_cur_lvl; + int maint_b_vol_lvl; + int maint_b_chg_timer_h; + int low_high_cur_lvl; + int low_high_vol_lvl; + int battery_resistance; + int n_temp_tbl_elements; + struct res_to_temp *r_to_t_tbl; + int n_v_cap_tbl_elements; + struct v_to_cap *v_to_cap_tbl; +}; + +/** + * struct ab8500_bm_capacity_levels - ab8500 capacity level data + * @critical: critical capacity level in percent + * @low: low capacity level in percent + * @normal: normal capacity level in percent + * @high: high capacity level in percent + * @full: full capacity level in percent + */ +struct ab8500_bm_capacity_levels { + int critical; + int low; + int normal; + int high; + int full; +}; + +/** + * struct ab8500_bm_charger_parameters - Charger specific parameters + * @usb_volt_max: maximum allowed USB charger voltage in mV + * @usb_curr_max: maximum allowed USB charger current in mA + * @ac_volt_max: maximum allowed AC charger voltage in mV + * @ac_curr_max: maximum allowed AC charger current in mA + */ +struct ab8500_bm_charger_parameters { + int usb_volt_max; + int usb_curr_max; + int ac_volt_max; + int ac_curr_max; +}; + +/** + * struct ab8500_bm_data - ab8500 battery management data + * @temp_under under this temp, charging is stopped + * @temp_low between this temp and temp_under charging is reduced + * @temp_high between this temp and temp_over charging is reduced + * @temp_over over this temp, charging is stopped + * @main_safety_tmr_h safety timer for main charger + * @usb_safety_tmr_h safety timer for usb charger + * @bkup_bat_v voltage which we charge the backup battery with + * @bkup_bat_i current which we charge the backup battery with + * @adc_therm placement of thermistor, batctrl or battemp adc + * @chg_unknown_bat flag to enable charging of unknown batteries + * @enable_overshoot flag to enable VBAT overshoot control + * @fg_res resistance of FG resistor in mOhm + * @n_btypes number of elements in array bat_type + * @batt_id index of the identified battery in array bat_type + * @interval_charging charge alg cycle period time when charging (sec) + * @interval_not_charging charge alg cycle period time when not charging (sec) + * @temp_hysteresis temperature hysteresis + * @maxi: maximization parameters + * @cap_levels capacity in percent for the different capacity levels + * @bat_type table of supported battery types + * @chg_params charger parameters + * @fg_params fuel gauge parameters + */ +struct ab8500_bm_data { + int temp_under; + int temp_low; + int temp_high; + int temp_over; + int main_safety_tmr_h; + int usb_safety_tmr_h; + int bkup_bat_v; + int bkup_bat_i; + bool chg_unknown_bat; + bool enable_overshoot; + enum adc_therm adc_therm; + int fg_res; + int n_btypes; + int batt_id; + int interval_charging; + int interval_not_charging; + int temp_hysteresis; + const struct ab8500_maxim_parameters *maxi; + const struct ab8500_bm_capacity_levels *cap_levels; + const struct battery_type *bat_type; + const struct ab8500_bm_charger_parameters *chg_params; + const struct ab8500_fg_parameters *fg_params; +}; + +struct ab8500_charger_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct ab8500_btemp_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct ab8500_fg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct ab8500_chargalg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; +#ifdef CONFIG_AB8500_BM +void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); +#else +static void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) +{ +} +#endif +#endif /* _AB8500_BM_H */ diff --git a/include/linux/mfd/ab8500/denc-regs.h b/include/linux/mfd/ab8500/denc-regs.h new file mode 100644 index 00000000000..a6683ca7470 --- /dev/null +++ b/include/linux/mfd/ab8500/denc-regs.h @@ -0,0 +1,357 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * ST-Ericsson AB8500 DENC related registers + * + * Author: Marcus Tunnissen + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ + +#ifndef __AB8500_DENC_H +#define __AB8500_DENC_H + +#define AB8500_VAL2REG(__reg, __fld, __val) \ + (((__val) << __reg##_##__fld##_SHIFT) & __reg##_##__fld##_MASK) +#define AB8500_REG2VAL(__reg, __fld, __val) \ + (((__val) & __reg##_##__fld##_MASK) >> __reg##_##__fld##_SHIFT) + +#define AB8500_CTRL3 0x00000200 +#define AB8500_CTRL3_TH_SD_ENA_SHIFT 3 +#define AB8500_CTRL3_TH_SD_ENA_MASK 0x00000008 +#define AB8500_CTRL3_TH_SD_ENA(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, TH_SD_ENA, __x) +#define AB8500_CTRL3_RESET_DENC_N_SHIFT 2 +#define AB8500_CTRL3_RESET_DENC_N_MASK 0x00000004 +#define AB8500_CTRL3_RESET_DENC_N(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, RESET_DENC_N, __x) +#define AB8500_CTRL3_RESET_AUD_N_SHIFT 1 +#define AB8500_CTRL3_RESET_AUD_N_MASK 0x00000002 +#define AB8500_CTRL3_RESET_AUD_N(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, RESET_AUD_N, __x) +#define AB8500_CTRL3_CLK_32K_OUT2_IS_SHIFT 0 +#define AB8500_CTRL3_CLK_32K_OUT2_IS_MASK 0x00000001 +#define AB8500_CTRL3_CLK_32K_OUT2_IS(__x) \ + AB8500_VAL2REG(AB8500_CTRL3, CLK_32K_OUT2_IS, __x) +#define AB8500_SYS_ULP_CLK_CONF 0x0000020A +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_PD_ENA_SHIFT 7 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_PD_ENA_MASK 0x00000080 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_PD_ENA(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_PD_ENA, __x) +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_BUF_ENA_SHIFT 6 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_BUF_ENA_MASK 0x00000040 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_BUF_ENA(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_BUF_ENA, __x) +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_STRE_SHIFT 5 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_STRE_MASK 0x00000020 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_STRE(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, ULP_CLK_STRE, __x) +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_INV_SHIFT 4 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_INV_MASK 0x00000010 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_INV(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_INV, __x) +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_DE_IN_SHIFT 3 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_DE_IN_MASK 0x00000008 +#define AB8500_SYS_ULP_CLK_CONF_TVOUT_CLK_DE_IN(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, TVOUT_CLK_DE_IN, __x) +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_STRE_SHIFT 2 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_STRE_MASK 0x00000004 +#define AB8500_SYS_ULP_CLK_CONF_CLK_27MHZ_STRE(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, CLK_27MHZ_STRE, __x) +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_SHIFT 0 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_MASK 0x00000003 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_NO_FUNC 0 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_AS_OUTPUT 1 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_AS_INPUT 2 +#define AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF(__x) \ + AB8500_VAL2REG(AB8500_SYS_ULP_CLK_CONF, ULP_CLK_CONF, \ + AB8500_SYS_ULP_CLK_CONF_ULP_CLK_CONF_##__x) +#define AB8500_SYS_CLK_CTRL 0x0000020C +#define AB8500_SYS_CLK_CTRL_USB_CLK_VALID_SHIFT 2 +#define AB8500_SYS_CLK_CTRL_USB_CLK_VALID_MASK 0x00000004 +#define AB8500_SYS_CLK_CTRL_USB_CLK_VALID(__x) \ + AB8500_VAL2REG(AB8500_SYS_CLK_CTRL, USB_CLK_VALID, __x) +#define AB8500_SYS_CLK_CTRL_TVOUT_CLK_VALID_SHIFT 1 +#define AB8500_SYS_CLK_CTRL_TVOUT_CLK_VALID_MASK 0x00000002 +#define AB8500_SYS_CLK_CTRL_TVOUT_CLK_VALID(__x) \ + AB8500_VAL2REG(AB8500_SYS_CLK_CTRL, TVOUT_CLK_VALID, __x) +#define AB8500_SYS_CLK_CTRL_TVOUT_PLL_ENA_SHIFT 0 +#define AB8500_SYS_CLK_CTRL_TVOUT_PLL_ENA_MASK 0x00000001 +#define AB8500_SYS_CLK_CTRL_TVOUT_PLL_ENA(__x) \ + AB8500_VAL2REG(AB8500_SYS_CLK_CTRL, TVOUT_PLL_ENA, __x) +#define AB8500_REGU_MISC1 0x00000380 +#define AB8500_REGU_MISC1_V_TVOUT_LP_SHIFT 7 +#define AB8500_REGU_MISC1_V_TVOUT_LP_MASK 0x00000080 +#define AB8500_REGU_MISC1_V_TVOUT_LP(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_TVOUT_LP, __x) +#define AB8500_REGU_MISC1_V_INT_CORE_12_LP_SHIFT 6 +#define AB8500_REGU_MISC1_V_INT_CORE_12_LP_MASK 0x00000040 +#define AB8500_REGU_MISC1_V_INT_CORE_12_LP(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_INT_CORE_12_LP, __x) +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_SHIFT 3 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_MASK 0x00000038 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_2V 0 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_225V 1 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_25V 2 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_275V 3 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_3V 4 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_325V 5 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL_1_35V 6 +#define AB8500_REGU_MISC1_V_INT_CORE_12_SEL(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_INT_CORE_12_SEL, \ + AB8500_REGU_MISC1_V_INT_CORE_12_SEL_##__x) +#define AB8500_REGU_MISC1_V_INT_CORE_12_ENA_SHIFT 2 +#define AB8500_REGU_MISC1_V_INT_CORE_12_ENA_MASK 0x00000004 +#define AB8500_REGU_MISC1_V_INT_CORE_12_ENA(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_INT_CORE_12_ENA, __x) +#define AB8500_REGU_MISC1_V_TVOUT_ENA_SHIFT 1 +#define AB8500_REGU_MISC1_V_TVOUT_ENA_MASK 0x00000002 +#define AB8500_REGU_MISC1_V_TVOUT_ENA(__x) \ + AB8500_VAL2REG(AB8500_REGU_MISC1, V_TVOUT_ENA, __x) +#define AB8500_VAUX12_REGU 0x00000409 +#define AB8500_VAUX12_REGU_VAUX_1_SHIFT 2 +#define AB8500_VAUX12_REGU_VAUX_1_MASK 0x0000000C +#define AB8500_VAUX12_REGU_VAUX_1_DISABLE 0 +#define AB8500_VAUX12_REGU_VAUX_1_FORCE_HP 1 +#define AB8500_VAUX12_REGU_VAUX_1_BY_CTRL_REG 2 +#define AB8500_VAUX12_REGU_VAUX_1_FORCE_LP 3 +#define AB8500_VAUX12_REGU_VAUX_1(__x) \ + AB8500_VAL2REG(AB8500_VAUX12_REGU, VAUX_1, \ + AB8500_VAUX12_REGU_VAUX_1_##__x) +#define AB8500_VAUX12_REGU_VAUX_2_SHIFT 0 +#define AB8500_VAUX12_REGU_VAUX_2_MASK 0x00000003 +#define AB8500_VAUX12_REGU_VAUX_2_DISABLE 0 +#define AB8500_VAUX12_REGU_VAUX_2_FORCE_HP 1 +#define AB8500_VAUX12_REGU_VAUX_2_BY_CTRL_REG 2 +#define AB8500_VAUX12_REGU_VAUX_2_FORCE_LP 3 +#define AB8500_VAUX12_REGU_VAUX_2(__x) \ + AB8500_VAL2REG(AB8500_VAUX12_REGU, VAUX_2, \ + AB8500_VAUX12_REGU_VAUX_2_##__x) +#define AB8500_VAUX1_SEL 0x0000041F +#define AB8500_VAUX1_SEL_VAL_SHIFT 0 +#define AB8500_VAUX1_SEL_VAL_MASK 0x0000000F +#define AB8500_VAUX1_SEL_VAL_1_1V 0 +#define AB8500_VAUX1_SEL_VAL_1_2V 1 +#define AB8500_VAUX1_SEL_VAL_1_3V 2 +#define AB8500_VAUX1_SEL_VAL_1_4V 3 +#define AB8500_VAUX1_SEL_VAL_1_5V 4 +#define AB8500_VAUX1_SEL_VAL_1_8V 5 +#define AB8500_VAUX1_SEL_VAL_1_85V 6 +#define AB8500_VAUX1_SEL_VAL_1_9V 7 +#define AB8500_VAUX1_SEL_VAL_2_5V 8 +#define AB8500_VAUX1_SEL_VAL_2_65V 9 +#define AB8500_VAUX1_SEL_VAL_2_7V 10 +#define AB8500_VAUX1_SEL_VAL_2_75V 11 +#define AB8500_VAUX1_SEL_VAL_2_8V 12 +#define AB8500_VAUX1_SEL_VAL_2_9V 13 +#define AB8500_VAUX1_SEL_VAL_3_0V 14 +#define AB8500_VAUX1_SEL_VAL_3_3V 15 +#define AB8500_VAUX1_SEL_VAL(__x) \ + AB8500_VAL2REG(AB8500_VAUX1_SEL, VAL, AB8500_VAUX1_SEL_VAL_##__x) +#define AB8500_DENC_CONF0 0x00000600 +#define AB8500_DENC_CONF0_STD_SHIFT 6 +#define AB8500_DENC_CONF0_STD_MASK 0x000000C0 +#define AB8500_DENC_CONF0_STD_PAL_BDGHI 0 +#define AB8500_DENC_CONF0_STD_PAL_N 1 +#define AB8500_DENC_CONF0_STD_NTSC_M 2 +#define AB8500_DENC_CONF0_STD_PAL_M 3 +#define AB8500_DENC_CONF0_STD(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF0, STD, AB8500_DENC_CONF0_STD_##__x) +#define AB8500_DENC_CONF0_SYNC_SHIFT 3 +#define AB8500_DENC_CONF0_SYNC_MASK 0x00000038 +#define AB8500_DENC_CONF0_SYNC_F_BASED_SLAVE 1 +#define AB8500_DENC_CONF0_SYNC_AUTO_TEST 7 +#define AB8500_DENC_CONF0_SYNC(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF0, SYNC, AB8500_DENC_CONF0_SYNC_##__x) +#define AB8500_DENC_CONF1 0x00000601 +#define AB8500_DENC_CONF1_BLK_LI_SHIFT 7 +#define AB8500_DENC_CONF1_BLK_LI_MASK 0x00000080 +#define AB8500_DENC_CONF1_BLK_LI_PARTIAL 0 +#define AB8500_DENC_CONF1_BLK_LI_FULL 1 +#define AB8500_DENC_CONF1_BLK_LI(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, BLK_LI, \ + AB8500_DENC_CONF1_BLK_LI_##__x) +#define AB8500_DENC_CONF1_FLT_SHIFT 5 +#define AB8500_DENC_CONF1_FLT_MASK 0x00000060 +#define AB8500_DENC_CONF1_FLT_1_1MHZ 0 +#define AB8500_DENC_CONF1_FLT_1_3MHZ 1 +#define AB8500_DENC_CONF1_FLT_1_6MHZ 2 +#define AB8500_DENC_CONF1_FLT_1_9MHZ 3 +#define AB8500_DENC_CONF1_FLT(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, FLT, AB8500_DENC_CONF1_FLT_##__x) +#define AB8500_DENC_CONF1_CO_KI_SHIFT 3 +#define AB8500_DENC_CONF1_CO_KI_MASK 0x00000008 +#define AB8500_DENC_CONF1_CO_KI(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, CO_KI, __x) +#define AB8500_DENC_CONF1_SETUP_MAIN_SHIFT 2 +#define AB8500_DENC_CONF1_SETUP_MAIN_MASK 0x00000004 +#define AB8500_DENC_CONF1_SETUP_MAIN_BLACK_EQ_BLANK 0 +#define AB8500_DENC_CONF1_SETUP_MAIN_BLACK_GT_BLANK 1 +#define AB8500_DENC_CONF1_SETUP_MAIN(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, SETUP_MAIN, \ + AB8500_DENC_CONF1_SETUP_MAIN_##__x) +#define AB8500_DENC_CONF1_CC_SHIFT 0 +#define AB8500_DENC_CONF1_CC_MASK 0x00000003 +#define AB8500_DENC_CONF1_CC_NONE 0 +#define AB8500_DENC_CONF1_CC_FIELD_1 1 +#define AB8500_DENC_CONF1_CC_FIELD_2 2 +#define AB8500_DENC_CONF1_CC_ALL 3 +#define AB8500_DENC_CONF1_CC(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF1, CC, AB8500_DENC_CONF1_CC_##__x) +#define AB8500_DENC_CONF2 0x00000602 +#define AB8500_DENC_CONF2_N_INTRL_SHIFT 7 +#define AB8500_DENC_CONF2_N_INTRL_MASK 0x00000080 +#define AB8500_DENC_CONF2_N_INTRL(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, N_INTRL, __x) +#define AB8500_DENC_CONF2_EN_RST_SHIFT 6 +#define AB8500_DENC_CONF2_EN_RST_MASK 0x00000040 +#define AB8500_DENC_CONF2_EN_RST(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, EN_RST, __x) +#define AB8500_DENC_CONF2_BURST_EN_SHIFT 5 +#define AB8500_DENC_CONF2_BURST_EN_MASK 0x00000020 +#define AB8500_DENC_CONF2_BURST_EN(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, BURST_EN, __x) +#define AB8500_DENC_CONF2_SEL_RST_SHIFT 4 +#define AB8500_DENC_CONF2_SEL_RST_MASK 0x00000010 +#define AB8500_DENC_CONF2_SEL_RST_USE_HW_VAL 0 +#define AB8500_DENC_CONF2_SEL_RST_USE_PROG_VAL 1 +#define AB8500_DENC_CONF2_SEL_RST(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, SEL_RST, \ + AB8500_DENC_CONF2_SEL_RST_##__x) +#define AB8500_DENC_CONF2_RST_OSC_BUF_SHIFT 2 +#define AB8500_DENC_CONF2_RST_OSC_BUF_MASK 0x00000004 +#define AB8500_DENC_CONF2_RST_OSC_BUF(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, RST_OSC_BUF, __x) +#define AB8500_DENC_CONF2_VAL_RST_SHIFT 0 +#define AB8500_DENC_CONF2_VAL_RST_MASK 0x00000003 +#define AB8500_DENC_CONF2_VAL_RST_ALL_LINES 0 +#define AB8500_DENC_CONF2_VAL_RST_EVERY_2ND_FIELD 1 +#define AB8500_DENC_CONF2_VAL_RST_EVERY_4TH_FIELD 2 +#define AB8500_DENC_CONF2_VAL_RST_EVERY_8TH_FIELD 3 +#define AB8500_DENC_CONF2_VAL_RST(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF2, VAL_RST, \ + AB8500_DENC_CONF2_VAL_RST_##__x) +#define AB8500_DENC_CONF6 0x00000606 +#define AB8500_DENC_CONF6_SOFT_RESET_SHIFT 7 +#define AB8500_DENC_CONF6_SOFT_RESET_MASK 0x00000080 +#define AB8500_DENC_CONF6_SOFT_RESET(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, SOFT_RESET, __x) +#define AB8500_DENC_CONF6_JUMP_SHIFT 6 +#define AB8500_DENC_CONF6_JUMP_MASK 0x00000040 +#define AB8500_DENC_CONF6_JUMP(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, JUMP, __x) +#define AB8500_DENC_CONF6_DEC_NINC_SHIFT 5 +#define AB8500_DENC_CONF6_DEC_NINC_MASK 0x00000020 +#define AB8500_DENC_CONF6_DEC_NINC(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, DEC_NINC, __x) +#define AB8500_DENC_CONF6_FREE_JUMP_SHIFT 4 +#define AB8500_DENC_CONF6_FREE_JUMP_MASK 0x00000010 +#define AB8500_DENC_CONF6_FREE_JUMP(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, FREE_JUMP, __x) +#define AB8500_DENC_CONF6_MAX_DYN_SHIFT 0 +#define AB8500_DENC_CONF6_MAX_DYN_MASK 0x00000001 +#define AB8500_DENC_CONF6_MAX_DYN(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF6, MAX_DYN, __x) +#define AB8500_DENC_CONF8 0x00000608 +#define AB8500_DENC_CONF8_PH_RST_MODE_SHIFT 6 +#define AB8500_DENC_CONF8_PH_RST_MODE_MASK 0x000000C0 +#define AB8500_DENC_CONF8_PH_RST_MODE_DISABLED 0 +#define AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_PHASE_BUF 1 +#define AB8500_DENC_CONF8_PH_RST_MODE_UPDATE_FROM_INC_DFS 2 +#define AB8500_DENC_CONF8_PH_RST_MODE_RESET 3 +#define AB8500_DENC_CONF8_PH_RST_MODE(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF8, PH_RST_MODE, \ + AB8500_DENC_CONF8_PH_RST_MODE_##__x) +#define AB8500_DENC_CONF8_VAL_422_MUX_SHIFT 4 +#define AB8500_DENC_CONF8_VAL_422_MUX_MASK 0x00000010 +#define AB8500_DENC_CONF8_VAL_422_MUX_TEST 0 +#define AB8500_DENC_CONF8_VAL_422_MUX_ACTIVE 1 +#define AB8500_DENC_CONF8_VAL_422_MUX(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF8, VAL_422_MUX, \ + AB8500_DENC_CONF8_VAL_422_MUX_##__x) +#define AB8500_DENC_CONF8_BLK_ALL_SHIFT 3 +#define AB8500_DENC_CONF8_BLK_ALL_MASK 0x00000008 +#define AB8500_DENC_CONF8_BLK_ALL(__x) \ + AB8500_VAL2REG(AB8500_DENC_CONF8, BLK_ALL, __x) +#define AB8500_TVOUT_CTRL 0x00000680 +#define AB8500_TVOUT_CTRL_TV_LOAD_RC_SHIFT 6 +#define AB8500_TVOUT_CTRL_TV_LOAD_RC_MASK 0x00000040 +#define AB8500_TVOUT_CTRL_TV_LOAD_RC(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, TV_LOAD_RC, __x) +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_SHIFT 3 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_MASK 0x00000038 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_0_5S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_1S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_1_5S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_2S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_2_5S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME_3S 0 +#define AB8500_TVOUT_CTRL_PLUG_TV_TIME(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, PLUG_TV_TIME, \ + AB8500_TVOUT_CTRL_PLUG_TV_TIME_##__x) +#define AB8500_TVOUT_CTRL_TV_PLUG_ON_SHIFT 2 +#define AB8500_TVOUT_CTRL_TV_PLUG_ON_MASK 0x00000004 +#define AB8500_TVOUT_CTRL_TV_PLUG_ON(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, TV_PLUG_ON, __x) +#define AB8500_TVOUT_CTRL_DAC_CTRL0_SHIFT 1 +#define AB8500_TVOUT_CTRL_DAC_CTRL0_MASK 0x00000002 +#define AB8500_TVOUT_CTRL_DAC_CTRL0(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, DAC_CTRL0, __x) +#define AB8500_TVOUT_CTRL_DAC_CTRL1_SHIFT 0 +#define AB8500_TVOUT_CTRL_DAC_CTRL1_MASK 0x00000001 +#define AB8500_TVOUT_CTRL_DAC_CTRL1(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL, DAC_CTRL1, __x) +#define AB8500_TVOUT_CTRL2 0x00000681 +#define AB8500_TVOUT_CTRL2_SWAP_DDR_DATA_IN_SHIFT 1 +#define AB8500_TVOUT_CTRL2_SWAP_DDR_DATA_IN_MASK 0x00000002 +#define AB8500_TVOUT_CTRL2_SWAP_DDR_DATA_IN(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, SWAP_DDR_DATA_IN, __x) +#define AB8500_TVOUT_CTRL2_DENC_DDR_SHIFT 0 +#define AB8500_TVOUT_CTRL2_DENC_DDR_MASK 0x00000001 +#define AB8500_TVOUT_CTRL2_DENC_DDR(__x) \ + AB8500_VAL2REG(AB8500_TVOUT_CTRL2, DENC_DDR, __x) +#define AB8500_IT_MASK1 0x00000E40 +#define AB8500_IT_MASK1_PON_KEY1_DBR_SHIFT 7 +#define AB8500_IT_MASK1_PON_KEY1_DBR_MASK 0x00000080 +#define AB8500_IT_MASK1_PON_KEY1_DBR(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY1_DBR, __x) +#define AB8500_IT_MASK1_PON_KEY1_DBF_SHIFT 6 +#define AB8500_IT_MASK1_PON_KEY1_DBF_MASK 0x00000040 +#define AB8500_IT_MASK1_PON_KEY1_DBF(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY1_DBF, __x) +#define AB8500_IT_MASK1_PON_KEY2_DBR_SHIFT 5 +#define AB8500_IT_MASK1_PON_KEY2_DBR_MASK 0x00000020 +#define AB8500_IT_MASK1_PON_KEY2_DBR(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY2_DBR, __x) +#define AB8500_IT_MASK1_PON_KEY2_DBF_SHIFT 4 +#define AB8500_IT_MASK1_PON_KEY2_DBF_MASK 0x00000010 +#define AB8500_IT_MASK1_PON_KEY2_DBF(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PON_KEY2_DBF, __x) +#define AB8500_IT_MASK1_TEMP_WARN_SHIFT 3 +#define AB8500_IT_MASK1_TEMP_WARN_MASK 0x00000008 +#define AB8500_IT_MASK1_TEMP_WARN(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, TEMP_WARN, __x) +#define AB8500_IT_MASK1_PLUG_TV_DET_SHIFT 2 +#define AB8500_IT_MASK1_PLUG_TV_DET_MASK 0x00000004 +#define AB8500_IT_MASK1_PLUG_TV_DET(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, PLUG_TV_DET, __x) +#define AB8500_IT_MASK1_UNPLUG_TV_DET_SHIFT 1 +#define AB8500_IT_MASK1_UNPLUG_TV_DET_MASK 0x00000002 +#define AB8500_IT_MASK1_UNPLUG_TV_DET(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, UNPLUG_TV_DET, __x) +#define AB8500_IT_MASK1_MAIN_EXT_CH_NOK_SHIFT 0 +#define AB8500_IT_MASK1_MAIN_EXT_CH_NOK_MASK 0x00000001 +#define AB8500_IT_MASK1_MAIN_EXT_CH_NOK(__x) \ + AB8500_VAL2REG(AB8500_IT_MASK1, MAIN_EXT_CH_NOK, __x) +#define AB8500_REV 0x00001080 +#define AB8500_REV_FULL_MASK_SHIFT 4 +#define AB8500_REV_FULL_MASK_MASK 0x000000F0 +#define AB8500_REV_FULL_MASK(__x) \ + AB8500_VAL2REG(AB8500_REV, FULL_MASK, __x) +#define AB8500_REV_METAL_FIX_SHIFT 0 +#define AB8500_REV_METAL_FIX_MASK 0x0000000F +#define AB8500_REV_METAL_FIX(__x) \ + AB8500_VAL2REG(AB8500_REV, METAL_FIX, __x) + +#endif /* __AB8500_DENC_H */ diff --git a/include/linux/mfd/ab8500/denc.h b/include/linux/mfd/ab8500/denc.h new file mode 100644 index 00000000000..25a09a2c2bd --- /dev/null +++ b/include/linux/mfd/ab8500/denc.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * AB8500 tvout driver interface + * + * Author: Marcel Tunnissen + * for ST-Ericsson. + * + * License terms: GNU General Public License (GPL), version 2. + */ +#ifndef __AB8500_DENC__H__ +#define __AB8500_DENC__H__ + +#include + +struct ab8500_denc_platform_data { + /* Platform info */ + bool ddr_enable; + bool ddr_little_endian; +}; + +enum ab8500_denc_TV_std { + TV_STD_PAL_BDGHI, + TV_STD_PAL_N, + TV_STD_PAL_M, + TV_STD_NTSC_M, +}; + +enum ab8500_denc_cr_filter_bandwidth { + TV_CR_NTSC_LOW_DEF_FILTER, + TV_CR_PAL_LOW_DEF_FILTER, + TV_CR_NTSC_HIGH_DEF_FILTER, + TV_CR_PAL_HIGH_DEF_FILTER, +}; + +enum ab8500_denc_phase_reset_mode { + TV_PHASE_RST_MOD_DISABLE, + TV_PHASE_RST_MOD_FROM_PHASE_BUF, + TV_PHASE_RST_MOD_FROM_INC_DFS, + TV_PHASE_RST_MOD_RST, +}; + +enum ab8500_denc_plug_time { + TV_PLUG_TIME_0_5S, + TV_PLUG_TIME_1S, + TV_PLUG_TIME_1_5S, + TV_PLUG_TIME_2S, + TV_PLUG_TIME_2_5S, + TV_PLUG_TIME_3S, +}; + +struct ab8500_denc_conf { + /* register settings for DENC_configuration */ + bool act_output; + enum ab8500_denc_TV_std TV_std; + bool progressive; + bool test_pattern; + bool partial_blanking; + bool blank_all; + bool black_level_setup; + enum ab8500_denc_cr_filter_bandwidth cr_filter; + bool suppress_col; + enum ab8500_denc_phase_reset_mode phase_reset_mode; + bool dac_enable; + bool act_dc_output; +}; + +struct platform_device *ab8500_denc_get_device(void); +void ab8500_denc_put_device(struct platform_device *pdev); + +void ab8500_denc_reset(struct platform_device *pdev, bool hard); +void ab8500_denc_power_up(struct platform_device *pdev); +void ab8500_denc_power_down(struct platform_device *pdev); + +void ab8500_denc_conf(struct platform_device *pdev, + struct ab8500_denc_conf *conf); +void ab8500_denc_conf_plug_detect(struct platform_device *pdev, + bool enable, bool load_RC, + enum ab8500_denc_plug_time time); +void ab8500_denc_mask_int_plug_det(struct platform_device *pdev, bool plug, + bool unplug); +#endif /* __AB8500_DENC__H__ */ diff --git a/include/linux/mfd/ab8500/gpadc.h b/include/linux/mfd/ab8500/gpadc.h index 46b954011f1..57c6b59fcaa 100644 --- a/include/linux/mfd/ab8500/gpadc.h +++ b/include/linux/mfd/ab8500/gpadc.h @@ -26,7 +26,7 @@ struct ab8500_gpadc; -struct ab8500_gpadc *ab8500_gpadc_get(char *name); +struct ab8500_gpadc *ab8500_gpadc_get(void); int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input); #endif /* _AB8500_GPADC_H */ diff --git a/include/linux/mfd/ab8500/ux500_chargalg.h b/include/linux/mfd/ab8500/ux500_chargalg.h new file mode 100644 index 00000000000..f04e47ff56a --- /dev/null +++ b/include/linux/mfd/ab8500/ux500_chargalg.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Johan Gardsmark for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _UX500_CHARGALG_H +#define _UX500_CHARGALG_H + +#include + +#define psy_to_ux500_charger(x) container_of((x), \ + struct ux500_charger, psy) + +/* Forward declaration */ +struct ux500_charger; + +struct ux500_charger_ops { + int (*enable) (struct ux500_charger *, int, int, int); + int (*kick_wd) (struct ux500_charger *); + int (*update_curr) (struct ux500_charger *, int); +}; + +/** + * struct ux500_charger - power supply ux500 charger sub class + * @psy power supply base class + * @ops ux500 charger operations + * @max_out_volt maximum output charger voltage in mV + * @max_out_curr maximum output charger current in mA + */ +struct ux500_charger { + struct power_supply psy; + struct ux500_charger_ops ops; + int max_out_volt; + int max_out_curr; +}; + +#endif diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 896b5e47f16..78fde125403 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -31,8 +31,15 @@ #define AB3100_R2B 0xc8 #define AB3550_P1A 0x10 #define AB5500_1_0 0x20 -#define AB5500_2_0 0x21 -#define AB5500_2_1 0x22 +#define AB5500_1_1 0x21 +#define AB5500_2_0 0x24 + +/* AB8500 CIDs*/ +#define AB8500_CUTEARLY 0x00 +#define AB8500_CUT1P0 0x10 +#define AB8500_CUT1P1 0x11 +#define AB8500_CUT2P0 0x20 +#define AB8500_CUT3P0 0x30 /* AB8500 CIDs*/ #define AB8500_CUTEARLY 0x00 @@ -198,6 +205,51 @@ struct ab3550_platform_data { unsigned int init_settings_sz; }; +enum ab5500_devid { + AB5500_DEVID_ADC, + AB5500_DEVID_LEDS, + AB5500_DEVID_POWER, + AB5500_DEVID_REGULATORS, + AB5500_DEVID_SIM, + AB5500_DEVID_RTC, + AB5500_DEVID_CHARGER, + AB5500_DEVID_FUELGAUGE, + AB5500_DEVID_VIBRATOR, + AB5500_DEVID_CODEC, + AB5500_DEVID_USB, + AB5500_DEVID_OTP, + AB5500_DEVID_VIDEO, + AB5500_DEVID_DBIECI, + AB5500_NUM_DEVICES, +}; + +enum ab5500_banks { + AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP = 0, + AB5500_BANK_VDDDIG_IO_I2C_CLK_TST = 1, + AB5500_BANK_VDENC = 2, + AB5500_BANK_SIM_USBSIM = 3, + AB5500_BANK_LED = 4, + AB5500_BANK_ADC = 5, + AB5500_BANK_RTC = 6, + AB5500_BANK_STARTUP = 7, + AB5500_BANK_DBI_ECI = 8, + AB5500_BANK_CHG = 9, + AB5500_BANK_FG_BATTCOM_ACC = 10, + AB5500_BANK_USB = 11, + AB5500_BANK_IT = 12, + AB5500_BANK_VIBRA = 13, + AB5500_BANK_AUDIO_HEADSETUSB = 14, + AB5500_NUM_BANKS = 15, +}; + +struct ab5500_platform_data { + struct {unsigned int base; unsigned int count; } irq; + void *dev_data[AB5500_NUM_DEVICES]; + size_t dev_data_sz[AB5500_NUM_DEVICES]; + struct abx500_init_settings *init_settings; + unsigned int init_settings_sz; +}; + int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, u8 value); int abx500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, @@ -235,6 +287,6 @@ struct abx500_ops { int (*startup_irq_enabled) (struct device *, unsigned int); }; -int abx500_register_ops(struct device *core_dev, struct abx500_ops *ops); +int abx500_register_ops(struct device *dev, struct abx500_ops *ops); void abx500_remove_ops(struct device *dev); #endif -- cgit v1.2.3 From c4efcb2bb27f6e179d11f63ad0129b0fcdab0bbe Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 11 Oct 2011 10:00:39 +0200 Subject: hwmon db8500: update for new prcmu interface --- drivers/hwmon/db8500.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/db8500.c b/drivers/hwmon/db8500.c index 09630f2f707..e28f9938eb4 100755 --- a/drivers/hwmon/db8500.c +++ b/drivers/hwmon/db8500.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include -- cgit v1.2.3 From d0de919aa215b1f8249713292f1ef43d195c4fcd Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Fri, 25 Mar 2011 12:54:23 +0100 Subject: mfd ab5500: Fix for 2.6.38 --- drivers/mfd/ab5500-core.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index a4fa61d3d9b..37bd215538f 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -1907,28 +1907,30 @@ static void ab5500_mask_work(struct work_struct *work) } } -static void ab5500_mask(unsigned int irq) +static void ab5500_mask(struct irq_data *data) { unsigned long flags; struct ab5500 *ab; + int irq; + + ab = irq_data_get_irq_chip_data(data); + irq = data->irq - ab->irq.base; - ab = get_irq_chip_data(irq); - irq -= ab->irq_base; - - spin_lock_irqsave(&ab->event_lock, flags); + spin_lock_irqsave(&ab->event_lock, flags); ab->event_mask[irq / 8] |= BIT(irq % 8); spin_unlock_irqrestore(&ab->event_lock, flags); schedule_work(&ab->mask_work); } -static void ab5500_unmask(unsigned int irq) +static void ab5500_unmask(struct irq_data *data) { unsigned long flags; struct ab5500 *ab; - - ab = get_irq_chip_data(irq); - irq -= ab->irq_base; + int irq; + + ab = irq_data_get_irq_chip_data(data); + irq = data->irq - ab->irq.base; spin_lock_irqsave(&ab->event_lock, flags); ab->event_mask[irq / 8] &= ~BIT(irq % 8); -- cgit v1.2.3 From 74e57ca8c02f00f23e963a8cd2719ca3b445fb54 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 28 Mar 2011 13:18:24 +0200 Subject: ux500-ARM : make backup RAM as an executable area On the v1.0/ED boards, during the deep sleep resume, it is required to make the backup RAM area as "executable" in order manage MMU settings for a single core mode. As a result, the rom code requires such a condition for aligning SMP strategy for different per-core MMU configuration. This patch makes the backup RAM configuration area as executable by adding a new memory type. *This change will not be needed for U8500 v2.0 as the rom code would (hopefully as discussions are showing) be updated with a minor API change* This patch is a part of patches for the deep sleep feature for the U8500 and FIDO_IR_ER: 258539 This patch *must* be re-visisted during u8500 v2.0 cut or mainlining the deep sleep to community Signed-off-by: Sundar R Iyer Acked-By: Biju C Das Signed-off-by: Mian Yousaf Kaukab Change-Id: Id4d8215ca1e9aaf8f327ff3f55f3f9ca1e68aef5 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/2190 Reviewed-by: Jonas ABERG --- arch/arm/include/asm/io.h | 6 ++++++ arch/arm/mm/mmu.c | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/arch/arm/include/asm/io.h b/arch/arm/include/asm/io.h index d66605dea55..ffa4430e106 100644 --- a/arch/arm/include/asm/io.h +++ b/arch/arm/include/asm/io.h @@ -62,6 +62,12 @@ extern void __raw_readsl(const void __iomem *addr, void *data, int longlen); #define MT_DEVICE_NONSHARED 1 #define MT_DEVICE_CACHED 2 #define MT_DEVICE_WC 3 +/* + * NOTE : U8500 v1.0/ED cut specific hack. + * look at the commit message for more details + */ +#define MT_BACKUP_RAM 4 + /* * types 4 onwards can be found in asm/mach/map.h and are undefined * for ioremap diff --git a/arch/arm/mm/mmu.c b/arch/arm/mm/mmu.c index 594d677b92c..df6ced286ea 100644 --- a/arch/arm/mm/mmu.c +++ b/arch/arm/mm/mmu.c @@ -273,6 +273,20 @@ static struct mem_type mem_types[] = { .prot_l1 = PMD_TYPE_TABLE, .domain = DOMAIN_KERNEL, }, + /* NOTE : this is only a temporary hack!!! + * The U8500 ED/V1.0 cuts require such a + * memory type for deep sleep resume. + * This is expected to be solved in cut v2.0 + * and we clean this up then. for more details + * look @ the commit message please + */ + [MT_BACKUP_RAM] = { + .prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED | + L_PTE_SHARED, + .prot_l1 = PMD_TYPE_TABLE, + .prot_sect = PROT_SECT_DEVICE | PMD_SECT_S, + .domain = DOMAIN_IO, + }, }; const struct mem_type *get_mem_type(unsigned int type) -- cgit v1.2.3 From 9350262e2e177ea1bbb36e567cb823a53437cbec Mon Sep 17 00:00:00 2001 From: Sundar R Iyer Date: Tue, 9 Feb 2010 15:52:26 +0530 Subject: ARM : handle preempt count in cpu_idle Signed-off-by: Sundar R Iyer Acked-by: Linus Walleij Signed-off-by: Mian Yousaf Kaukab --- arch/arm/kernel/process.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/arm/kernel/process.c b/arch/arm/kernel/process.c index 1a347f481e5..f48b1c2e958 100644 --- a/arch/arm/kernel/process.c +++ b/arch/arm/kernel/process.c @@ -187,8 +187,17 @@ void cpu_idle(void) leds_event(led_idle_start); while (!need_resched()) { #ifdef CONFIG_HOTPLUG_CPU - if (cpu_is_offline(smp_processor_id())) + if (cpu_is_offline(smp_processor_id())) { + + /* NOTE : preempt_count() should be 0 for dying CPU + * as the CPU will use this very thread when + * it is alive + */ + if (preempt_count()) + preempt_enable_no_resched(); + cpu_die(); + } #endif local_irq_disable(); -- cgit v1.2.3 From 336b99dfa42ccdc84ff7094f90e1f0a4ffdf9db4 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 28 Mar 2011 10:49:09 +0200 Subject: u8500: enable u8500 configurations Signed-off-by: Mian Yousaf Kaukab --- arch/arm/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 3146ed3f6ec..cb4afed0d87 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -845,7 +845,9 @@ config ARCH_U8500 select GENERIC_CLOCKEVENTS select CLKDEV_LOOKUP select ARCH_REQUIRE_GPIOLIB + select HAVE_CLK select ARCH_HAS_CPUFREQ + select NOMADIK_GPIO help Support for ST-Ericsson's Ux500 architecture -- cgit v1.2.3 From 23ef43115d9f50bcb066e27c77d6be038766ee2d Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 28 Mar 2011 11:08:15 +0200 Subject: Add clean and flush_dcache_all to ARM cache API This patch adds functions to flush and clean the entire data cache. Since the existing flush/clean range functions take more time to flush/clean the entire data cache, they cannot be used. Whenever the range of data to be flushed/cleaned from the data cache is more than some threshold value, it is better to do the entire data flush/clean, this will reduce the time taken and effectively increases the performance of the system. ST-Ericsson ID: IR275682 ER275397 Change-Id: I8d7e6004232301cc2c9922738fa728cdc833cfde Signed-off-by: Johan Mossberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/7161 Reviewed-by: Robert FEKETE Reviewed-by: Srinidhi KASAGAR Conflicts: arch/arm/mm/cache-v7.S --- arch/arm/include/asm/cacheflush.h | 15 +++++++ arch/arm/mm/cache-fa.S | 18 ++++++++ arch/arm/mm/cache-v3.S | 18 ++++++++ arch/arm/mm/cache-v4.S | 18 ++++++++ arch/arm/mm/cache-v4wb.S | 18 ++++++++ arch/arm/mm/cache-v4wt.S | 18 ++++++++ arch/arm/mm/cache-v6.S | 18 ++++++++ arch/arm/mm/cache-v7.S | 94 ++++++++++++++++++++++++++++++++++++--- arch/arm/mm/proc-macros.S | 2 + arch/arm/mm/proc-v7.S | 2 +- 10 files changed, 214 insertions(+), 7 deletions(-) diff --git a/arch/arm/include/asm/cacheflush.h b/arch/arm/include/asm/cacheflush.h index d5d8d5c7268..a4bf3199819 100644 --- a/arch/arm/include/asm/cacheflush.h +++ b/arch/arm/include/asm/cacheflush.h @@ -84,6 +84,14 @@ * - kaddr - page address * - size - region size * + * clean_dcache_all() + * + * Cleans the entire d-cache. + * + * flush_dcache_all() + * + * Flushes the entire d-cache. + * * DMA Cache Coherency * =================== * @@ -104,6 +112,9 @@ struct cpu_cache_fns { void (*coherent_user_range)(unsigned long, unsigned long); void (*flush_kern_dcache_area)(void *, size_t); + void (*clean_dcache_all)(void); + void (*flush_dcache_all)(void); + void (*dma_map_area)(const void *, size_t, int); void (*dma_unmap_area)(const void *, size_t, int); @@ -124,6 +135,8 @@ extern struct cpu_cache_fns cpu_cache; #define __cpuc_coherent_kern_range cpu_cache.coherent_kern_range #define __cpuc_coherent_user_range cpu_cache.coherent_user_range #define __cpuc_flush_dcache_area cpu_cache.flush_kern_dcache_area +#define __cpuc_clean_dcache_all cpu_cache.clean_dcache_all +#define __cpuc_flush_dcache_all cpu_cache.flush_dcache_all /* * These are private to the dma-mapping API. Do not use directly. @@ -144,6 +157,8 @@ extern void __cpuc_flush_user_range(unsigned long, unsigned long, unsigned int); extern void __cpuc_coherent_kern_range(unsigned long, unsigned long); extern void __cpuc_coherent_user_range(unsigned long, unsigned long); extern void __cpuc_flush_dcache_area(void *, size_t); +extern void __cpuc_clean_dcache_all(void); +extern void __cpuc_flush_dcache_all(void); /* * These are private to the dma-mapping API. Do not use directly. diff --git a/arch/arm/mm/cache-fa.S b/arch/arm/mm/cache-fa.S index 07201637109..226f8736152 100644 --- a/arch/arm/mm/cache-fa.S +++ b/arch/arm/mm/cache-fa.S @@ -240,6 +240,24 @@ ENTRY(fa_dma_unmap_area) mov pc, lr ENDPROC(fa_dma_unmap_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(fa_clean_dcache_all) + mov pc, lr +ENDPROC(fa_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(fa_flush_dcache_all) + mov pc, lr +ENDPROC(fa_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v3.S b/arch/arm/mm/cache-v3.S index c2301f22610..ab5bf508a2a 100644 --- a/arch/arm/mm/cache-v3.S +++ b/arch/arm/mm/cache-v3.S @@ -127,6 +127,24 @@ ENTRY(v3_dma_map_area) ENDPROC(v3_dma_unmap_area) ENDPROC(v3_dma_map_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v3_clean_dcache_all) + mov pc, lr +ENDPROC(v3_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v3_flush_dcache_all) + mov pc, lr +ENDPROC(v3_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v4.S b/arch/arm/mm/cache-v4.S index fd9bb7addc8..9d3a055127e 100644 --- a/arch/arm/mm/cache-v4.S +++ b/arch/arm/mm/cache-v4.S @@ -139,6 +139,24 @@ ENTRY(v4_dma_map_area) ENDPROC(v4_dma_unmap_area) ENDPROC(v4_dma_map_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v4_clean_dcache_all) + mov pc, lr +ENDPROC(v4_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v4_flush_dcache_all) + mov pc, lr +ENDPROC(v4_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v4wb.S b/arch/arm/mm/cache-v4wb.S index 4f2c14151cc..54d3cda4a89 100644 --- a/arch/arm/mm/cache-v4wb.S +++ b/arch/arm/mm/cache-v4wb.S @@ -251,6 +251,24 @@ ENTRY(v4wb_dma_unmap_area) mov pc, lr ENDPROC(v4wb_dma_unmap_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v4wb_clean_dcache_all) + mov pc, lr +ENDPROC(v4wb_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v4wb_flush_dcache_all) + mov pc, lr +ENDPROC(v4wb_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v4wt.S b/arch/arm/mm/cache-v4wt.S index 4d7b467631c..40f7dba11f5 100644 --- a/arch/arm/mm/cache-v4wt.S +++ b/arch/arm/mm/cache-v4wt.S @@ -195,6 +195,24 @@ ENTRY(v4wt_dma_map_area) ENDPROC(v4wt_dma_unmap_area) ENDPROC(v4wt_dma_map_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v4wt_clean_dcache_all) + mov pc, lr +ENDPROC(v4wt_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v4wt_flush_dcache_all) + mov pc, lr +ENDPROC(v4wt_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v6.S b/arch/arm/mm/cache-v6.S index 74c2e5a33a4..b88dd4ab038 100644 --- a/arch/arm/mm/cache-v6.S +++ b/arch/arm/mm/cache-v6.S @@ -328,6 +328,24 @@ ENTRY(v6_dma_unmap_area) mov pc, lr ENDPROC(v6_dma_unmap_area) +/* + * clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v6_clean_dcache_all) + mov pc, lr +ENDPROC(v6_clean_dcache_all) + +/* + * flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v6_flush_dcache_all) + mov pc, lr +ENDPROC(v6_flush_dcache_all) + __INITDATA @ define struct cpu_cache_fns (see and proc-macros.S) diff --git a/arch/arm/mm/cache-v7.S b/arch/arm/mm/cache-v7.S index 07c4bc8ea0a..3c77277d8cb 100644 --- a/arch/arm/mm/cache-v7.S +++ b/arch/arm/mm/cache-v7.S @@ -33,7 +33,7 @@ ENTRY(v7_flush_icache_all) ENDPROC(v7_flush_icache_all) /* - * v7_flush_dcache_all() + * __v7_flush_dcache_all() * * Flush the whole D-cache. * @@ -41,7 +41,7 @@ ENDPROC(v7_flush_icache_all) * * - mm - mm_struct describing address space */ -ENTRY(v7_flush_dcache_all) +ENTRY(__v7_flush_dcache_all) dmb @ ensure ordering with previous memory accesses mrc p15, 1, r0, c0, c0, 1 @ read clidr ands r3, r0, #0x7000000 @ extract loc from clidr @@ -88,8 +88,92 @@ finished: dsb isb mov pc, lr +ENDPROC(__v7_flush_dcache_all) + +/* + * __v7_clean_dcache_all() + * + * Clean the whole D-cache. + * + * Corrupted registers: r0-r7, r9-r11 (r6 only in Thumb mode) + */ +ENTRY(__v7_clean_dcache_all) + dmb @ ensure ordering with previous memory accesses + mrc p15, 1, r0, c0, c0, 1 @ read clidr + ands r3, r0, #0x7000000 @ extract loc from clidr + mov r3, r3, lsr #23 @ left align loc bit field + beq finished1 @ if loc is 0, then no need to clean + mov r10, #0 @ start clean at cache level 0 +loop21: + add r2, r10, r10, lsr #1 @ work out 3x current cache level + mov r1, r0, lsr r2 @ extract cache type bits from clidr + and r1, r1, #7 @ mask of the bits for current cache only + cmp r1, #2 @ see what cache we have at this level + blt skip1 @ skip if no cache, or just i-cache + mcr p15, 2, r10, c0, c0, 0 @ select current cache level in cssr + isb @ isb to sych the new cssr&csidr + mrc p15, 1, r1, c0, c0, 0 @ read the new csidr + and r2, r1, #7 @ extract the length of the cache lines + add r2, r2, #4 @ add 4 (line length offset) + ldr r4, =0x3ff + ands r4, r4, r1, lsr #3 @ find maximum number on the way size + clz r5, r4 @ find bit position of way size increment + ldr r7, =0x7fff + ands r7, r7, r1, lsr #13 @ extract max number of the index size +loop22: + mov r9, r4 @ create working copy of max way size +loop23: + ARM( orr r11, r10, r9, lsl r5 ) @ factor way and cache number into r11 + THUMB( lsl r6, r9, r5 ) + THUMB( orr r11, r10, r6 ) @ factor way and cache number into r11 + ARM( orr r11, r11, r7, lsl r2 ) @ factor index number into r11 + THUMB( lsl r6, r7, r2 ) + THUMB( orr r11, r11, r6 ) @ factor index number into r11 + mcr p15, 0, r11, c7, c10, 2 @ clean by set/way + subs r9, r9, #1 @ decrement the way + bge loop23 + subs r7, r7, #1 @ decrement the index + bge loop22 +skip1: + add r10, r10, #2 @ increment cache number + cmp r3, r10 + bgt loop21 +finished1: + mov r10, #0 @ swith back to cache level 0 + mcr p15, 2, r10, c0, c0, 0 @ select current cache level in cssr + dsb + isb + mov pc, lr +ENDPROC(__v7_clean_dcache_all) + +/* + * v7_flush_dcache_all() + * + * Flush the whole D-cache. + */ +ENTRY(v7_flush_dcache_all) + ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) + bl __v7_flush_dcache_all + ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) + mov pc, lr ENDPROC(v7_flush_dcache_all) +/* + * v7_clean_dcache_all() + * + * Clean the whole D-cache. + */ +ENTRY(v7_clean_dcache_all) + ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) + bl __v7_clean_dcache_all + ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) + THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) + mov pc, lr +ENDPROC(v7_clean_dcache_all) + /* * v7_flush_cache_all() * @@ -102,14 +186,12 @@ ENDPROC(v7_flush_dcache_all) * */ ENTRY(v7_flush_kern_cache_all) - ARM( stmfd sp!, {r4-r5, r7, r9-r11, lr} ) - THUMB( stmfd sp!, {r4-r7, r9-r11, lr} ) + stmfd sp!, {lr} bl v7_flush_dcache_all mov r0, #0 ALT_SMP(mcr p15, 0, r0, c7, c1, 0) @ invalidate I-cache inner shareable ALT_UP(mcr p15, 0, r0, c7, c5, 0) @ I+BTB cache invalidate - ARM( ldmfd sp!, {r4-r5, r7, r9-r11, lr} ) - THUMB( ldmfd sp!, {r4-r7, r9-r11, lr} ) + ldmfd sp!, {lr} mov pc, lr ENDPROC(v7_flush_kern_cache_all) diff --git a/arch/arm/mm/proc-macros.S b/arch/arm/mm/proc-macros.S index 307a4def8d3..411610d4973 100644 --- a/arch/arm/mm/proc-macros.S +++ b/arch/arm/mm/proc-macros.S @@ -303,6 +303,8 @@ ENTRY(\name\()_cache_fns) .long \name\()_coherent_kern_range .long \name\()_coherent_user_range .long \name\()_flush_kern_dcache_area + .long \name\()_clean_dcache_all + .long \name\()_flush_dcache_all .long \name\()_dma_map_area .long \name\()_dma_unmap_area .long \name\()_dma_flush_range diff --git a/arch/arm/mm/proc-v7.S b/arch/arm/mm/proc-v7.S index 9049c0764db..2cd0273bc65 100644 --- a/arch/arm/mm/proc-v7.S +++ b/arch/arm/mm/proc-v7.S @@ -304,7 +304,7 @@ __v7_ca15mp_setup: __v7_setup: adr r12, __v7_setup_stack @ the local stack stmia r12, {r0-r5, r7, r9, r11, lr} - bl v7_flush_dcache_all + bl __v7_flush_dcache_all ldmia r12, {r0-r5, r7, r9, r11, lr} mrc p15, 0, r0, c0, c0, 0 @ read main ID register -- cgit v1.2.3 From 0d92750424e20ea10a28393b852d3bdd35b9d14b Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 28 Mar 2011 11:24:09 +0200 Subject: ARM: Translate delay.S into (mostly) C We want to allow machines to override the __delay() implementation at runtime so they can use a timer based __delay() routine. It's easier to do this using C, so let's write udelay and friends in C. We lose the #if 0 code, which according to Russell is used "to make the delay loop more stable and predictable on older CPUs" (see http://article.gmane.org/gmane.linux.kernel/888867 for more info). We shouldn't be too worried though, since we'll soon add functionality allowing a machine to set the __delay() loop themselves, thus allowing machines to resurrect the commented out code should they need it. Nico expressed concern that fixed lpj cmdlines will break due to compiler optimizations. That doesn't seem to be the case since before and after this patch I get the same lpj value when running my CPU at 19.2 MHz. That should be sufficiently slow enough to cover any machine running Linux. Reviewed-by: Saravana Kannan Acked-by: Nicolas Pitre Signed-off-by: Stephen Boyd Change-Id: I84311dc3955250960ffa8dc56d45a4833b3ad0f2 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13562 Tested-by: Mattias WALLIN Reviewed-by: Jonas ABERG Conflicts: arch/arm/lib/delay.S --- arch/arm/include/asm/delay.h | 2 +- arch/arm/kernel/armksyms.c | 4 --- arch/arm/lib/delay.S | 69 -------------------------------------------- arch/arm/lib/delay.c | 56 +++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 74 deletions(-) delete mode 100644 arch/arm/lib/delay.S create mode 100644 arch/arm/lib/delay.c diff --git a/arch/arm/include/asm/delay.h b/arch/arm/include/asm/delay.h index b2deda18154..ccc5ed573fa 100644 --- a/arch/arm/include/asm/delay.h +++ b/arch/arm/include/asm/delay.h @@ -8,7 +8,7 @@ #include /* HZ */ -extern void __delay(int loops); +extern void __delay(unsigned long loops); /* * This function intentionally does not exist; if you see references to diff --git a/arch/arm/kernel/armksyms.c b/arch/arm/kernel/armksyms.c index aeef960ff79..15f151e02d6 100644 --- a/arch/arm/kernel/armksyms.c +++ b/arch/arm/kernel/armksyms.c @@ -52,10 +52,6 @@ extern void fpundefinstr(void); EXPORT_SYMBOL(__backtrace); - /* platform dependent support */ -EXPORT_SYMBOL(__udelay); -EXPORT_SYMBOL(__const_udelay); - /* networking */ EXPORT_SYMBOL(csum_partial); EXPORT_SYMBOL(csum_partial_copy_from_user); diff --git a/arch/arm/lib/delay.S b/arch/arm/lib/delay.S deleted file mode 100644 index 3c9a05c8d20..00000000000 --- a/arch/arm/lib/delay.S +++ /dev/null @@ -1,69 +0,0 @@ -/* - * linux/arch/arm/lib/delay.S - * - * Copyright (C) 1995, 1996 Russell King - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ -#include -#include -#include - .text - -.LC0: .word loops_per_jiffy -.LC1: .word (2199023*HZ)>>11 - -/* - * r0 <= 2000 - * lpj <= 0x01ffffff (max. 3355 bogomips) - * HZ <= 1000 - */ - -ENTRY(__udelay) - ldr r2, .LC1 - mul r0, r2, r0 -ENTRY(__const_udelay) @ 0 <= r0 <= 0x7fffff06 - mov r1, #-1 - ldr r2, .LC0 - ldr r2, [r2] @ max = 0x01ffffff - add r0, r0, r1, lsr #32-14 - mov r0, r0, lsr #14 @ max = 0x0001ffff - add r2, r2, r1, lsr #32-10 - mov r2, r2, lsr #10 @ max = 0x00007fff - mul r0, r2, r0 @ max = 2^32-1 - add r0, r0, r1, lsr #32-6 - movs r0, r0, lsr #6 - moveq pc, lr - -/* - * loops = r0 * HZ * loops_per_jiffy / 1000000 - * - * Oh, if only we had a cycle counter... - */ - -@ Delay routine -ENTRY(__delay) - subs r0, r0, #1 -#if 0 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 - movls pc, lr - subs r0, r0, #1 -#endif - bhi __delay - mov pc, lr -ENDPROC(__udelay) -ENDPROC(__const_udelay) -ENDPROC(__delay) diff --git a/arch/arm/lib/delay.c b/arch/arm/lib/delay.c new file mode 100644 index 00000000000..f92aca011ba --- /dev/null +++ b/arch/arm/lib/delay.c @@ -0,0 +1,56 @@ +/* + * Originally from linux/arch/arm/lib/delay.S + * + * Copyright (C) 1995, 1996 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include +#include + +/* + * loops = usecs * HZ * loops_per_jiffy / 1000000 + * + * Oh, if only we had a cycle counter... + */ +void __delay(unsigned long loops) +{ + asm volatile( + "1: subs %0, %0, #1 \n" + " bhi 1b \n" + : /* No output */ + : "r" (loops) + ); +} +EXPORT_SYMBOL(__delay); + +/* + * 0 <= xloops <= 0x7fffff06 + * loops_per_jiffy <= 0x01ffffff (max. 3355 bogomips) + */ +void __const_udelay(unsigned long xloops) +{ + unsigned long lpj; + unsigned long loops; + + xloops >>= 14; /* max = 0x01ffffff */ + lpj = loops_per_jiffy >> 10; /* max = 0x0001ffff */ + loops = lpj * xloops; /* max = 0x00007fff */ + loops >>= 6; /* max = 2^32-1 */ + + if (likely(loops)) + __delay(loops); +} +EXPORT_SYMBOL(__const_udelay); + +/* + * usecs <= 2000 + * HZ <= 1000 + */ +void __udelay(unsigned long usecs) +{ + __const_udelay(usecs * ((2199023*HZ)>>11)); +} +EXPORT_SYMBOL(__udelay); -- cgit v1.2.3 From f606c95ecfec5e99c68af9ead04e2fe8d441066e Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Mon, 20 Dec 2010 14:20:31 -0800 Subject: ARM: Allow machines to override __delay() Some machines want to implement their own __delay() routine based on fixed rate timers. Expose functionality to set the __delay() routine at runtime. This should allow two machines with different __delay() routines to happily co-exist within the same kernel with minimal overhead. Russell expressed concern that using a timer based __delay() would cause problems when an iomapped device isn't mapped in prior to a delay call being made (see http://article.gmane.org/gmane.linux.ports.arm.kernel/78543 for more info). We can sidestep that issue with this approach since the __delay() routine _should_ only be pointed to a timer based delay once the timer has been properly mapped. Up until that point __delay() and udelay() will use delay_loop() which is always safe to call. This patch is inspired by x86's delay.c Reviewed-by: Saravana Kannan Signed-off-by: Stephen Boyd Change-Id: I269f101b40ba50c2b635dc92d50f6e82bb934b32 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13563 Tested-by: Mattias WALLIN Reviewed-by: Jonas ABERG --- arch/arm/include/asm/delay.h | 7 +++++++ arch/arm/lib/delay.c | 14 +++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/arch/arm/include/asm/delay.h b/arch/arm/include/asm/delay.h index ccc5ed573fa..82ef82a417b 100644 --- a/arch/arm/include/asm/delay.h +++ b/arch/arm/include/asm/delay.h @@ -40,5 +40,12 @@ extern void __const_udelay(unsigned long); __const_udelay((n) * ((2199023U*HZ)>>11))) : \ __udelay(n)) +extern void (*delay_fn)(unsigned long); + +static inline void set_delay_fn(void (*fn)(unsigned long)) +{ + delay_fn = fn; +} + #endif /* defined(_ARM_DELAY_H) */ diff --git a/arch/arm/lib/delay.c b/arch/arm/lib/delay.c index f92aca011ba..7468bc64b35 100644 --- a/arch/arm/lib/delay.c +++ b/arch/arm/lib/delay.c @@ -11,11 +11,9 @@ #include /* - * loops = usecs * HZ * loops_per_jiffy / 1000000 - * * Oh, if only we had a cycle counter... */ -void __delay(unsigned long loops) +static void delay_loop(unsigned long loops) { asm volatile( "1: subs %0, %0, #1 \n" @@ -24,6 +22,16 @@ void __delay(unsigned long loops) : "r" (loops) ); } + +void (*delay_fn)(unsigned long) = delay_loop; + +/* + * loops = usecs * HZ * loops_per_jiffy / 1000000 + */ +void __delay(unsigned long loops) +{ + delay_fn(loops); +} EXPORT_SYMBOL(__delay); /* -- cgit v1.2.3 From 7cab92ba7c64615ad9302db0fe1e9bca52fddbd1 Mon Sep 17 00:00:00 2001 From: Stephen Boyd Date: Mon, 20 Dec 2010 14:20:32 -0800 Subject: ARM: Implement a timer based __delay() loop udelay() can be incorrect on SMP machines that scale their CPU frequencies independently of one another (as pointed out here http://article.gmane.org/gmane.linux.kernel/977567). The delay loop can either be too fast or too slow depending on which CPU the loops_per_jiffy counter is calibrated on and which CPU the delay loop is running on. udelay() can also be incorrect if the CPU frequency switches during the __delay() loop, causing the loop to either terminate too early, or too late. Forcing udelay() to run on one CPU is unreasonable and taking the penalty of a rather large loops_per_jiffy in udelay() when the CPU is actually running slower is bad for performance. Solve the problem by adding a timer based__delay() loop unaffected by CPU frequency scaling. Machines should set this loop as their __delay() implementation by calling set_timer_fn() during their timer initialization. The kernel is already prepared for a timer based approach (evident by the read_current_timer() function). If an arch implements read_current_timer(), calibrate_delay() will use calibrate_delay_direct() to calculate loops_per_jiffy (in which case loops_per_jiffy should really be renamed to timer_ticks_per_jiffy). Since the loops_per_jiffy will be based on timer ticks, __delay() should be implemented as a loop around read_current_timer(). Doing this makes the expensive loops_per_jiffy calculation go away (saving ~150ms on boot time on my machine) and fixes udelay() by making it safe in the face of independently scaling CPUs. The only prerequisite is that read_current_timer() is monotonically increasing across calls (and doesn't overflow within ~2000us). There is a downside to this approach though. BogoMIPS is no longer "accurate" in that it reflects the BogoMIPS of the timer and not the CPU. On most SoC's the timer isn't running anywhere near as fast as the CPU so BogoMIPS will be ridiculously low (my timer runs at 4.8 MHz and thus my BogoMIPS is 9.6 compared to my CPU's 800). This shouldn't be too much of a concern though since BogoMIPS are bogus anyway (hence the name). This loop is pretty much a copy of AVR's version. Reported-and-reviewed-by: Saravana Kannan Signed-off-by: Stephen Boyd Change-Id: I9a4bee236ff1f26e1f2ae7e15e92b9ba14b46952 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/13564 Tested-by: Mattias WALLIN Reviewed-by: Jonas ABERG --- arch/arm/include/asm/delay.h | 2 ++ arch/arm/lib/delay.c | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/arch/arm/include/asm/delay.h b/arch/arm/include/asm/delay.h index 82ef82a417b..91063a3976f 100644 --- a/arch/arm/include/asm/delay.h +++ b/arch/arm/include/asm/delay.h @@ -47,5 +47,7 @@ static inline void set_delay_fn(void (*fn)(unsigned long)) delay_fn = fn; } +extern void read_current_timer_delay_loop(unsigned long loops); + #endif /* defined(_ARM_DELAY_H) */ diff --git a/arch/arm/lib/delay.c b/arch/arm/lib/delay.c index 7468bc64b35..f24c956e4fd 100644 --- a/arch/arm/lib/delay.c +++ b/arch/arm/lib/delay.c @@ -9,6 +9,7 @@ */ #include #include +#include /* * Oh, if only we had a cycle counter... @@ -23,6 +24,22 @@ static void delay_loop(unsigned long loops) ); } +#ifdef ARCH_HAS_READ_CURRENT_TIMER +/* + * Assumes read_current_timer() is monotonically increasing + * across calls and wraps at most once within MAX_UDELAY_MS. + */ +void read_current_timer_delay_loop(unsigned long loops) +{ + unsigned long bclock, now; + + read_current_timer(&bclock); + do { + read_current_timer(&now); + } while ((now - bclock) < loops); +} +#endif + void (*delay_fn)(unsigned long) = delay_loop; /* -- cgit v1.2.3 From 27b1051011443c3ef3e5d2119cfade443fdd99fd Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Mon, 28 Feb 2011 16:43:44 +0100 Subject: ARM: kernel: Remove warnings This patch removes a warning in order to reach a point where we have zero warnings and Hudson can start to give -1 results on any added ones. This patch shall never reach mainline. ST-Ericsson Linux next: Never ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Jonas Aaberg Change-Id: Ic80fb9b7280a52c1e03948de91b602d148b1a760 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17154 Reviewed-by: Mattias WALLIN --- arch/arm/kernel/return_address.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/arch/arm/kernel/return_address.c b/arch/arm/kernel/return_address.c index 0b13a72f855..b7fe66d270a 100644 --- a/arch/arm/kernel/return_address.c +++ b/arch/arm/kernel/return_address.c @@ -58,10 +58,6 @@ void *return_address(unsigned int level) #else /* if defined(CONFIG_FRAME_POINTER) && !defined(CONFIG_ARM_UNWIND) */ -#if defined(CONFIG_ARM_UNWIND) -#warning "TODO: return_address should use unwind tables" -#endif - void *return_address(unsigned int level) { return NULL; -- cgit v1.2.3 From bbc2d27c63e898c864fc12a76eb99e597832eaa5 Mon Sep 17 00:00:00 2001 From: "Mathieu J. Poirier" Date: Tue, 29 Mar 2011 16:29:14 -0600 Subject: Adding configuration options for cg2900. Signed-off-by: Mathieu Poirier --- arch/arm/configs/u8500_defconfig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 97d31a4663d..66a2481d62b 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -97,6 +97,13 @@ CONFIG_DMADEVICES=y CONFIG_STE_DMA40=y CONFIG_STAGING=y CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y +CONFIG_CG2900=y +CONFIG_CG2900_CHIP=y +CONFIG_STLC2690_CHIP=y +CONFIG_CG2900_UART=y +CONFIG_CG2900_AUDIO=y +CONFIG_CG2900_TEST=y +CONFIG_BT_CG2900=y CONFIG_EXT2_FS=y CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y @@ -109,6 +116,8 @@ CONFIG_CONFIGFS_FS=m # CONFIG_MISC_FILESYSTEMS is not set CONFIG_NFS_FS=y CONFIG_ROOT_NFS=y +CONFIG_PARTITION_ADVANCED=y +CONFIG_BLKDEV_PARTITION=y CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ISO8859_1=y CONFIG_MAGIC_SYSRQ=y -- cgit v1.2.3 From 356049db370fba00c853cc213df8895a8234dc8a Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 17 Mar 2011 09:45:22 +0100 Subject: amba-pl022: Add loopback support for the SPI on 5500 Add new amba-pl022 vendor for the DB5500 pl023, as the pl023 on db8500 and db5500 vary. Signed-off-by: Prajadevi H Signed-off-by: Philippe Langlais --- drivers/spi/spi-pl022.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 856dd06661e..d1ce32448a9 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -2376,14 +2376,6 @@ static struct vendor_data vendor_db5500_pl023 = { .loopback = true, }; -static struct vendor_data vendor_db5500_pl023 = { - .fifodepth = 32, - .max_bpw = 32, - .unidir = false, - .extended_cr = true, - .pl023 = true, -}; - static struct amba_id pl022_ids[] = { { /* -- cgit v1.2.3 From 5d4e47e61ca3964aadf6d55be80b03dce50780e4 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 7 Mar 2011 11:39:35 +0100 Subject: mfd: add a core driver for TI TPS61050/TPS61052 chips v2 The TPS61050/TPS61052 are boost converters, LED drivers, LED flash drivers and a simple GPIO pin chips. Cc: Liam Girdwood Cc: Mark Brown Cc: Jonas Aberg Cc: Ola Lilja Signed-off-by: Linus Walleij --- drivers/mfd/tps6105x.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/tps6105x.c b/drivers/mfd/tps6105x.c index a293b978e27..d7b9e0c60ea 100644 --- a/drivers/mfd/tps6105x.c +++ b/drivers/mfd/tps6105x.c @@ -195,6 +195,7 @@ static int __devinit tps6105x_probe(struct i2c_client *client, return 0; fail: + i2c_set_clientdata(client, NULL); kfree(tps6105x); return ret; } -- cgit v1.2.3 From 599edb0d5d164f7e2f3075bdd1a423d3e2e84f68 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Fri, 1 Apr 2011 16:17:19 +0200 Subject: BH1780: Fix regulator name --- drivers/misc/bh1780gli.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/misc/bh1780gli.c b/drivers/misc/bh1780gli.c index 428708c78e1..1e495bfd7a7 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -176,9 +176,8 @@ static int __devinit bh1780_probe(struct i2c_client *client, ddata->client = client; i2c_set_clientdata(client, ddata); - dev_set_name(&client->dev, "bh1780"); - ddata->regulator = regulator_get(&client->dev, "v-als"); + ddata->regulator = regulator_get(&client->dev, "vcc"); if (IS_ERR(ddata->regulator)) { dev_err(&client->dev, "failed to get regulator\n"); ret = PTR_ERR(ddata->regulator); -- cgit v1.2.3 From 8d37095ccb653cd19e4b6bc70acd7a4e7a20ea95 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 11 Apr 2011 11:36:54 +0200 Subject: ux500: Align u8500 defconfig with 2.6.35 one, u5500 is enabled too Signed-off-by: Philippe Langlais --- arch/arm/configs/u8500_defconfig | 165 +++++++++++++++++++++++++++++++++++---- 1 file changed, 151 insertions(+), 14 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 66a2481d62b..2c576e99ba5 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -1,42 +1,101 @@ CONFIG_EXPERIMENTAL=y -# CONFIG_SWAP is not set +# CONFIG_LOCALVERSION_AUTO is not set CONFIG_SYSVIPC=y +CONFIG_BSD_PROCESS_ACCT=y +CONFIG_LOG_BUF_SHIFT=18 +CONFIG_CGROUPS=y +CONFIG_CGROUP_CPUACCT=y +CONFIG_RELAY=y CONFIG_BLK_DEV_INITRD=y +CONFIG_EMBEDDED=y CONFIG_KALLSYMS_ALL=y +CONFIG_PERF_EVENTS=y +CONFIG_SLAB=y +CONFIG_BOOTTIME=y CONFIG_MODULES=y CONFIG_MODULE_UNLOAD=y -# CONFIG_LBDAF is not set +CONFIG_MODVERSIONS=y +CONFIG_MODULE_SRCVERSION_ALL=y # CONFIG_BLK_DEV_BSG is not set +CONFIG_DEFAULT_DEADLINE=y CONFIG_ARCH_U8500=y CONFIG_UX500_SOC_DB5500=y CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_U8500=y CONFIG_MACH_SNOWBALL=y +CONFIG_MACH_HREFV60=y CONFIG_MACH_U5500=y +CONFIG_DB8500_MLOADER=y +CONFIG_U8500_CPUIDLE_DEEPEST_STATE=3 +CONFIG_UX500_SUSPEND=y +CONFIG_UX500_SUSPEND_STANDBY=y +CONFIG_UX500_SUSPEND_MEM=y +CONFIG_UX500_SUSPEND_DBG=y +CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y CONFIG_NR_CPUS=2 CONFIG_PREEMPT=y CONFIG_AEABI=y -CONFIG_CMDLINE="root=/dev/ram0 console=ttyAMA2,115200n8" +CONFIG_CMDLINE="root=/dev/ram0 init=init rw console=ttyAMA2,115200n8 mem=256M initrd=0x800000,72M" +CONFIG_KEXEC=y +CONFIG_CRASH_DUMP=y CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT_DETAILS=y CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_IDLE=y +CONFIG_FPE_NWFPE=y CONFIG_VFP=y CONFIG_NEON=y +# CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set +CONFIG_PM=y +CONFIG_PM_RUNTIME=y CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y CONFIG_INET=y -CONFIG_IP_PNP=y -CONFIG_IP_PNP_DHCP=y +# CONFIG_INET_LRO is not set +CONFIG_IPV6=y +# CONFIG_INET6_XFRM_MODE_TRANSPORT is not set +# CONFIG_INET6_XFRM_MODE_TUNNEL is not set +# CONFIG_INET6_XFRM_MODE_BEET is not set +# CONFIG_IPV6_SIT is not set CONFIG_NETFILTER=y +CONFIG_NF_CONNTRACK=y +CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y +CONFIG_NETFILTER_XT_MATCH_STATE=y +CONFIG_NF_CONNTRACK_IPV4=y +CONFIG_IP_NF_IPTABLES=y +CONFIG_IP_NF_FILTER=y +CONFIG_NF_NAT=y +CONFIG_IP_NF_TARGET_MASQUERADE=y CONFIG_PHONET=y -# CONFIG_WIRELESS is not set +CONFIG_PHONET_PIPECTRLR=y +CONFIG_NET_SCHED=y +CONFIG_BT_L2CAP=y +CONFIG_BT_SCO=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_TTY=y +CONFIG_BT_BNEP=y +CONFIG_BT_BNEP_MC_FILTER=y +CONFIG_BT_BNEP_PROTO_FILTER=y +CONFIG_BT_HIDP=y +CONFIG_CFG80211=m +# CONFIG_CFG80211_WEXT is not set +CONFIG_MAC80211=m +CONFIG_MAC80211_LEDS=y +CONFIG_RFKILL=y +CONFIG_RFKILL_INPUT=y CONFIG_CAIF=y CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" +# CONFIG_STANDALONE is not set +CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_DEV_RAM=y -CONFIG_BLK_DEV_RAM_SIZE=65536 +CONFIG_BLK_DEV_RAM_SIZE=73728 CONFIG_MISC_DEVICES=y CONFIG_AB8500_PWM=y CONFIG_SENSORS_BH1780=y @@ -48,10 +107,26 @@ CONFIG_SMSC911X=y # CONFIG_NETDEV_10000 is not set # CONFIG_WLAN is not set # CONFIG_INPUT_MOUSEDEV_PSAUX is not set +CONFIG_STE_TRACE_MODEM=y +CONFIG_STM_TRACE=y +CONFIG_U8500_SHRM=y +CONFIG_U8500_SHRM_MODEM_SILENT_RESET=y +CONFIG_STM_I2S_TEST_PROTOCOL_DRIVER=y +CONFIG_SCSI=y +CONFIG_BLK_DEV_SD=y +CONFIG_NETDEVICES=y +CONFIG_TUN=y +CONFIG_USB_USBNET=y +# CONFIG_USB_NET_AX8817X is not set +# CONFIG_USB_NET_NET1080 is not set +# CONFIG_USB_BELKIN is not set +# CONFIG_USB_ARMLINUX is not set +# CONFIG_USB_NET_ZAURUS is not set +# CONFIG_INPUT_MOUSEDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set CONFIG_KEYBOARD_GPIO=y -CONFIG_KEYBOARD_NOMADIK=y +CONFIG_KEYBOARD_NOMADIK_SKE=y CONFIG_KEYBOARD_STMPE=y CONFIG_KEYBOARD_TC3589X=y # CONFIG_INPUT_MOUSE is not set @@ -59,37 +134,75 @@ CONFIG_INPUT_TOUCHSCREEN=y CONFIG_TOUCHSCREEN_BU21013=y CONFIG_INPUT_MISC=y CONFIG_INPUT_AB8500_PONKEY=y -# CONFIG_SERIO is not set +CONFIG_INPUT_UINPUT=y CONFIG_VT_HW_CONSOLE_BINDING=y # CONFIG_LEGACY_PTYS is not set +# CONFIG_DEVKMEM is not set CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM_NOMADIK=y CONFIG_I2C=y +CONFIG_I2C_CHARDEV=y CONFIG_I2C_NOMADIK=y CONFIG_SPI=y +# CONFIG_STM_MSP_SPI is not set CONFIG_SPI_PL022=y +CONFIG_GPIO_SYSFS=y CONFIG_GPIO_STMPE=y CONFIG_GPIO_TC3589X=y -# CONFIG_HWMON is not set +CONFIG_AB8500_GPIO=y +CONFIG_POWER_SUPPLY=y +CONFIG_AB8500_BM=y +CONFIG_SENSORS_AB8500=y +CONFIG_SENSORS_DB8500=y +CONFIG_SENSORS_LSM303DLH=y +CONFIG_SENSORS_L3G4200D=y +CONFIG_WATCHDOG=y +CONFIG_MPCORE_WATCHDOG=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y CONFIG_AB8500_CORE=y +CONFIG_REGULATOR_DEBUG=y +CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y -# CONFIG_HID_SUPPORT is not set +CONFIG_REGULATOR_AB8500_DEBUG=y +CONFIG_MEDIA_SUPPORT=y +CONFIG_VIDEO_DEV=y +# CONFIG_MEDIA_TUNER_CUSTOMISE is not set +# CONFIG_VIDEO_CAPTURE_DRIVERS is not set +CONFIG_FB=y +CONFIG_FB_MCDE=y +CONFIG_MCDE_DISPLAY_GENERIC_DSI=y +CONFIG_FB_B2R2=y +CONFIG_B2R2_PLUG_CONF=y +CONFIG_SOUND=y +CONFIG_SND=y +CONFIG_SND_USB_AUDIO=y +CONFIG_USB=y +CONFIG_USB_DEBUG=y +CONFIG_USB_DEVICEFS=y +# CONFIG_USB_OTG_WHITELIST is not set +CONFIG_USB_MON=y CONFIG_USB_MUSB_HDRC=y +CONFIG_USB_MUSB_OTG=y CONFIG_USB_GADGET_MUSB_HDRC=y -CONFIG_MUSB_PIO_ONLY=y +CONFIG_USB_ACM=y +CONFIG_USB_STORAGE=y +CONFIG_USB_LIBUSUAL=y CONFIG_USB_GADGET=y +CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_NOP_USB_XCEIV=y CONFIG_AB8500_USB=y CONFIG_MMC=y CONFIG_MMC_CLKGATE=y +CONFIG_MMC_UNSAFE_RESUME=y CONFIG_MMC_ARMMMCI=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y CONFIG_LEDS_LM3530=y CONFIG_LEDS_LP5521=y +CONFIG_LEDS_PWM=y CONFIG_RTC_CLASS=y CONFIG_RTC_DRV_AB8500=y CONFIG_RTC_DRV_PL031=y @@ -109,6 +222,11 @@ CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y CONFIG_EXT2_FS_SECURITY=y CONFIG_EXT3_FS=y +# CONFIG_EXT3_DEFAULTS_TO_ORDERED is not set +CONFIG_EXT3_FS_POSIX_ACL=y +CONFIG_EXT4_FS=y +CONFIG_EXT4_FS_POSIX_ACL=y +CONFIG_QUOTA=y CONFIG_VFAT_FS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y @@ -116,15 +234,34 @@ CONFIG_CONFIGFS_FS=m # CONFIG_MISC_FILESYSTEMS is not set CONFIG_NFS_FS=y CONFIG_ROOT_NFS=y +# CONFIG_NETWORK_FILESYSTEMS is not set CONFIG_PARTITION_ADVANCED=y CONFIG_BLKDEV_PARTITION=y CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ISO8859_1=y +CONFIG_PRINTK_TIME=y CONFIG_MAGIC_SYSRQ=y -CONFIG_DEBUG_FS=y +CONFIG_UNUSED_SYMBOLS=y CONFIG_DEBUG_KERNEL=y -# CONFIG_SCHED_DEBUG is not set +CONFIG_DETECT_HUNG_TASK=y +CONFIG_SCHEDSTATS=y +CONFIG_TIMER_STATS=y # CONFIG_DEBUG_PREEMPT is not set +CONFIG_DEBUG_SPINLOCK_SLEEP=y CONFIG_DEBUG_INFO=y # CONFIG_FTRACE is not set CONFIG_DEBUG_USER=y +# CONFIG_RCU_CPU_STALL_DETECTOR is not set +CONFIG_SYSCTL_SYSCALL_CHECK=y +CONFIG_SCHED_TRACER=y +CONFIG_DYNAMIC_DEBUG=y +CONFIG_DEBUG_USER=y +CONFIG_DEBUG_ERRORS=y +CONFIG_KEYS=y +CONFIG_CRYPTO_ECB=y +CONFIG_CRYPTO_AES=y +CONFIG_CRYPTO_ARC4=y +# CONFIG_CRYPTO_ANSI_CPRNG is not set +CONFIG_CRYPTO_DEV_UX500=y +CONFIG_CRYPTO_DEV_UX500_CRYP=y +CONFIG_CRC7=y -- cgit v1.2.3 From a062eca78412f9356dd0b796d812c3250fc99d51 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Tue, 12 Apr 2011 14:48:52 +0200 Subject: power: ab8500-bm: update usb state change interface to otg notifier Remove usb_state_changed() exported function. Replace it with otg notifier call. This makes static_di redundant so it is removed. Moreover, there is no need to protect usb_state.usb_changed with usb_lock. So usb_lock is removed as well. Usb state is fabricated based on the reported mA. State is not as accurate as reading it directly from the usb controller. This should not matter as battery- manager driver should care more about mA than the state anyway. Signed-off-by: Mian Yousaf Kaukab --- drivers/power/ab8500_charger.c | 67 +++++++++++++++++++++++++++--------------- include/linux/mfd/ab8500/bm.h | 7 ----- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index 194ff9d23d7..9c4a9b9aa2b 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -25,6 +25,7 @@ #include #include #include +#include /* Charger constants */ #define NO_PW_CONN 0 @@ -135,7 +136,6 @@ struct ab8500_charger_usb_state { bool usb_changed; int usb_current; enum ab8500_usb_state state; - spinlock_t usb_lock; }; /** @@ -198,15 +198,10 @@ struct ab8500_charger { struct work_struct check_usbchgnotok_work; struct work_struct check_main_thermal_prot_work; struct work_struct check_usb_thermal_prot_work; + struct otg_transceiver *otg; + struct notifier_block nb; }; -/* - * TODO: This variable is static in order to get information - * about maximum current and USB state from the USB driver - * This should be solved in a better way - */ -static struct ab8500_charger *static_di; - /* AC properties */ static enum power_supply_property ab8500_charger_ac_props[] = { POWER_SUPPLY_PROP_HEALTH, @@ -1348,9 +1343,7 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work) if (!di->vbus_detected) return; - spin_lock(&di->usb_state.usb_lock); di->usb_state.usb_changed = false; - spin_unlock(&di->usb_state.usb_lock); /* * wait for some time until you get updates from the usb stack @@ -2026,25 +2019,39 @@ static struct ab8500_charger_interrupts ab8500_charger_irq[] = { {"CH_WD_EXP", ab8500_charger_chwdexp_handler}, }; -void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) +static int ab8500_charger_usb_notifier_call(struct notifier_block *nb, + unsigned long event, void *power) { - struct ab8500_charger *di = static_di; + struct ab8500_charger *di = + container_of(nb, struct ab8500_charger, nb); + enum ab8500_usb_state bm_usb_state; + unsigned mA = *((unsigned *)power); + + /* TODO: State is fabricate here. See if charger really needs USB + * state or if mA is enough + */ + if ((di->usb_state.usb_current == 2) && (mA > 2)) + bm_usb_state = AB8500_BM_USB_STATE_RESUME; + else if (mA == 0) + bm_usb_state = AB8500_BM_USB_STATE_RESET_HS; + else if (mA == 2) + bm_usb_state = AB8500_BM_USB_STATE_SUSPEND; + else if (mA >= 8) /* 8, 100, 500 */ + bm_usb_state = AB8500_BM_USB_STATE_CONFIGURED; + else /* Should never occur */ + bm_usb_state = AB8500_BM_USB_STATE_RESET_FS; dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n", __func__, bm_usb_state, mA); - spin_lock(&di->usb_state.usb_lock); di->usb_state.usb_changed = true; - spin_unlock(&di->usb_state.usb_lock); - di->usb_state.state = bm_usb_state; di->usb_state.usb_current = mA; queue_work(di->charger_wq, &di->usb_state_changed_work); - return; + return NOTIFY_OK; } -EXPORT_SYMBOL(ab8500_charger_usb_state_changed); #if defined(CONFIG_PM) static int ab8500_charger_resume(struct platform_device *pdev) @@ -2124,6 +2131,9 @@ static int __devexit ab8500_charger_remove(struct platform_device *pdev) if (ret < 0) dev_err(di->dev, "%s mask and set failed\n", __func__); + otg_unregister_notifier(di->otg, &di->nb); + otg_put_transceiver(di->otg); + /* Delete the work queue */ destroy_workqueue(di->charger_wq); @@ -2146,16 +2156,11 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) if (!di) return -ENOMEM; - static_di = di; - /* get parent data */ di->dev = &pdev->dev; di->parent = dev_get_drvdata(pdev->dev.parent); di->gpadc = ab8500_gpadc_get(); - /* initialize lock */ - spin_lock_init(&di->usb_state.usb_lock); - plat = dev_get_platdata(di->parent->dev); /* get charger specific platform data */ @@ -2283,6 +2288,18 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) goto free_ac; } + di->otg = otg_get_transceiver(); + if (!di->otg) { + dev_err(di->dev, "failed to get otg transceiver\n"); + goto free_usb; + } + di->nb.notifier_call = ab8500_charger_usb_notifier_call; + ret = otg_register_notifier(di->otg, &di->nb); + if (ret) { + dev_err(di->dev, "failed to register otg notifier\n"); + goto put_otg_transceiver; + } + /* Identify the connected charger types during startup */ charger_status = ab8500_charger_detect_chargers(di); if (charger_status & AC_PW_CONN) { @@ -2321,13 +2338,17 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) return ret; free_irq: - power_supply_unregister(&di->usb_chg.psy); + otg_unregister_notifier(di->otg, &di->nb); /* We also have to free all successfully registered irqs */ for (i = i - 1; i >= 0; i--) { irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name); free_irq(irq, di); } +put_otg_transceiver: + otg_put_transceiver(di->otg); +free_usb: + power_supply_unregister(&di->usb_chg.psy); free_ac: power_supply_unregister(&di->ac_chg.psy); free_charger_wq: diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h index aacba93b912..0e7ed692dfa 100644 --- a/include/linux/mfd/ab8500/bm.h +++ b/include/linux/mfd/ab8500/bm.h @@ -446,11 +446,4 @@ struct ab8500_chargalg_platform_data { char **supplied_to; size_t num_supplicants; }; -#ifdef CONFIG_AB8500_BM -void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); -#else -static void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) -{ -} -#endif #endif /* _AB8500_BM_H */ -- cgit v1.2.3 From 51d433a9772ae12049c1179e56180c8e31f73f17 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 13 Apr 2011 15:07:44 +0200 Subject: Update USB configurations in u8500_defconfig Signed-off-by: Philippe Langlais --- arch/arm/configs/u8500_defconfig | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 2c576e99ba5..43f9e7883e5 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -180,8 +180,8 @@ CONFIG_SOUND=y CONFIG_SND=y CONFIG_SND_USB_AUDIO=y CONFIG_USB=y -CONFIG_USB_DEBUG=y -CONFIG_USB_DEVICEFS=y +# CONFIG_USB_DEVICE_CLASS is not set +CONFIG_USB_SUSPEND=y # CONFIG_USB_OTG_WHITELIST is not set CONFIG_USB_MON=y CONFIG_USB_MUSB_HDRC=y @@ -192,7 +192,13 @@ CONFIG_USB_STORAGE=y CONFIG_USB_LIBUSUAL=y CONFIG_USB_GADGET=y CONFIG_USB_GADGET_VBUS_DRAW=500 -CONFIG_NOP_USB_XCEIV=y +CONFIG_USB_ZERO=m +CONFIG_USB_ETH=m +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_G_SERIAL=m +CONFIG_USB_CDC_COMPOSITE=m +CONFIG_USB_G_MULTI=m +# CONFIG_USB_G_MULTI_RNDIS is not set CONFIG_AB8500_USB=y CONFIG_MMC=y CONFIG_MMC_CLKGATE=y -- cgit v1.2.3 From beed90cf9ad482481ee5a4833a0741691a1467ab Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 11 Oct 2011 14:10:09 +0200 Subject: wdt: Warnings fix Signed-off-by: Philippe Langlais --- drivers/watchdog/mpcore_wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/mpcore_wdt.c b/drivers/watchdog/mpcore_wdt.c index 0603422d316..85997b6c298 100644 --- a/drivers/watchdog/mpcore_wdt.c +++ b/drivers/watchdog/mpcore_wdt.c @@ -38,7 +38,7 @@ #include struct mpcore_wdt { - unsigned long timer_alive; + cpumask_t timer_alive; struct device *dev; void __iomem *base; int irq; -- cgit v1.2.3 From aaf802e1907ad068751e1e4cb25300d35cdad769 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 11 Oct 2011 14:30:52 +0200 Subject: ux500: Adding board, smsc911x and DHCP configuration for snowball Signed-off-by: Mathieu Poirier Conflicts: arch/arm/configs/u8500_defconfig --- arch/arm/configs/u8500_defconfig | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 43f9e7883e5..021efa37106 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -24,9 +24,12 @@ CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_U8500=y CONFIG_MACH_SNOWBALL=y CONFIG_MACH_HREFV60=y +CONFIG_MACH_U8500_SNOWBALL=y CONFIG_MACH_U5500=y CONFIG_DB8500_MLOADER=y CONFIG_U8500_CPUIDLE_DEEPEST_STATE=3 +CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y +CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_STANDBY=y CONFIG_UX500_SUSPEND_MEM=y @@ -58,6 +61,8 @@ CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y CONFIG_INET=y +CONFIG_IP_PNP=y +CONFIG_IP_PNP_DHCP=y # CONFIG_INET_LRO is not set CONFIG_IPV6=y # CONFIG_INET6_XFRM_MODE_TRANSPORT is not set @@ -116,6 +121,9 @@ CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y CONFIG_NETDEVICES=y CONFIG_TUN=y +CONFIG_SMSC_PHY=y +CONFIG_NET_ETHERNET=y +CONFIG_SMSC911X=y CONFIG_USB_USBNET=y # CONFIG_USB_NET_AX8817X is not set # CONFIG_USB_NET_NET1080 is not set @@ -173,7 +181,6 @@ CONFIG_VIDEO_DEV=y # CONFIG_VIDEO_CAPTURE_DRIVERS is not set CONFIG_FB=y CONFIG_FB_MCDE=y -CONFIG_MCDE_DISPLAY_GENERIC_DSI=y CONFIG_FB_B2R2=y CONFIG_B2R2_PLUG_CONF=y CONFIG_SOUND=y -- cgit v1.2.3 From 2aac6c5b62b9df7ab3a4bbf0e4ba6d97c4e544e2 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Thu, 21 Apr 2011 10:47:40 +0200 Subject: rtc: ab8500: Dont disable IRQ:s when suspending Signed-off-by: Robert Marklund --- drivers/rtc/rtc-ab8500.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-ab8500.c b/drivers/rtc/rtc-ab8500.c index e346705aae9..d6bfc4f3658 100644 --- a/drivers/rtc/rtc-ab8500.c +++ b/drivers/rtc/rtc-ab8500.c @@ -316,8 +316,8 @@ static int __devinit ab8500_rtc_probe(struct platform_device *pdev) return err; } - err = request_threaded_irq(irq, NULL, rtc_alarm_handler, 0, - "ab8500-rtc", rtc); + err = request_threaded_irq(irq, NULL, rtc_alarm_handler, + IRQF_NO_SUSPEND, "ab8500-rtc", rtc); if (err < 0) { rtc_device_unregister(rtc); return err; -- cgit v1.2.3 From b9268d9879dc4a99a9183c7123cd5c90d9563240 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 11 Oct 2011 14:36:29 +0200 Subject: mach-ux500: cleanup u8500_defconfig Signed-off-by: Philippe Langlais Conflicts: arch/arm/configs/u8500_defconfig --- arch/arm/configs/u8500_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 021efa37106..96dc3a9da13 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -30,6 +30,7 @@ CONFIG_DB8500_MLOADER=y CONFIG_U8500_CPUIDLE_DEEPEST_STATE=3 CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y +CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_STANDBY=y CONFIG_UX500_SUSPEND_MEM=y @@ -174,7 +175,6 @@ CONFIG_AB8500_CORE=y CONFIG_REGULATOR_DEBUG=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y -CONFIG_REGULATOR_AB8500_DEBUG=y CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y # CONFIG_MEDIA_TUNER_CUSTOMISE is not set -- cgit v1.2.3 From 173555c9258e83fe7d4de85af7d9335527e5025d Mon Sep 17 00:00:00 2001 From: Sakethram Bommisetti Date: Thu, 5 May 2011 10:33:05 +0530 Subject: ux500:Removing warnings from USB Disabling USB_USBNET which is not used and which is resulting in warnings for the compiler. Signed-off-by: Sakethram Bommisetti --- arch/arm/configs/u8500_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 96dc3a9da13..f82ec2e33d0 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -125,7 +125,7 @@ CONFIG_TUN=y CONFIG_SMSC_PHY=y CONFIG_NET_ETHERNET=y CONFIG_SMSC911X=y -CONFIG_USB_USBNET=y +#CONFIG_USB_USBNET is not set # CONFIG_USB_NET_AX8817X is not set # CONFIG_USB_NET_NET1080 is not set # CONFIG_USB_BELKIN is not set -- cgit v1.2.3 From 52c4a9eb4af0d3f99572df7aa8d14cf4e1b1b2d0 Mon Sep 17 00:00:00 2001 From: Johan Palsson Date: Fri, 4 Mar 2011 11:05:09 +0100 Subject: power: ab8500_bm: Use maximum charger current according to charger configuration During configuration of the charger, a maximum charger current is set. The requested charger current will never be more than this. ST-Ericsson Linux next: - ST-Ericsson ID: WP324204 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I00e71ec126ff5f13fe1e1581020456d6609c788d Signed-off-by: Johan Palsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17417 Reviewed-by: Karl KOMIEROWSKI Reviewed-by: Jonas ABERG --- drivers/power/ab8500_chargalg.c | 24 ++++--- drivers/power/ab8500_charger.c | 137 ++++++++++++++++++++++++++++++++++------ include/linux/mfd/ab8500/bm.h | 15 ----- 3 files changed, 130 insertions(+), 46 deletions(-) diff --git a/drivers/power/ab8500_chargalg.c b/drivers/power/ab8500_chargalg.c index 2137f467064..9a1efd7c078 100644 --- a/drivers/power/ab8500_chargalg.c +++ b/drivers/power/ab8500_chargalg.c @@ -613,21 +613,19 @@ static void ab8500_chargalg_check_temp(struct ab8500_chargalg *di) } /** - * ab8500_chargalg_check_charger_health() - Check charger health + * ab8500_chargalg_check_charger_voltage() - Check charger voltage * @di: pointer to the ab8500_chargalg structure * - * Charger voltage and current is checked against maximum limits + * Charger voltage is checked against maximum limit */ -static void ab8500_chargalg_check_charger_health(struct ab8500_chargalg *di) +static void ab8500_chargalg_check_charger_voltage(struct ab8500_chargalg *di) { - if (di->chg_info.usb_volt > di->bat->chg_params->usb_volt_max || - di->chg_info.usb_curr > di->bat->chg_params->usb_curr_max) + if (di->chg_info.usb_volt > di->bat->chg_params->usb_volt_max) di->chg_info.usb_chg_ok = false; else di->chg_info.usb_chg_ok = true; - if (di->chg_info.ac_volt > di->bat->chg_params->ac_volt_max || - di->chg_info.ac_curr > di->bat->chg_params->ac_curr_max) + if (di->chg_info.ac_volt > di->bat->chg_params->ac_volt_max) di->chg_info.ac_chg_ok = false; else di->chg_info.ac_chg_ok = true; @@ -771,15 +769,21 @@ static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) static void handle_maxim_chg_curr(struct ab8500_chargalg *di) { enum maxim_ret ret; + int result; ret = ab8500_chargalg_chg_curr_maxim(di); switch (ret) { case MAXIM_RET_CHANGE: - ab8500_chargalg_update_chg_curr(di, di->ccm.current_iset); + result = ab8500_chargalg_update_chg_curr(di, + di->ccm.current_iset); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); break; case MAXIM_RET_IBAT_TOO_HIGH: - ab8500_chargalg_update_chg_curr(di, + result = ab8500_chargalg_update_chg_curr(di, di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); break; case MAXIM_RET_NOACTION: @@ -1131,7 +1135,7 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) ab8500_chargalg_end_of_charge(di); ab8500_chargalg_check_temp(di); - ab8500_chargalg_check_charger_health(di); + ab8500_chargalg_check_charger_voltage(di); charger_status = ab8500_chargalg_check_charger_connection(di); /* diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index 9c4a9b9aa2b..4aff8d03eae 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -47,6 +47,9 @@ #define VBUS_DET_DBNC1 0x01 #define OTP_ENABLE_WD 0x01 +#define MAIN_CH_INPUT_CURR_SHIFT 4 +#define VBUS_IN_CURR_LIM_SHIFT 4 + #define LED_INDICATOR_PWM_ENA 0x01 #define LED_INDICATOR_PWM_DIS 0x00 #define LED_IND_CUR_5MA 0x04 @@ -100,6 +103,24 @@ enum ab8500_usb_state { AB8500_BM_USB_STATE_MAX, }; +/* VBUS input current limits supported in AB8500 in mA */ +#define USB_CH_IP_CUR_LVL_0P05 50 +#define USB_CH_IP_CUR_LVL_0P09 98 +#define USB_CH_IP_CUR_LVL_0P19 193 +#define USB_CH_IP_CUR_LVL_0P29 290 +#define USB_CH_IP_CUR_LVL_0P38 380 +#define USB_CH_IP_CUR_LVL_0P45 450 +#define USB_CH_IP_CUR_LVL_0P5 500 +#define USB_CH_IP_CUR_LVL_0P6 600 +#define USB_CH_IP_CUR_LVL_0P7 700 +#define USB_CH_IP_CUR_LVL_0P8 800 +#define USB_CH_IP_CUR_LVL_0P9 900 +#define USB_CH_IP_CUR_LVL_1P0 1000 +#define USB_CH_IP_CUR_LVL_1P1 1100 +#define USB_CH_IP_CUR_LVL_1P3 1300 +#define USB_CH_IP_CUR_LVL_1P4 1400 +#define USB_CH_IP_CUR_LVL_1P5 1500 + #define to_ab8500_charger_usb_device_info(x) container_of((x), \ struct ab8500_charger, usb_chg) #define to_ab8500_charger_ac_device_info(x) container_of((x), \ @@ -480,7 +501,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, break; }; - dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: 0x%02x", + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status, di->max_usb_in_curr); return ret; @@ -675,6 +696,29 @@ static int ab8500_charger_current_map[] = { 1500 , }; +/* + * This array maps the raw hex value to VBUS input current used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_vbus_in_curr_map[] = { + USB_CH_IP_CUR_LVL_0P05, + USB_CH_IP_CUR_LVL_0P09, + USB_CH_IP_CUR_LVL_0P19, + USB_CH_IP_CUR_LVL_0P29, + USB_CH_IP_CUR_LVL_0P38, + USB_CH_IP_CUR_LVL_0P45, + USB_CH_IP_CUR_LVL_0P5, + USB_CH_IP_CUR_LVL_0P6, + USB_CH_IP_CUR_LVL_0P7, + USB_CH_IP_CUR_LVL_0P8, + USB_CH_IP_CUR_LVL_0P9, + USB_CH_IP_CUR_LVL_1P0, + USB_CH_IP_CUR_LVL_1P1, + USB_CH_IP_CUR_LVL_1P3, + USB_CH_IP_CUR_LVL_1P4, + USB_CH_IP_CUR_LVL_1P5, +}; + static int ab8500_voltage_to_regval(int voltage) { int i; @@ -716,6 +760,26 @@ static int ab8500_current_to_regval(int curr) return -1; } +static int ab8500_vbus_in_curr_to_regval(int curr) +{ + int i; + + if (curr < ab8500_charger_vbus_in_curr_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab8500_charger_vbus_in_curr_map); i++) { + if (curr < ab8500_charger_vbus_in_curr_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_vbus_in_curr_map) - 1; + if (curr == ab8500_charger_vbus_in_curr_map[i]) + return i; + else + return -1; +} + /** * ab8500_charger_get_usb_cur() - get usb current * @di: pointer to the ab8500_charger structre @@ -751,6 +815,39 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di) return 0; } +/** + * ab8500_charger_set_vbus_in_curr() - set VBUS input current limit + * @di: pointer to the ab8500_charger structure + * @ich_in: charger input current limit + * + * Sets the current that can be drawn from the USB host + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di, + int ich_in) +{ + int ret; + int input_curr_index; + int min_value; + + /* We should always use to lowest current limit */ + min_value = min(di->bat->chg_params->usb_curr_max, ich_in); + + input_curr_index = ab8500_vbus_in_curr_to_regval(min_value); + if (input_curr_index < 0) { + dev_err(di->dev, "VBUS input current limit too high\n"); + return -ENXIO; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_IPT_CRNTLVL_REG, + input_curr_index << VBUS_IN_CURR_LIM_SHIFT); + if (ret) + dev_err(di->dev, "%s write failed\n", __func__); + + return ret; +} + /** * ab8500_charger_led_en() - turn on/off chargign led * @di: pointer to the ab8500_charger structure @@ -810,6 +907,7 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger, int ret; int volt_index; int curr_index; + int input_curr_index; u8 overshoot = 0; struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger); @@ -827,7 +925,9 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger, /* Check if the requested voltage or current is valid */ volt_index = ab8500_voltage_to_regval(vset); curr_index = ab8500_current_to_regval(iset); - if (volt_index < 0 || curr_index < 0) { + input_curr_index = ab8500_current_to_regval( + di->bat->chg_params->ac_curr_max); + if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) { dev_err(di->dev, "Charger voltage or current too high, " "charging not started\n"); @@ -843,7 +943,8 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger, } /* MainChInputCurr: current that can be drawn from the charger*/ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_MCH_IPT_CURLVL_REG, MAIN_CH_IP_CUR_1P5A); + AB8500_MCH_IPT_CURLVL_REG, + input_curr_index << MAIN_CH_INPUT_CURR_SHIFT); if (ret) { dev_err(di->dev, "%s write failed\n", __func__); return ret; @@ -996,11 +1097,9 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger, return ret; } /* USBChInputCurr: current that can be drawn from the usb */ - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_USBCH_IPT_CRNTLVL_REG, - di->max_usb_in_curr); + ret = ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); + dev_err(di->dev, "setting USBChInputCurr failed\n"); return ret; } /* ChOutputCurentLevel: protected output current */ @@ -1103,7 +1202,7 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *charger, curr_index = ab8500_current_to_regval(ich_out); if (curr_index < 0) { dev_err(di->dev, - "Charger voltage or current too high, " + "Charger current too high, " "charging not started\n"); return -ENXIO; } @@ -1317,13 +1416,11 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work) ret = ab8500_charger_read_usb_type(di); if (!ret) { /* Update maximum input current */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_USBCH_IPT_CRNTLVL_REG, - di->max_usb_in_curr); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); + ret = ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr); + if (ret) return; - } + di->usb.charger_connected = 1; power_supply_changed(&di->usb_chg.psy); } else if (ret == -ENXIO) { @@ -1380,13 +1477,11 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work) */ if (!ab8500_charger_get_usb_cur(di)) { /* Update maximum input current */ - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_USBCH_IPT_CRNTLVL_REG, - di->max_usb_in_curr); - if (ret) { - dev_err(di->dev, "%s write failed\n", __func__); + ret = ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr); + if (ret) return; - } + di->usb.charger_connected = 1; power_supply_changed(&di->usb_chg.psy); } @@ -2209,7 +2304,7 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) /* ux500_charger sub-class */ di->usb_chg.ops.enable = &ab8500_charger_usb_en; di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; - di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->usb_chg.ops.update_curr = &ab8500_charger_update_charger_current; di->usb_chg.max_out_volt = ab8500_charger_voltage_map[ ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; di->usb_chg.max_out_curr = ab8500_charger_current_map[ diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h index 0e7ed692dfa..70994f434df 100644 --- a/include/linux/mfd/ab8500/bm.h +++ b/include/linux/mfd/ab8500/bm.h @@ -163,21 +163,6 @@ #define USB_0P4A 400 #define USB_0P5A 500 -/* UsbChCurrLevel */ -#define USB_CH_IP_CUR_LVL_0P05 0x00 -#define USB_CH_IP_CUR_LVL_0P09 0x10 -#define USB_CH_IP_CUR_LVL_0P19 0x20 -#define USB_CH_IP_CUR_LVL_0P29 0x30 -#define USB_CH_IP_CUR_LVL_0P38 0x40 -#define USB_CH_IP_CUR_LVL_0P45 0x50 -#define USB_CH_IP_CUR_LVL_0P5 0x60 -#define USB_CH_IP_CUR_LVL_0P9 0xA0 -#define USB_CH_IP_CUR_LVL_1P0 0xB0 -#define USB_CH_IP_CUR_LVL_1P1 0xC0 -#define USB_CH_IP_CUR_LVL_1P3 0xD0 -#define USB_CH_IP_CUR_LVL_1P4 0xE0 -#define USB_CH_IP_CUR_LVL_1P5 0xF0 - #define LOW_BAT_3P1V 0x20 #define LOW_BAT_2P3V 0x00 #define LOW_BAT_RESET 0x01 -- cgit v1.2.3 From 528b740a8f5cf530936e462a8e54799afd54a281 Mon Sep 17 00:00:00 2001 From: Johan Palsson Date: Fri, 4 Mar 2011 11:09:57 +0100 Subject: power: ab8500_bm: Allow USB charger voltage to collapse When the requested USB charger current is more than the charger can deliver, the output voltage will drop and AB8500 will detect it as a not-ok charger. In this case we will try to lower the current and try charging again ST-Ericsson Linux next: - ST-Ericsson ID: ER327042 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I620663643d08b214b38a7498d891f30e3e4342ca Signed-off-by: Johan Palsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17544 Reviewed-by: Karl KOMIEROWSKI Reviewed-by: Jonas ABERG --- drivers/power/ab8500_chargalg.c | 120 ++++++++++++++++++++++++++++------------ drivers/power/ab8500_charger.c | 46 +++++++++++++-- include/linux/mfd/ab8500/bm.h | 1 + 3 files changed, 128 insertions(+), 39 deletions(-) diff --git a/drivers/power/ab8500_chargalg.c b/drivers/power/ab8500_chargalg.c index 9a1efd7c078..f4e4528b15c 100644 --- a/drivers/power/ab8500_chargalg.c +++ b/drivers/power/ab8500_chargalg.c @@ -51,6 +51,10 @@ struct ab8500_chargalg_charger_info { int usb_curr; int ac_volt; int ac_curr; + int usb_vset; + int usb_iset; + int ac_vset; + int ac_iset; }; struct ab8500_chargalg_suspension_status { @@ -143,6 +147,7 @@ struct ab8500_chargalg_events { bool usb_wd_expired; bool ac_cv_active; bool usb_cv_active; + bool vbus_collapsed; }; /** @@ -154,6 +159,9 @@ struct ab8500_chargalg_events { * @condition_cnt: number of iterations needed before a new charger current is set * @max_current: maximum charger current + * @wait_cnt: to avoid too fast current step down in case of charger + * voltage collapse, we insert this delay between step + * down * @level: tells in how many steps the charging current has been increased */ @@ -163,6 +171,7 @@ struct ab8500_charge_curr_maximization { int test_delta_i; int condition_cnt; int max_current; + int wait_cnt; u8 level; }; @@ -464,6 +473,9 @@ static int ab8500_chargalg_ac_en(struct ab8500_chargalg *di, int enable, if (di->ac_chg->max_out_curr) iset = min(iset, di->ac_chg->max_out_curr); + di->chg_info.ac_iset = iset; + di->chg_info.ac_vset = vset; + return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset); } @@ -489,6 +501,9 @@ static int ab8500_chargalg_usb_en(struct ab8500_chargalg *di, int enable, if (di->usb_chg->max_out_curr) iset = min(iset, di->usb_chg->max_out_curr); + di->chg_info.usb_iset = iset; + di->chg_info.usb_vset = vset; + return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset); } @@ -505,11 +520,30 @@ static int ab8500_chargalg_update_chg_curr(struct ab8500_chargalg *di, { /* Check if charger exists and update current if charging */ if (di->ac_chg && di->ac_chg->ops.update_curr && - di->chg_info.charger_type & AC_CHG) + di->chg_info.charger_type & AC_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + di->chg_info.ac_iset = iset; + return di->ac_chg->ops.update_curr(di->ac_chg, iset); - else if (di->usb_chg && di->usb_chg->ops.update_curr && - di->chg_info.charger_type & USB_CHG) + } else if (di->usb_chg && di->usb_chg->ops.update_curr && + di->chg_info.charger_type & USB_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + di->chg_info.usb_iset = iset; + return di->usb_chg->ops.update_curr(di->usb_chg, iset); + } return -ENXIO; } @@ -697,22 +731,26 @@ static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) return MAXIM_RET_NOACTION; delta_i = di->ccm.original_iset - di->batt_data.inst_curr; - dev_dbg(di->dev, "[CHARGALG_OPTI_DATA], Ucv ,%d, Dcv ,%d, cnt ,%d," - " cur iset ,%d, avg i ,%d, Ibat inst: ,%d,mA, Orig iset: ,%d, " - "delta_i ,%d, Max ,%d,mA" - " AC chg curr: ,%d,mA" - " USB chg curr: ,%d,mA\n", - di->events.usb_cv_active, - di->events.ac_cv_active, - di->ccm.condition_cnt, - di->ccm.current_iset, - di->batt_data.avg_curr, - di->batt_data.inst_curr, - di->ccm.original_iset, - delta_i, - di->ccm.max_current, - di->chg_info.ac_curr, - di->chg_info.usb_curr); + + if (di->events.vbus_collapsed) { + dev_dbg(di->dev, "Charger voltage has collapsed\n"); + if (di->ccm.wait_cnt++ == 0) { + dev_dbg(di->dev, "lowering current\n"); + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.max_current = + di->ccm.current_iset - di->ccm.test_delta_i; + di->ccm.current_iset = di->ccm.max_current; + di->ccm.level--; + return MAXIM_RET_CHANGE; + } else { + dev_dbg(di->dev, "waiting\n"); + /* Let's go in here twice before lowering curr again */ + di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 4; + return MAXIM_RET_NOACTION; + } + } + + di->ccm.wait_cnt = 0; if ((di->batt_data.inst_curr > di->ccm.original_iset)) { dev_dbg(di->dev, " Maximization Ibat (%dmA) too high" @@ -730,15 +768,6 @@ static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) return MAXIM_RET_IBAT_TOO_HIGH; } - /* Make sure first we are actually charging.. */ - if (di->charge_status != POWER_SUPPLY_STATUS_CHARGING || - (di->events.usb_cv_active || di->events.ac_cv_active)) { - di->ccm.condition_cnt = di->bat->maxi->wait_cycles; - di->ccm.level = 0; - di->ccm.current_iset = di->ccm.original_iset; - return MAXIM_RET_NOACTION; - } - if (delta_i > di->ccm.test_delta_i && (di->ccm.current_iset + di->ccm.test_delta_i) < di->ccm.max_current) { @@ -756,8 +785,6 @@ static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) di->ccm.level); return MAXIM_RET_CHANGE; } else { - dev_dbg(di->dev, " Maximization, wait for" - " counter, to go %d\n", di->ccm.condition_cnt); return MAXIM_RET_NOACTION; } } else { @@ -1085,7 +1112,19 @@ static int ab8500_chargalg_get_ext_psy_data(struct device *dev, void *data) break; case POWER_SUPPLY_PROP_CURRENT_AVG: - di->batt_data.avg_curr = ret.intval / 1000; + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.avg_curr = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + if (ret.intval) + di->events.vbus_collapsed = true; + else + di->events.vbus_collapsed = false; + break; + default: + break; + } break; case POWER_SUPPLY_PROP_CAPACITY: di->batt_data.percent = ret.intval; @@ -1175,7 +1214,12 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) } /* Main or USB charger not ok. */ else if (di->events.mainextchnotok || di->events.usbchargernotok) { - if (di->charge_state != STATE_CHG_NOT_OK) + /* + * If vbus_collapsed is set, we have to lower the charger + * current, which is done in the normal state below + */ + if (di->charge_state != STATE_CHG_NOT_OK && + !di->events.vbus_collapsed) ab8500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT); } /* VBUS, Main or VBAT OVV. */ @@ -1215,8 +1259,8 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) dev_dbg(di->dev, "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d " "State %s Active_chg %d Chg_status %d AC %d USB %d " - "AC_online %d USB_online %d " - "AC_CV %d USB_CV %d\n", + "AC_online %d USB_online %d AC_CV %d USB_CV %d AC_I %d " + "USB_I %d AC_Vset %d AC_Iset %d USB_Vset %d USB_Iset %d\n", di->batt_data.volt, di->batt_data.avg_curr, di->batt_data.inst_curr, @@ -1231,7 +1275,13 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) di->chg_info.online_chg & AC_CHG, di->chg_info.online_chg & USB_CHG, di->events.ac_cv_active, - di->events.usb_cv_active); + di->events.usb_cv_active, + di->chg_info.ac_curr, + di->chg_info.usb_curr, + di->chg_info.ac_vset, + di->chg_info.ac_iset, + di->chg_info.usb_vset, + di->chg_info.usb_iset); switch (di->charge_state) { case STATE_HANDHELD_INIT: diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index 4aff8d03eae..6c0b8af2f7f 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -151,6 +151,7 @@ struct ab8500_charger_event_flags { bool vbus_ovv; bool usbchargernotok; bool chgwdexp; + bool vbus_collapse; }; struct ab8500_charger_usb_state { @@ -236,6 +237,7 @@ static enum power_supply_property ab8500_charger_ac_props[] = { /* USB properties */ static enum power_supply_property ab8500_charger_usb_props[] = { POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CURRENT_AVG, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_VOLTAGE_NOW, @@ -486,9 +488,22 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, case USB_STAT_ACA_RID_C_NM: di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5; break; + case USB_STAT_RESERVED: + /* + * This state is used to indicate that VBUS has dropped below + * the detection level 4 times in a row. This is due to the + * charger output current is set to high making the charger + * voltage collapse. This have to be propagated through to + * chargalg. This is done using the property + * POWER_SUPPLY_PROP_CURRENT_AVG = 1 + */ + di->flags.vbus_collapse = true; + dev_dbg(di->dev, "USB Type - USB_STAT_RESERVED " + "VBUS has collapsed\n"); + ret = -1; + break; case USB_STAT_HM_IDGND: case USB_STAT_NOT_CONFIGURED: - case USB_STAT_RESERVED: case USB_STAT_NOT_VALID_LINK: dev_err(di->dev, "USB Type - Charging not allowed\n"); di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; @@ -1209,8 +1224,19 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *charger, ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index); - if (ret) + if (ret) { dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + /* Reset the main and usb drop input current measurement counter */ + ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CHARGER_CTRL, + 0x1); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } return ret; } @@ -1513,10 +1539,12 @@ static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work) dev_err(di->dev, "%s ab8500 read failed\n", __func__); return; } - if (reg_value & VBUS_CH_NOK) + if (reg_value & VBUS_CH_NOK) { di->flags.usbchargernotok = true; - else + } else { di->flags.usbchargernotok = false; + di->flags.vbus_collapse = false; + } power_supply_changed(&di->usb_chg.psy); } @@ -1965,6 +1993,16 @@ static int ab8500_charger_usb_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = ab8500_charger_get_usb_current(di) * 1000; break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + /* + * This property is used to indicate when VBUS has collapsed + * due to too high output current from the USB charger + */ + if (di->flags.vbus_collapse) + val->intval = 1; + else + val->intval = 0; + break; default: return -EINVAL; } diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h index 70994f434df..a6dc6e46124 100644 --- a/include/linux/mfd/ab8500/bm.h +++ b/include/linux/mfd/ab8500/bm.h @@ -46,6 +46,7 @@ #define AB8500_LED_INDICATOR_PWM_CTRL 0x53 #define AB8500_LED_INDICATOR_PWM_DUTY 0x54 #define AB8500_BATT_OVV 0x55 +#define AB8500_CHARGER_CTRL 0x56 #define AB8500_BAT_CTRL_CURRENT_SOURCE 0x60 /*Only in Cut2.0*/ /* -- cgit v1.2.3 From 835d401ff4dd31d0f1f5ec83490413d6aaff9506 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 4 May 2011 15:06:19 +0200 Subject: i2c-nomadik: Remove the unnecessary delay The delay in the driver seems to be not needed, so remove it. ST-Ericsson ID: 323012 Change-Id: Id7d3c286063b271bc5f204fb08c289653ba76776 Signed-off-by: Virupax Sadashivpetimath Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17673 Reviewed-by: Markus GRAPE Tested-by: Per PERSSON Tested-by: Chethan Krishna N Reviewed-by: Srinidhi KASAGAR Conflicts: drivers/i2c/busses/i2c-nomadik.c --- drivers/i2c/busses/i2c-nomadik.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index 775924fe96c..ea04acb2f41 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -266,7 +266,6 @@ static int init_hw(struct nmk_i2c_dev *dev) dev->cli.operation = I2C_NO_OPERATION; exit: - udelay(I2C_DELAY); return stat; } -- cgit v1.2.3 From 28fc1b1dd4fd46e5d20b8d344e0a1bb418512aa9 Mon Sep 17 00:00:00 2001 From: Johan Palsson Date: Tue, 8 Mar 2011 13:20:04 +0100 Subject: mfd: ab8500_gpadc: Corrected the name and bitmask for BTempPullUp The bitmask for enabling the BatTemp pull-up was wrong and is corrected. The name is also changed to be inline with the AB8500 register description ST-Ericsson Linux next: - ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ie697c3d5958938dd0c741f63f7424980367f7abc Signed-off-by: Johan Palsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17830 Reviewed-by: Mattias WALLIN --- drivers/mfd/ab8500-gpadc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index 027d5205f23..3da7d2e0808 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -57,7 +57,7 @@ #define SW_AVG_16 0x60 #define ADC_SW_CONV 0x04 #define EN_ICHAR 0x80 -#define BATTEMP_PULL_UP 0x04 +#define BTEMP_PULL_UP 0x08 #define EN_BUF 0x40 #define DIS_ZERO 0x00 #define GPADC_BUSY 0x01 -- cgit v1.2.3 From f4b6623ec2e59ec70bb6c225f209b838279c93f3 Mon Sep 17 00:00:00 2001 From: efanwen Date: Wed, 9 Mar 2011 08:47:26 +0100 Subject: DB Thermal Driver: Add label,min/max_alarm ST-Ericsson ID: CR267428 Change-Id: I848d0a779eb4a3d3719a8d6e5f5448dc122331e0 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17929 Reviewed-by: Wenhai FANG Tested-by: Wenhai FANG Reviewed-by: Martin PERSSON --- drivers/hwmon/db8500.c | 63 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/db8500.c b/drivers/hwmon/db8500.c index e28f9938eb4..b9b4312b8c1 100755 --- a/drivers/hwmon/db8500.c +++ b/drivers/hwmon/db8500.c @@ -47,6 +47,8 @@ struct db8500_temp { unsigned char min[NUM_SENSORS]; unsigned char max[NUM_SENSORS]; unsigned char crit[NUM_SENSORS]; + unsigned char min_alarm[NUM_SENSORS]; + unsigned char max_alarm[NUM_SENSORS]; unsigned short measure_time; struct delayed_work power_off_work; struct mutex lock; @@ -100,6 +102,12 @@ static ssize_t show_name(struct device *dev, struct device_attribute *devattr, return sprintf(buf, "db8500\n"); } +static ssize_t show_label(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "db8500\n"); +} + /* set functions (RW nodes) */ static ssize_t set_min(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) @@ -160,8 +168,7 @@ static ssize_t set_crit(struct device *dev, return res; mutex_lock(&data->lock); - val = (val > 255) ? 0xFF : val; - + val &= 0xFF; data->crit[attr->index - 1] = val; (void)prcmu_config_hotdog(data->crit[attr->index - 1]); mutex_unlock(&data->lock); @@ -169,18 +176,21 @@ static ssize_t set_crit(struct device *dev, return count; } -/* set functions (W nodes) */ +/* start/stop temperature measurement */ static ssize_t start_temp(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { unsigned long val; struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); int res = strict_strtoul(buf, 10, &val); if (res < 0) return res; mutex_lock(&data->lock); data->measure_time = val & 0xFFFF; + data->min_alarm[attr->index - 1] = 0; + data->max_alarm[attr->index - 1] = 0; mutex_unlock(&data->lock); (void)prcmu_start_temp_sense(data->measure_time); @@ -205,7 +215,7 @@ static ssize_t stop_temp(struct device *dev, struct device_attribute *devattr, /* * show functions (RO nodes) - * Notice that min/max/max_hyst refer to millivolts and not millidegrees + * Notice that min/max/crit refer to degrees */ static ssize_t show_min(struct device *dev, struct device_attribute *devattr, char *buf) @@ -234,6 +244,24 @@ static ssize_t show_crit(struct device *dev, return sprintf(buf, "%d\n", data->crit[attr->index - 1]); } +/* Alarms */ +static ssize_t show_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->min_alarm[attr->index - 1]); +} + +static ssize_t show_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct db8500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->max_alarm[attr->index - 1]); +} /*These node are not included in the kernel hwmon sysfs interface */ static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, @@ -241,13 +269,16 @@ static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, set_temp_power_off_delay, 0); /* Chip name, required by hwmon*/ -static SENSOR_DEVICE_ATTR(temp1_start, S_IWUSR, NULL, start_temp, 0); -static SENSOR_DEVICE_ATTR(temp1_stop, S_IWUSR, NULL, stop_temp, 0); static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_start, S_IWUSR, NULL, start_temp, 1); +static SENSOR_DEVICE_ATTR(temp1_stop, S_IWUSR, NULL, stop_temp, 1); static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_crit, set_crit, 1); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); static struct attribute *db8500_temp_attributes[] = { &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, @@ -257,6 +288,9 @@ static struct attribute *db8500_temp_attributes[] = { &sensor_dev_attr_temp1_min.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr, &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, NULL }; @@ -267,7 +301,13 @@ static const struct attribute_group db8500_temp_group = { static irqreturn_t prcmu_hotmon_low_irq_handler(int irq, void *irq_data) { struct platform_device *pdev = irq_data; - sysfs_notify(&pdev->dev.kobj, NULL, "prcmu_hotmon_low alarm"); + struct db8500_temp *data = platform_get_drvdata(pdev); + + mutex_lock(&data->lock); + data->min_alarm[0] = 1; + mutex_unlock(&data->lock); + + sysfs_notify(&pdev->dev.kobj, NULL, "temp1_min_alarm"); dev_dbg(&pdev->dev, "DB8500 thermal low warning\n"); return IRQ_HANDLED; } @@ -278,7 +318,11 @@ static irqreturn_t prcmu_hotmon_high_irq_handler(int irq, void *irq_data) struct platform_device *pdev = irq_data; struct db8500_temp *data = platform_get_drvdata(pdev); - sysfs_notify(&pdev->dev.kobj, NULL, "prcmu_hotmon_high alarm"); + mutex_lock(&data->lock); + data->max_alarm[0] = 1; + mutex_unlock(&data->lock); + + sysfs_notify(&pdev->dev.kobj, NULL, "temp1_max_alarm"); dev_dbg(&pdev->dev, "DB8500 thermal warning, power off in %lu s\n", (data->power_off_delay) / 1000); delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); @@ -338,6 +382,9 @@ static int __devinit db8500_temp_probe(struct platform_device *pdev) for (i = 0; i < NUM_SENSORS; i++) { data->min[i] = 0; data->max[i] = 0xFF; + data->crit[i] = 0xFF; + data->min_alarm[i] = 0; + data->max_alarm[i] = 0; } mutex_init(&data->lock); -- cgit v1.2.3 From 350b8ac5b596d7a30499b9eba08ea5207ce5df64 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 11 Oct 2011 15:21:50 +0200 Subject: drivers: video: add regulator for CVBS out - Adds a regulator that is used for CVBS TV out by AB8500 DENC and AV8100 DENC. The regulator is for the switch at the AV connector that switches between video out and mic in. ST-Ericsson ID: AP 322391 Linux-next: ST-Ericsson ID: ER 282779 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ie7a821b8d3965aa65384b4393e3083ef406c8282 Signed-off-by: Marcel Tunnissen Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/16819 Tested-by: Marcel TUNNISSEN Reviewed-by: Per PERSSON Reviewed-by: Robert FEKETE --- drivers/mfd/ab8500-denc.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/ab8500-denc.c b/drivers/mfd/ab8500-denc.c index 06e4b282cbf..1bf61e41bcd 100644 --- a/drivers/mfd/ab8500-denc.c +++ b/drivers/mfd/ab8500-denc.c @@ -434,6 +434,20 @@ static int debugfs_ab8500_open_file(struct inode *inode, struct file *file) #define DEBUG_BUF_SIZE 900 +#define AB8500_GPIO_DIR5 0x1014 +#define AB8500_GPIO_DIR5_35_SHIFT 2 +#define AB8500_GPIO_DIR5_35_MASK (1 << AB8500_GPIO_DIR5_35_SHIFT) +#define AB8500_GPIO_OUT5 0x1024 +#define AB8500_GPIO_OUT5_35_SHIFT 2 +#define AB8500_GPIO_OUT5_35_MASK (1 << AB8500_GPIO_OUT5_35_SHIFT) +#define AB8500_GPIO_OUT5_35_VIDEO 0 +#define AB8500_GPIO_OUT5_35_AUDIO 1 +#define AB8500_GPIO_NPUD5 0x1034 +#define AB8500_GPIO_NPUD5_35_SHIFT 2 +#define AB8500_GPIO_NPUD5_35_MASK (1 << AB8500_GPIO_NPUD5_35_SHIFT) +#define AB8500_GPIO_NPUD5_35_ACTIVE 0 +#define AB8500_GPIO_NPUD5_35_INACTIVE 1 + static ssize_t debugfs_ab8500_dump_regs(struct file *file, char __user *buf, size_t count, loff_t *f_pos) { @@ -444,12 +458,14 @@ static ssize_t debugfs_ab8500_dump_regs(struct file *file, char __user *buf, data_size += sprintf(buffer + data_size, "AB8500 DENC registers:\n" + "------Regulators etc ----------\n" "CTRL3 : 0x%04x = 0x%02x\n" "SYSULPCLK_CONF: 0x%04x = 0x%02x\n" "SYSCLK_CTRL : 0x%04x = 0x%02x\n" "REGU_MISC1 : 0x%04x = 0x%02x\n" "VAUX12_REGU : 0x%04x = 0x%02x\n" "VAUX1_SEL1 : 0x%04x = 0x%02x\n" + "------TVout only --------------\n" "DENC_CONF0 : 0x%04x = 0x%02x\n" "DENC_CONF1 : 0x%04x = 0x%02x\n" "DENC_CONF2 : 0x%04x = 0x%02x\n" @@ -458,6 +474,10 @@ static ssize_t debugfs_ab8500_dump_regs(struct file *file, char __user *buf, "TVOUT_CTRL : 0x%04x = 0x%02x\n" "TVOUT_CTRL2 : 0x%04x = 0x%02x\n" "IT_MASK1 : 0x%04x = 0x%02x\n" + "------AV connector-------------\n" + "GPIO_DIR5 : 0x%04x = 0x%02x\n" + "GPIO_OUT5 : 0x%04x = 0x%02x\n" + "GPIO_NPUD5 : 0x%04x = 0x%02x\n" , AB8500_CTRL3, ab8500_rreg(dev, AB8500_CTRL3), AB8500_SYS_ULP_CLK_CONF, ab8500_rreg(dev, @@ -473,7 +493,10 @@ static ssize_t debugfs_ab8500_dump_regs(struct file *file, char __user *buf, AB8500_DENC_CONF8, ab8500_rreg(dev, AB8500_DENC_CONF8), AB8500_TVOUT_CTRL, ab8500_rreg(dev, AB8500_TVOUT_CTRL), AB8500_TVOUT_CTRL2, ab8500_rreg(dev, AB8500_TVOUT_CTRL2), - AB8500_IT_MASK1, ab8500_rreg(dev, AB8500_IT_MASK1) + AB8500_IT_MASK1, ab8500_rreg(dev, AB8500_IT_MASK1), + AB8500_GPIO_DIR5, ab8500_rreg(dev, AB8500_GPIO_DIR5), + AB8500_GPIO_OUT5, ab8500_rreg(dev, AB8500_GPIO_OUT5), + AB8500_GPIO_NPUD5, ab8500_rreg(dev, AB8500_GPIO_NPUD5) ); if (data_size >= DEBUG_BUF_SIZE) { printk(KERN_EMERG "AB8500 DENC: Buffer overrun\n"); -- cgit v1.2.3 From a5e4111f1c2812480785b9fc38c04fabf5e8a8af Mon Sep 17 00:00:00 2001 From: Kalle Komierowski Date: Tue, 8 Mar 2011 09:42:20 +0100 Subject: power: ab8500_fg: Adding internal fuel gauge offset calibration This calibration is to compensate for the offset in the fuel gauge ST-Ericsson ID: ER327880 Change-Id: Ibf7e37ea65c4e8da334dcd554da71d442f3cdcb7 Signed-off-by: Kalle Komierowski Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17815 Reviewed-by: Johan PALSSON Tested-by: Karl KOMIEROWSKI Reviewed-by: Jonas ABERG --- drivers/power/ab8500_fg.c | 98 +++++++++++++++++++++++++++++++++++++++---- include/linux/mfd/ab8500/bm.h | 7 ++++ 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index d141f44d2cd..a42ce95d48f 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -84,6 +84,12 @@ static char *charge_state[] = { "CHARGE_READOUT", }; +enum ab8500_fg_calibration_state { + AB8500_FG_CALIB_INIT, + AB8500_FG_CALIB_WAIT, + AB8500_FG_CALIB_END, +}; + struct ab8500_fg_avg_cap { int avg; int samples[NBR_AVG_SAMPLES]; @@ -113,6 +119,7 @@ struct ab8500_fg_flags { bool low_bat; bool bat_ovv; bool batt_unknown; + bool calibrate; }; /** @@ -130,6 +137,7 @@ struct ab8500_fg_flags { * @recovery_needed: Indicate if recovery is needed * @high_curr_mode: Indicate if we're in high current mode * @init_capacity: Indicate if initial capacity measuring should be done + * @calib_state State during offset calibration * @discharge_state: Current discharge state * @charge_state: Current charge state * @flags: Structure for information about events triggered @@ -161,6 +169,7 @@ struct ab8500_fg { bool recovery_needed; bool high_curr_mode; bool init_capacity; + enum ab8500_fg_calibration_state calib_state; enum ab8500_fg_discharge_state discharge_state; enum ab8500_fg_charge_state charge_state; struct ab8500_fg_flags flags; @@ -1073,14 +1082,12 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) case AB8500_FG_DISCHARGE_INIT: /* We use the FG IRQ to work on */ di->init_cnt = 0; - di->fg_samples = SEC_TO_SAMPLE( - di->bat->fg_params->init_timer); + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); ab8500_fg_coulomb_counter(di, true); ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INITMEASURING); /* Intentional fallthrough */ - case AB8500_FG_DISCHARGE_INITMEASURING: /* * Discard a number of samples during startup. @@ -1231,6 +1238,56 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) } } +/** + * ab8500_fg_algorithm_calibrate() - Internal columb counter offset calibration + * @di: pointer to the ab8500_fg structure + * + */ +static void ab8500_fg_algorithm_calibrate(struct ab8500_fg *di) +{ + int ret; + + switch (di->calib_state) { + case AB8500_FG_CALIB_INIT: + dev_dbg(di->dev, "Calibration ongoing...\n"); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_INT_CAL_N_AVG_MASK, CC_INT_CAL_SAMPLES_8); + if (ret < 0) + goto err; + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_INTAVGOFFSET_ENA, CC_INTAVGOFFSET_ENA); + if (ret < 0) + goto err; + di->calib_state = AB8500_FG_CALIB_WAIT; + break; + case AB8500_FG_CALIB_END: + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, + CC_MUXOFFSET, CC_MUXOFFSET); + if (ret < 0) + goto err; + di->flags.calibrate = false; + dev_dbg(di->dev, "Calibration done...\n"); + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + break; + case AB8500_FG_CALIB_WAIT: + dev_dbg(di->dev, "Calibration WFI\n"); + default: + break; + } + return; +err: + /* Something went wrong, don't calibrate then */ + dev_err(di->dev, "failed to calibrate the CC\n"); + di->flags.calibrate = false; + di->calib_state = AB8500_FG_CALIB_INIT; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); +} + /** * ab8500_fg_algorithm() - Entry point for the FG algorithm * @di: pointer to the ab8500_fg structure @@ -1239,10 +1296,14 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) */ static void ab8500_fg_algorithm(struct ab8500_fg *di) { - if (di->flags.charging) - ab8500_fg_algorithm_charging(di); - else - ab8500_fg_algorithm_discharging(di); + if (di->flags.calibrate) + ab8500_fg_algorithm_calibrate(di); + else { + if (di->flags.charging) + ab8500_fg_algorithm_charging(di); + else + ab8500_fg_algorithm_discharging(di); + } dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d " "%d %d %d %d %d %d %d\n", @@ -1282,9 +1343,9 @@ static void ab8500_fg_periodic_work(struct work_struct *work) ab8500_fg_calc_cap_discharge_voltage(di, true); ab8500_fg_check_capacity_limits(di, true); di->init_capacity = false; - } else { + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + } else ab8500_fg_algorithm(di); - } } /** @@ -1338,6 +1399,21 @@ static void ab8500_fg_instant_work(struct work_struct *work) ab8500_fg_algorithm(di); } +/** + * ab8500_fg_cc_convend_handler() - isr to get battery avg current. + * @irq: interrupt number + * @_di: pointer to the ab8500_fg structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab8500_fg_cc_int_calib_handler(int irq, void *_di) +{ + struct ab8500_fg *di = _di; + di->calib_state = AB8500_FG_CALIB_END; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + return IRQ_HANDLED; +} + /** * ab8500_fg_cc_convend_handler() - isr to get battery avg current. * @irq: interrupt number @@ -1690,6 +1766,7 @@ static struct ab8500_fg_interrupts ab8500_fg_irq[] = { {"NCONV_ACCU", ab8500_fg_cc_convend_handler}, {"BATT_OVV", ab8500_fg_batt_ovv_handler}, {"LOW_BAT_F", ab8500_fg_lowbatf_handler}, + {"CC_INT_CALIB", ab8500_fg_cc_int_calib_handler}, }; static int __devinit ab8500_fg_probe(struct platform_device *pdev) @@ -1808,6 +1885,9 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev) platform_set_drvdata(pdev, di); + /* Calibrate the fg first time */ + di->flags.calibrate = true; + di->calib_state = AB8500_FG_CALIB_INIT; /* Run the FG algorithm */ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h index a6dc6e46124..7f5d25f84e0 100644 --- a/include/linux/mfd/ab8500/bm.h +++ b/include/linux/mfd/ab8500/bm.h @@ -192,6 +192,13 @@ #define CC_PWR_UP_ENA 0x01 #define CC_SAMPLES_40 0x28 #define RD_NCONV_ACCU_REQ 0x01 +#define CC_CALIB 0x08 +#define CC_INTAVGOFFSET_ENA 0x10 +#define CC_MUXOFFSET 0x80 +#define CC_INT_CAL_N_AVG_MASK 0x60 +#define CC_INT_CAL_SAMPLES_16 0x40 +#define CC_INT_CAL_SAMPLES_8 0x20 +#define CC_INT_CAL_SAMPLES_4 0x00 /* RTC constants */ #define RTC_BUP_CH_ENA 0x10 -- cgit v1.2.3 From 1ef73d1d822972eb990774d4cab9b91ddaaad4f5 Mon Sep 17 00:00:00 2001 From: Maxime Coquelin Date: Mon, 7 Mar 2011 11:26:02 +0100 Subject: Export SoC info through sysfs Common base to export System-on-Chip related informations through sysfs. Creation of a "socinfo" directory under /sys/. Creation of SoC information entries. Signed-off-by: Maxime COQUELIN Change-Id: I8f9cbccbed07293d26974e9df3bf1102d42e561c Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/18178 Reviewed-by: Linus WALLEIJ --- Documentation/ABI/testing/sysfs-socinfo | 16 +++++++ drivers/base/Kconfig | 3 ++ drivers/base/Makefile | 1 + drivers/base/soc.c | 79 +++++++++++++++++++++++++++++++++ include/linux/sys_soc.h | 50 +++++++++++++++++++++ 5 files changed, 149 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-socinfo create mode 100644 drivers/base/soc.c create mode 100644 include/linux/sys_soc.h diff --git a/Documentation/ABI/testing/sysfs-socinfo b/Documentation/ABI/testing/sysfs-socinfo new file mode 100644 index 00000000000..afd9da2fa76 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-socinfo @@ -0,0 +1,16 @@ +What: /sys/socinfo +Date: March 2011 +contact: Maxime Coquelin +Description: + The /sys/socinfo directory contains information about the + System-on-Chip. It is only available if platform implements it. + This directory contains two kind of attributes : + - common attributes: + * machine: the name of the machine. + * family: the family name of the SoC + - SoC-specific attributes: The SoC vendor can declare attributes + to export some strings to user-space, like the serial-number for + example. + +Users: + User-space applications which needs these kind of attributes. diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 21cf46f4524..95f10c51f23 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -172,6 +172,9 @@ config SYS_HYPERVISOR bool default n +config SYS_SOC + bool + source "drivers/base/regmap/Kconfig" endmenu diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 99a375ad2cc..442251cf83e 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -17,6 +17,7 @@ ifeq ($(CONFIG_SYSFS),y) obj-$(CONFIG_MODULES) += module.o endif obj-$(CONFIG_SYS_HYPERVISOR) += hypervisor.o +obj-$(CONFIG_SYS_SOC) += soc.o obj-$(CONFIG_REGMAP) += regmap/ ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG diff --git a/drivers/base/soc.c b/drivers/base/soc.c new file mode 100644 index 00000000000..046b43bfcdb --- /dev/null +++ b/drivers/base/soc.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Maxime Coquelin for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#include +#include +#include +#include +#include +#include + +struct kobject *soc_object; + +ssize_t show_soc_info(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct sysfs_soc_info *si = container_of(attr, + struct sysfs_soc_info, attr); + + if (si->info) + return sprintf(buf, "%s\n", si->info); + + return si->get_info(buf, si); +} + +int __init register_sysfs_soc_info(struct sysfs_soc_info *info, int nb_info) +{ + int i, ret; + + for (i = 0; i < nb_info; i++) { + ret = sysfs_create_file(soc_object, &info[i].attr.attr); + if (ret) { + for (i -= 1; i >= 0; i--) + sysfs_remove_file(soc_object, &info[i].attr.attr); + break; + } + } + + return ret; +} + +static struct attribute *soc_attrs[] = { + NULL, +}; + +static struct attribute_group soc_attr_group = { + .attrs = soc_attrs, +}; + +int __init register_sysfs_soc(struct sysfs_soc_info *info, size_t num) +{ + int ret; + + soc_object = kobject_create_and_add("socinfo", NULL); + if (!soc_object) { + ret = -ENOMEM; + goto exit; + } + + ret = sysfs_create_group(soc_object, &soc_attr_group); + if (ret) + goto kset_exit; + + ret = register_sysfs_soc_info(info, num); + if (ret) + goto group_exit; + + return 0; + +group_exit: + sysfs_remove_group(soc_object, &soc_attr_group); +kset_exit: + kobject_put(soc_object); +exit: + return ret; +} + diff --git a/include/linux/sys_soc.h b/include/linux/sys_soc.h new file mode 100644 index 00000000000..05e5529a6aa --- /dev/null +++ b/include/linux/sys_soc.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Maxime Coquelin for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ +#ifndef __SYS_SOC_H +#define __SYS_SOC_H + +#include + +/** + * struct sys_soc_info - SoC exports related informations + * @name: name of the export + * @info: pointer on the key to export + * @get_info: callback to retrieve key if info field is NULL + * @attr: export's sysdev class attribute + */ +struct sysfs_soc_info { + const char *info; + ssize_t (*get_info)(char *buf, struct sysfs_soc_info *); + struct kobj_attribute attr; +}; + +ssize_t show_soc_info(struct kobject *, struct kobj_attribute *, char *); + +#define SYSFS_SOC_ATTR_VALUE(_name, _value) { \ + .attr.attr.name = _name, \ + .attr.attr.mode = S_IRUGO, \ + .attr.show = show_soc_info, \ + .info = _value, \ +} + +#define SYSFS_SOC_ATTR_CALLBACK(_name, _callback) { \ + .attr.attr.name = _name, \ + .attr.attr.mode = S_IRUGO, \ + .attr.show = show_soc_info, \ + .get_info = _callback, \ +} + +/** + * register_sys_soc - register the soc information + * @name: name of the machine + * @info: pointer on the info table to export + * @num: number of info to export + * + * NOTE: This function must only be called once + */ +int register_sysfs_soc(struct sysfs_soc_info *info, size_t num); + +#endif /* __SYS_SOC_H */ -- cgit v1.2.3 From de1c516936838d2f1f16d77a1212311861d514ad Mon Sep 17 00:00:00 2001 From: Mikael Larsson Date: Fri, 11 Mar 2011 15:56:34 +0100 Subject: setlocalversion: Add support for version from specific tag This patch makes it possible to specify a tag as input to setlocalversion. If specified the version is then calculated from this tag and not the latest tag as when not using this option. ST-Ericsson ID: None ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I168546efeb95978b53a5f7de992ddc955be26477 Signed-off-by: Mikael Larsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/18195 Reviewed-by: Robert ROSENGREN Reviewed-by: Jonas ABERG --- Makefile | 2 +- scripts/setlocalversion | 42 ++++++++++++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 07bc92544e9..c68a0a084bf 100644 --- a/Makefile +++ b/Makefile @@ -947,7 +947,7 @@ $(vmlinux-dirs): prepare scripts # Store (new) KERNELRELASE string in include/config/kernel.release include/config/kernel.release: include/config/auto.conf FORCE $(Q)rm -f $@ - $(Q)echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))" > $@ + $(Q)echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion -s $(srctree) -t v$(KERNELVERSION))" > $@ # Things we need to do before we recursively start building the kernel diff --git a/scripts/setlocalversion b/scripts/setlocalversion index 4d403844e13..06c33adfe7f 100755 --- a/scripts/setlocalversion +++ b/scripts/setlocalversion @@ -10,23 +10,37 @@ # usage() { - echo "Usage: $0 [--save-scmversion] [srctree]" >&2 + echo "Usage: $0 [--save-scmversion] [-s srctree] [-t ref_tag]" >&2 exit 1 } scm_only=false srctree=. -if test "$1" = "--save-scmversion"; then - scm_only=true - shift -fi -if test $# -gt 0; then - srctree=$1 +match_option=--exact-match + +while [ $# -ne 0 ]; do + if test "$1" = "--save-scmversion"; then + scm_only=true + elif test "$1" = "-s"; then + shift + if test $# -ne 0 -a -d "$1"; then + srctree=$1 + else + usage + fi + elif test "$1" = "-t"; then + shift + if [ $# -ne 0 ]; then + match=" --tags --match "$1 + rev_refs="--refs refs/tags/"$1 + else + usage + fi + else + usage + fi shift -fi -if test $# -gt 0 -o ! -d "$srctree"; then - usage -fi +done scm_version() { @@ -47,8 +61,8 @@ scm_version() # If we are at a tagged commit (like "v2.6.30-rc6"), we ignore # it, because this version is defined in the top level Makefile. - if [ -z "`git describe --exact-match 2>/dev/null`" ]; then - + if git name-rev --tags $rev_refs HEAD | \ + grep -E '^HEAD[[:space:]]+(.*~[0-9]*|undefined)$' > /dev/null; then # If only the short version is requested, don't bother # running further git commands if $short; then @@ -57,7 +71,7 @@ scm_version() fi # If we are past a tagged commit (like # "v2.6.30-rc5-302-g72357d5"), we pretty print it. - if atag="`git describe 2>/dev/null`"; then + if atag="`git describe $match 2>/dev/null`"; then echo "$atag" | awk -F- '{printf("-%05d-%s", $(NF-1),$(NF))}' # If we don't have a tag at all we print -g{commitish}. -- cgit v1.2.3 From 7aa4d648a5b900a932a3a7cd1381e328cc84f7fd Mon Sep 17 00:00:00 2001 From: Chethan Krishna N Date: Tue, 15 Mar 2011 11:58:24 +0530 Subject: bh1780gli: correct mutex locking behaviour Multiple activation and deactivation calls for BH1780GLI Ambient Light Sensor will not crash now. ST Ericsson ID: ER326054 Signed-off-by: Chethan Krishna N Change-Id: I3bfeb6a371ae567e14300339b1b18dc750a59b14 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/18319 Reviewed-by: Srinidhi KASAGAR --- drivers/misc/bh1780gli.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/bh1780gli.c b/drivers/misc/bh1780gli.c index 1e495bfd7a7..0a731371fb5 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -115,11 +115,11 @@ static ssize_t bh1780_store_power_state(struct device *dev, if (val < BH1780_POFF || val > BH1780_PON) return -EINVAL; - mutex_lock(&ddata->lock); - if (ddata->power_state == val) return count; + mutex_lock(&ddata->lock); + if (ddata->power_state == BH1780_POFF) regulator_enable(ddata->regulator); -- cgit v1.2.3 From 9b5a39c4e7dc7316792e16a239eaba135d5a9988 Mon Sep 17 00:00:00 2001 From: Johan Palsson Date: Wed, 16 Mar 2011 13:25:10 +0100 Subject: power: ab8500_bm: Lower current when charger voltage drops The charger current optimization loop has been corrected for when the VBUS voltage collapses ST-Ericsson Linux next: - ST-Ericsson ID: ER328139 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ie997402e3c0b868212ad59fe242f393ba0721007 Signed-off-by: Johan Palsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/18415 Reviewed-by: Karl KOMIEROWSKI Reviewed-by: Jonas ABERG --- drivers/power/ab8500_chargalg.c | 8 ++++--- drivers/power/ab8500_charger.c | 48 ++++++++++++++++------------------------- include/linux/mfd/ab8500/bm.h | 13 +++++++++++ 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/drivers/power/ab8500_chargalg.c b/drivers/power/ab8500_chargalg.c index f4e4528b15c..788d81aa7cd 100644 --- a/drivers/power/ab8500_chargalg.c +++ b/drivers/power/ab8500_chargalg.c @@ -733,9 +733,11 @@ static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) delta_i = di->ccm.original_iset - di->batt_data.inst_curr; if (di->events.vbus_collapsed) { - dev_dbg(di->dev, "Charger voltage has collapsed\n"); - if (di->ccm.wait_cnt++ == 0) { + dev_dbg(di->dev, "Charger voltage has collapsed %d\n", + di->ccm.wait_cnt); + if (di->ccm.wait_cnt == 0) { dev_dbg(di->dev, "lowering current\n"); + di->ccm.wait_cnt++; di->ccm.condition_cnt = di->bat->maxi->wait_cycles; di->ccm.max_current = di->ccm.current_iset - di->ccm.test_delta_i; @@ -745,7 +747,7 @@ static enum maxim_ret ab8500_chargalg_chg_curr_maxim(struct ab8500_chargalg *di) } else { dev_dbg(di->dev, "waiting\n"); /* Let's go in here twice before lowering curr again */ - di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 4; + di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 3; return MAXIM_RET_NOACTION; } } diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index 6c0b8af2f7f..47be3e089ff 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -181,13 +181,13 @@ struct ab8500_charger_usb_state { * @usb: Structure that holds the USB charger properties * @charger_wq: Work queue for the IRQs and checking HW state * @check_hw_failure_work: Work for checking HW state + * @check_usbchgnotok_work: Work for checking USB charger not ok status * @kick_wd_work: Work for kicking the charger watchdog in case * of ABB rev 1.* due to the watchog logic bug * @ac_work: Work for checking AC charger connection * @detect_usb_type_work: Work for detecting the USB type connected * @usb_link_status_work: Work for checking the new USB link status * @usb_state_changed_work: Work for checking USB state - * @check_usbchgnotok_work: Work for checking USB charger not ok status * @check_main_thermal_prot_work: * Work for checking Main thermal status * @check_usb_thermal_prot_work: @@ -212,12 +212,12 @@ struct ab8500_charger { struct ab8500_charger_info usb; struct workqueue_struct *charger_wq; struct delayed_work check_hw_failure_work; + struct delayed_work check_usbchgnotok_work; struct delayed_work kick_wd_work; struct work_struct ac_work; struct work_struct detect_usb_type_work; struct work_struct usb_link_status_work; struct work_struct usb_state_changed_work; - struct work_struct check_usbchgnotok_work; struct work_struct check_main_thermal_prot_work; struct work_struct check_usb_thermal_prot_work; struct otg_transceiver *otg; @@ -1528,9 +1528,10 @@ static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work) { int ret; u8 reg_value; + bool prev_status; struct ab8500_charger *di = container_of(work, - struct ab8500_charger, check_usbchgnotok_work); + struct ab8500_charger, check_usbchgnotok_work.work); /* Check if the status bit for usbchargernotok is still active */ ret = abx500_get_register_interruptible(di->dev, @@ -1539,14 +1540,20 @@ static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work) dev_err(di->dev, "%s ab8500 read failed\n", __func__); return; } + prev_status = di->flags.usbchargernotok; + if (reg_value & VBUS_CH_NOK) { di->flags.usbchargernotok = true; + /* Check again in 1sec */ + queue_delayed_work(di->charger_wq, + &di->check_usbchgnotok_work, HZ); } else { di->flags.usbchargernotok = false; di->flags.vbus_collapse = false; } - power_supply_changed(&di->usb_chg.psy); + if (prev_status != di->flags.usbchargernotok) + power_supply_changed(&di->usb_chg.psy); } /** @@ -1793,23 +1800,6 @@ static irqreturn_t ab8500_charger_usbchthprotf_handler(int irq, void *_di) return IRQ_HANDLED; } -/** - * ab8500_charger_usbchargernotokf_handler() - USB charger ok detected - * @irq: interrupt number - * @_di: pointer to the ab8500_charger structure - * - * Returns IRQ status(IRQ_HANDLED) - */ -static irqreturn_t ab8500_charger_usbchargernotokf_handler(int irq, void *_di) -{ - struct ab8500_charger *di = _di; - - dev_dbg(di->dev, "Allowed USB charger detected\n"); - queue_work(di->charger_wq, &di->check_usbchgnotok_work); - - return IRQ_HANDLED; -} - /** * ab8500_charger_usbchargernotokr_handler() - USB charger not ok detected * @irq: interrupt number @@ -1822,7 +1812,7 @@ static irqreturn_t ab8500_charger_usbchargernotokr_handler(int irq, void *_di) struct ab8500_charger *di = _di; dev_dbg(di->dev, "Not allowed USB charger detected\n"); - queue_work(di->charger_wq, &di->check_usbchgnotok_work); + queue_delayed_work(di->charger_wq, &di->check_usbchgnotok_work, 0); return IRQ_HANDLED; } @@ -2026,6 +2016,7 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) case AB8500_CUT1P1: break; case AB8500_CUT2P0: + default: ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, AB8500_CH_VOLT_LVL_MAX_REG, CH_VOL_LVL_4P6); @@ -2045,14 +2036,13 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) } break; - default: - goto out; } - /* VBUS OVV set to 6.3V */ + /* VBUS OVV set to 6.3V and enable automatic current limitiation */ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_USBCH_CTRL2_REG, 0x78); + AB8500_USBCH_CTRL2_REG, + VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA); if (ret) { dev_err(di->dev, "failed to set VBUS OVV\n"); goto out; @@ -2146,7 +2136,6 @@ static struct ab8500_charger_interrupts ab8500_charger_irq[] = { {"USB_LINK_STATUS", ab8500_charger_usblinkstatus_handler}, {"USB_CH_TH_PROT_R", ab8500_charger_usbchthprotr_handler}, {"USB_CH_TH_PROT_F", ab8500_charger_usbchthprotf_handler}, - {"USB_CHARGER_NOT_OKF", ab8500_charger_usbchargernotokf_handler}, {"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler}, {"VBUS_OVV", ab8500_charger_vbusovv_handler}, {"CH_WD_EXP", ab8500_charger_chwdexp_handler}, @@ -2360,6 +2349,9 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) /* Init work for HW failure check */ INIT_DELAYED_WORK_DEFERRABLE(&di->check_hw_failure_work, ab8500_charger_check_hw_failure_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->check_usbchgnotok_work, + ab8500_charger_check_usbchargernotok_work); + /* * For ABB revision 1.0 and 1.1 there is a bug in the watchdog @@ -2384,8 +2376,6 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) ab8500_charger_usb_state_changed_work); /* Init work for checking HW status */ - INIT_WORK(&di->check_usbchgnotok_work, - ab8500_charger_check_usbchargernotok_work); INIT_WORK(&di->check_main_thermal_prot_work, ab8500_charger_check_main_thermal_prot_work); INIT_WORK(&di->check_usb_thermal_prot_work, diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h index 7f5d25f84e0..068b63a93ce 100644 --- a/include/linux/mfd/ab8500/bm.h +++ b/include/linux/mfd/ab8500/bm.h @@ -185,6 +185,19 @@ #define BATT_OVV_TH_3P7 0x00 #define BATT_OVV_TH_4P75 0x01 +/* VBUS OVV constants */ +#define VBUS_OVV_SELECT_MASK 0x78 +#define VBUS_OVV_SELECT_5P6V 0x00 +#define VBUS_OVV_SELECT_5P7V 0x08 +#define VBUS_OVV_SELECT_5P8V 0x10 +#define VBUS_OVV_SELECT_5P9V 0x18 +#define VBUS_OVV_SELECT_6P0V 0x20 +#define VBUS_OVV_SELECT_6P1V 0x28 +#define VBUS_OVV_SELECT_6P2V 0x30 +#define VBUS_OVV_SELECT_6P3V 0x38 + +#define VBUS_AUTO_IN_CURR_LIM_ENA 0x04 + /* Fuel Gauge constants */ #define RESET_ACCU 0x02 #define READ_REQ 0x01 -- cgit v1.2.3 From eada65a6912c17b01e69ad8d6e0db4fd9c053066 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 19 Oct 2011 10:52:57 +0200 Subject: power: ab8500_charger: Keep VDD ADC supply enabled if charger is connected Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts will be triggered everytime we enable the VDD ADC supply. This will turn off charging for a short while. It will be avoided by having the regulator on when there is a charger connected. ST-Ericsson Linux next: - ST-Ericsson ID: ER329557 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I2a084945430f1e0a996526a194d898db13c54a29 Signed-off-by: Johan Palsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/18610 Reviewed-by: Jonas ABERG --- drivers/power/ab8500_charger.c | 58 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index 47be3e089ff..96859456ef7 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include #include #include @@ -169,6 +171,7 @@ struct ab8500_charger_usb_state { * @vbus_detected_start: * VBUS detected during startup * @ac_conn: This will be true when the AC charger has been plugged + * @vddadc_en: Indicate if VDD ADC supply is enabled from this driver * @parent: Pointer to the struct ab8500 * @gpadc: Pointer to the struct gpadc * @pdata: Pointer to the ab8500_charger platform data @@ -179,6 +182,7 @@ struct ab8500_charger_usb_state { * @usb_chg: USB charger power supply * @ac: Structure that holds the AC charger properties * @usb: Structure that holds the USB charger properties + * @regu: Pointer to the struct regulator * @charger_wq: Work queue for the IRQs and checking HW state * @check_hw_failure_work: Work for checking HW state * @check_usbchgnotok_work: Work for checking USB charger not ok status @@ -200,6 +204,7 @@ struct ab8500_charger { bool vbus_detected; bool vbus_detected_start; bool ac_conn; + bool vddadc_en; struct ab8500 *parent; struct ab8500_gpadc *gpadc; struct ab8500_charger_platform_data *pdata; @@ -210,6 +215,7 @@ struct ab8500_charger { struct ux500_charger usb_chg; struct ab8500_charger_info ac; struct ab8500_charger_info usb; + struct regulator *regu; struct workqueue_struct *charger_wq; struct delayed_work check_hw_failure_work; struct delayed_work check_usbchgnotok_work; @@ -419,7 +425,7 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di) AB8500_CH_STATUS1_REG, &val); if (ret < 0) { dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return ret; + goto out; } if (val & MAIN_CH_DET) @@ -430,13 +436,39 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di) AB8500_CH_USBCH_STAT1_REG, &val); if (ret < 0) { dev_err(di->dev, "%s ab8500 read failed\n", __func__); - return ret; + goto out; } if (val & (VBUS_DET_DBNC100 | VBUS_DET_DBNC1)) result |= USB_PW_CONN; + /* + * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts + * will be triggered everytime we enable the VDD ADC supply. + * This will turn off charging for a short while. + * It can be avoided by having the supply on when + * there is a charger connected. Normally the VDD ADC supply + * is enabled everytime a GPADC conversion is triggered. We will + * force it to be enabled from this driver to have + * the GPADC module independant of the AB8500 chargers + */ + if (result == NO_PW_CONN && di->vddadc_en) { + regulator_disable(di->regu); + di->vddadc_en = false; + } else if ((result & AC_PW_CONN || result & USB_PW_CONN) && + !di->vddadc_en) { + regulator_enable(di->regu); + di->vddadc_en = true; + } + return result; + +out: + if (di->vddadc_en) { + regulator_disable(di->regu); + di->vddadc_en = false; + } + return ret; } /** @@ -2247,6 +2279,9 @@ static int __devexit ab8500_charger_remove(struct platform_device *pdev) free_irq(irq, di); } + /* disable the regulator */ + regulator_put(di->regu); + /* Backup battery voltage and current disable */ ret = abx500_mask_and_set_register_interruptible(di->dev, AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0); @@ -2390,18 +2425,31 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) di->chip_id = ret; dev_dbg(di->dev, "AB8500 CID is: 0x%02x\n", di->chip_id); + /* + * VDD ADC supply needs to be enabled from this driver when there + * is a charger connected to avoid erroneous BTEMP_HIGH/LOW + * interrupts during charging + */ + di->regu = regulator_get(di->dev, "vddadc"); + if (IS_ERR(di->regu)) { + ret = PTR_ERR(di->regu); + dev_err(di->dev, "failed to get vddadc regulator\n"); + goto free_charger_wq; + } + + /* Initialize OVV, and other registers */ ret = ab8500_charger_init_hw_registers(di); if (ret) { dev_err(di->dev, "failed to initialize ABB registers\n"); - goto free_charger_wq; + goto free_regulator; } /* Register AC charger class */ ret = power_supply_register(di->dev, &di->ac_chg.psy); if (ret) { dev_err(di->dev, "failed to register AC charger\n"); - goto free_charger_wq; + goto free_regulator; } /* Register USB charger class */ @@ -2474,6 +2522,8 @@ free_usb: power_supply_unregister(&di->usb_chg.psy); free_ac: power_supply_unregister(&di->ac_chg.psy); +free_regulator: + regulator_put(di->regu); free_charger_wq: destroy_workqueue(di->charger_wq); free_device_info: -- cgit v1.2.3 From e3cf2a8d6a4aee277449a0e1c3b1d72a56abd87a Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 18 Oct 2011 14:29:36 +0200 Subject: ab5500: fixes and basic irq support - Remove old kernel version compat code - Make some functions static - Move to irq chip bus lock - Fix broken irq handler registration - Fix interrupt count - Use direct interrupt instead of via unimplemented PRCMU - Add RTC interrupts - Fix RTC register list - Allow get_page to read more than 4 registers ST-Ericsson Linux next: - ST-Ericsson ID: WP257121 ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/18936 Reviewed-by: Bibek BASU Reviewed-by: Srinidhi KASAGAR --- drivers/mfd/ab5500-core.c | 345 ++++++++++++++++++---------------------------- 1 file changed, 133 insertions(+), 212 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 37bd215538f..2c4451ea5aa 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -48,6 +47,11 @@ #define AB5500_MASK_END (0x79) #define AB5500_CHIP_ID (0x20) +#define AB5500_IT_LATCH0_REG 0x40 +#define AB5500_IT_MASK0_REG 0x60 +#define AB5500_NUM_IRQ_REGS 23 +#define AB5500_NR_IRQS (23 * 8) + /** * struct ab5500 * @access_mutex: lock out concurrent accesses to the AB registers @@ -56,8 +60,7 @@ * @irq_base: the platform configuration irq base for subdevices * @chip_name: name of this chip variant * @chip_id: 8 bit chip ID for this chip variant - * @mask_work: a worker for writing to mask registers - * @event_lock: a lock to protect the event_mask + * @irq_lock: a lock to protect the mask * @abb_events: a local bit mask of the prcmu wakeup events * @event_mask: a local copy of the mask event registers * @last_event_mask: a copy of the last event_mask written to hardware @@ -71,15 +74,11 @@ struct ab5500 { unsigned int irq_base; char chip_name[32]; u8 chip_id; - struct work_struct mask_work; -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) - struct work_struct irq_work; -#endif - spinlock_t event_lock; + struct mutex irq_lock; u32 abb_events; - u8 event_mask[AB5500_NUM_EVENT_REG]; - u8 last_event_mask[AB5500_NUM_EVENT_REG]; - u8 startup_events[AB5500_NUM_EVENT_REG]; + u8 mask[AB5500_NUM_IRQ_REGS]; + u8 oldmask[AB5500_NUM_IRQ_REGS]; + u8 startup_events[AB5500_NUM_IRQ_REGS]; bool startup_events_read; #ifdef CONFIG_DEBUG_FS unsigned int debug_bank; @@ -250,7 +249,7 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { .nbanks = 1, .bank = (struct ab5500_i2c_ranges[]) { { - .bankid = AB5500_BANK_LED, + .bankid = AB5500_BANK_RTC, .nranges = 1, .range = (struct ab5500_reg_range[]) { { @@ -351,6 +350,8 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { }, }; +#define AB5500_IRQ(bank, bit) ((bank) * 8 + (bit)) + /* I appologize for the resource names beeing a mix of upper case * and lower case but I want them to be exact as the documentation */ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { @@ -382,6 +383,15 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { [AB5500_DEVID_RTC] = { .name = "ab5500-rtc", .id = AB5500_DEVID_RTC, + .num_resources = 1, + .resources = (struct resource[]) { + { + .name = "RTC_Alarm", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 7), + .end = AB5500_IRQ(1, 7), + } + }, }, [AB5500_DEVID_CHARGER] = { .name = "ab5500-charger", @@ -868,17 +878,25 @@ static int get_register_page_interruptible(struct ab5500 *ab, u8 bank, if (bank >= AB5500_NUM_BANKS) return -EINVAL; - /* The hardware limit for get page is 4 */ - if (numregs > 4) - return -EINVAL; - err = mutex_lock_interruptible(&ab->access_mutex); if (err) return err; - err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, first_reg, - regvals, numregs); + while (numregs) { + /* The hardware limit for get page is 4 */ + u8 curnum = min_t(u8, numregs, 4u); + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, + first_reg, regvals, curnum); + if (err) + goto out; + + numregs -= curnum; + first_reg += curnum; + regvals += curnum; + } + +out: mutex_unlock(&ab->access_mutex); return err; } @@ -917,6 +935,12 @@ static int mask_and_set_register_interruptible(struct ab5500 *ab, u8 bank, return err; } +static int +set_register_interruptible(struct ab5500 *ab, u8 bank, u8 reg, u8 value) +{ + return mask_and_set_register_interruptible(ab, bank, reg, 0xff, value); +} + /* * Read/write permission checking functions. */ @@ -999,15 +1023,15 @@ static bool reg_read_allowed(u8 devid, u8 bank, u8 reg) /* * The exported register access functionality. */ -int ab5500_get_chip_id(struct device *dev) +static int ab5500_get_chip_id(struct device *dev) { struct ab5500 *ab = dev_get_drvdata(dev->parent); return (int)ab->chip_id; } -int ab5500_mask_and_set_register_interruptible(struct device *dev, u8 bank, - u8 reg, u8 bitmask, u8 bitvalues) +static int ab5500_mask_and_set_register_interruptible(struct device *dev, + u8 bank, u8 reg, u8 bitmask, u8 bitvalues) { struct ab5500 *ab; struct platform_device *pdev = to_platform_device(dev); @@ -1021,15 +1045,15 @@ int ab5500_mask_and_set_register_interruptible(struct device *dev, u8 bank, bitmask, bitvalues); } -int ab5500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, - u8 value) +static int ab5500_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 value) { return ab5500_mask_and_set_register_interruptible(dev, bank, reg, 0xFF, value); } -int ab5500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, - u8 *value) +static int ab5500_get_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 *value) { struct ab5500 *ab; struct platform_device *pdev = to_platform_device(dev); @@ -1042,8 +1066,8 @@ int ab5500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, return get_register_interruptible(ab, bank, reg, value); } -int ab5500_get_register_page_interruptible(struct device *dev, u8 bank, - u8 first_reg, u8 *regvals, u8 numregs) +static int ab5500_get_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) { struct ab5500 *ab; struct platform_device *pdev = to_platform_device(dev); @@ -1058,7 +1082,8 @@ int ab5500_get_register_page_interruptible(struct device *dev, u8 bank, numregs); } -int ab5500_event_registers_startup_state_get(struct device *dev, u8 *event) +static int +ab5500_event_registers_startup_state_get(struct device *dev, u8 *event) { struct ab5500 *ab; @@ -1070,7 +1095,7 @@ int ab5500_event_registers_startup_state_get(struct device *dev, u8 *event) return 0; } -int ab5500_startup_irq_enabled(struct device *dev, unsigned int irq) +static int ab5500_startup_irq_enabled(struct device *dev, unsigned int irq) { struct ab5500 *ab; bool val; @@ -1094,102 +1119,35 @@ static struct abx500_ops ab5500_ops = { .startup_irq_enabled = ab5500_startup_irq_enabled, }; -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) -static irqreturn_t ab5500_irq_handler(int irq, void *data) +static irqreturn_t ab5500_irq(int irq, void *data) { struct ab5500 *ab = data; + u8 i; /* - * Disable the IRQ and dispatch a worker to handle the - * event. Since the chip resides on I2C this is slow - * stuff and we will re-enable the interrupts once the - * worker has finished. + * TODO: use the ITMASTER registers to reduce the number of i2c reads. */ - disable_irq_nosync(irq); - schedule_work(&ab->irq_work); - return IRQ_HANDLED; -} - -static void ab5500_irq_work(struct work_struct *work) -{ - struct ab5500 *ab = container_of(work, struct ab5500, irq_work); - u8 i; - u8 *e = 0; - u8 events[AB5500_NUM_EVENT_REG]; - unsigned long flags; - prcmu_get_abb_event_buf(&e); - - spin_lock_irqsave(&ab->event_lock, flags); - for (i = 0; i < AB5500_NUM_EVENT_REG; i++) - events[i] = e[i] & ~ab->event_mask[i]; - spin_unlock_irqrestore(&ab->event_lock, flags); - - local_irq_disable(); for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { - u8 bit; - u8 event_reg; - - dev_dbg(ab->dev, "IRQ Event[%d]: 0x%2x\n", - i, events[i]); - - event_reg = events[i]; - for (bit = 0; event_reg; bit++, event_reg /= 2) { - if (event_reg % 2) { - unsigned int irq; - struct irq_desc *desc; - - irq = ab->irq_base + (i * 8) + bit; - desc = irq_to_desc(irq); - if (desc->status & IRQ_DISABLED) - note_interrupt(irq, desc, IRQ_NONE); - else - desc->handle_irq(irq, desc); - } - } - } - local_irq_enable(); - /* By now the IRQ should be acked and deasserted so enable it again */ - enable_irq(ab->ab5500_irq); -} - -#else - -static irqreturn_t ab5500_irq_handler(int irq, void *data) -{ - struct ab5500 *ab = data; - u8 i; - u8 *e = 0; - u8 events[AB5500_NUM_EVENT_REG]; - - prcmu_get_abb_event_buf(&e); + int status; + u8 value; - spin_lock(&ab->event_lock); - for (i = 0; i < AB5500_NUM_EVENT_REG; i++) - events[i] = e[i] & ~ab->event_mask[i]; - spin_unlock(&ab->event_lock); - - for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { - u8 bit; - u8 event_reg; + status = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + i, &value); + if (status < 0 || value == 0) + continue; - dev_dbg(ab->dev, "IRQ Event[%d]: 0x%2x\n", - i, events[i]); + do { + int bit = __ffs(value); + int line = i * 8 + bit; - event_reg = events[i]; - for (bit = 0; event_reg; bit++, event_reg /= 2) { - if (event_reg % 2) { - unsigned int irq; - - irq = ab->irq_base + (i * 8) + bit; - generic_handle_irq(irq); - } - } + handle_nested_irq(ab->irq_base + line); + value &= ~(1 << bit); + } while (value); } return IRQ_HANDLED; } -#endif #ifdef CONFIG_DEBUG_FS static struct ab5500_i2c_ranges debug_ranges[AB5500_NUM_BANKS] = { @@ -1854,7 +1812,7 @@ static int __init ab5500_setup(struct ab5500 *ab, if ((settings[i].bank == AB5500_BANK_IT) && (AB5500_MASK_BASE <= settings[i].reg) && (settings[i].reg <= AB5500_MASK_END)) { - ab->event_mask[settings[i].reg - AB5500_MASK_BASE] = + ab->mask[settings[i].reg - AB5500_MASK_BASE] = settings[i].setting; } } @@ -1862,97 +1820,61 @@ exit_no_setup: return err; } -static void ab5500_mask_work(struct work_struct *work) +static void ab5500_irq_mask(struct irq_data *data) { - struct ab5500 *ab = container_of(work, struct ab5500, mask_work); - int i; - int err; - unsigned long flags; - u8 mask[AB5500_NUM_EVENT_REG]; - int call_prcmu_event_readout = 0; + struct ab5500 *ab = irq_data_get_irq_chip_data(data); + int offset = data->irq - ab->irq_base; + int index = offset / 8; + int mask = BIT(offset % 8); - spin_lock_irqsave(&ab->event_lock, flags); - for (i = 0; i < AB5500_NUM_EVENT_REG; i++) - mask[i] = ab->event_mask[i]; - spin_unlock_irqrestore(&ab->event_lock, flags); - - for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { - if (mask[i] != ab->last_event_mask[i]) { - err = mask_and_set_register_interruptible(ab, 0, - (AB5500_MASK_BASE + i), ~0, mask[i]); - if (err) { - dev_err(ab->dev, - "ab5500_mask_work failed 0x%x,0x%x\n", - (AB5500_MASK_BASE + i), mask[i]); - break; - } + ab->mask[index] |= mask; +} - if (mask[i] == 0xFF) { - ab->abb_events &= ~BIT(i); - call_prcmu_event_readout = 1; - } else { - ab->abb_events |= BIT(i); - if (ab->last_event_mask[i] == 0xFF) - call_prcmu_event_readout = 1; - } +static void ab5500_irq_unmask(struct irq_data *data) +{ + struct ab5500 *ab = irq_data_get_irq_chip_data(data); + int offset = data->irq - ab->irq_base; + int index = offset / 8; + int mask = BIT(offset % 8); - ab->last_event_mask[i] = mask[i]; - } - } - if (call_prcmu_event_readout) { - err = db5500_prcmu_config_abb_event_readout(ab->abb_events); - if (err) - dev_err(ab->dev, - "prcmu_config_abb_event_readout failed\n"); - } + ab->mask[index] &= ~mask; } -static void ab5500_mask(struct irq_data *data) +static void ab5500_irq_lock(struct irq_data *data) { - unsigned long flags; - struct ab5500 *ab; - int irq; - - ab = irq_data_get_irq_chip_data(data); - irq = data->irq - ab->irq.base; - - spin_lock_irqsave(&ab->event_lock, flags); - ab->event_mask[irq / 8] |= BIT(irq % 8); - spin_unlock_irqrestore(&ab->event_lock, flags); + struct ab5500 *ab = irq_data_get_irq_chip_data(data); - schedule_work(&ab->mask_work); + mutex_lock(&ab->irq_lock); } -static void ab5500_unmask(struct irq_data *data) +static void ab5500_irq_sync_unlock(struct irq_data *data) { - unsigned long flags; - struct ab5500 *ab; - int irq; - - ab = irq_data_get_irq_chip_data(data); - irq = data->irq - ab->irq.base; + struct ab5500 *ab = irq_data_get_irq_chip_data(data); + int i; - spin_lock_irqsave(&ab->event_lock, flags); - ab->event_mask[irq / 8] &= ~BIT(irq % 8); - spin_unlock_irqrestore(&ab->event_lock, flags); + for (i = 0; i < AB5500_NUM_IRQ_REGS; i++) { + u8 old = ab->oldmask[i]; + u8 new = ab->mask[i]; + int reg; - schedule_work(&ab->mask_work); -} + if (new == old) + continue; -static void noop(unsigned int irq) -{ + ab->oldmask[i] = new; + + reg = AB5500_IT_MASK0_REG + i; + set_register_interruptible(ab, AB5500_BANK_IT, reg, new); + } + + mutex_unlock(&ab->irq_lock); } static struct irq_chip ab5500_irq_chip = { - .name = "ab5500-core", /* Keep the same name as the request */ - .startup = NULL, /* defaults to enable */ - .shutdown = NULL, /* defaults to disable */ - .enable = NULL, /* defaults to unmask */ - .disable = ab5500_mask, /* No default to mask in chip.c */ - .ack = noop, - .mask = ab5500_mask, - .unmask = ab5500_unmask, - .end = NULL, + .name = "ab5500", + .irq_mask = ab5500_irq_mask, + .irq_unmask = ab5500_irq_unmask, + .irq_bus_lock = ab5500_irq_lock, + .irq_bus_sync_unlock = ab5500_irq_sync_unlock, }; struct ab_family_id { @@ -1966,6 +1888,10 @@ static const struct ab_family_id ids[] __initdata = { .id = AB5500_1_0, .name = "1.0" }, + { + .id = AB5500_1_1, + .name = "1.1" + }, /* Terminator */ { .id = 0x00, @@ -1990,7 +1916,7 @@ static int __init ab5500_probe(struct platform_device *pdev) /* Initialize data structure */ mutex_init(&ab->access_mutex); - spin_lock_init(&ab->event_lock); + mutex_init(&ab->irq_lock); ab->dev = &pdev->dev; ab->irq_base = ab5500_plf_data->irq.base; @@ -2032,11 +1958,6 @@ static int __init ab5500_probe(struct platform_device *pdev) goto exit_no_setup; } -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) - INIT_WORK(&ab->irq_work, ab5500_irq_work); -#endif - INIT_WORK(&ab->mask_work, ab5500_mask_work); - for (i = 0; i < ab5500_plf_data->irq.count; i++) { unsigned int irq; @@ -2044,36 +1965,38 @@ static int __init ab5500_probe(struct platform_device *pdev) set_irq_chip_data(irq, ab); set_irq_chip_and_handler(irq, &ab5500_irq_chip, handle_simple_irq); -#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 29) set_irq_nested_thread(irq, 1); -#endif +#ifdef CONFIG_ARM set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif } res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - if (!res) { dev_err(&pdev->dev, "ab5500_platform_get_resource error\n"); goto exit_no_irq; } ab->ab5500_irq = res->start; -#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30) - /* This really unpredictable IRQ is of course sampled for entropy. */ - err = request_irq(res->start, ab5500_irq_handler, - (IRQF_DISABLED | IRQF_SAMPLE_RANDOM), "ab5500-core", ab); - if (err) { - dev_err(&pdev->dev, "ab5500_request_irq error\n"); - goto exit_no_irq; + /* Clear and mask all interrupts */ + for (i = 0; i < AB5500_NUM_IRQ_REGS; i++) { + u8 latchreg = AB5500_IT_LATCH0_REG + i; + u8 maskreg = AB5500_IT_MASK0_REG + i; + u8 val; + + get_register_interruptible(ab, AB5500_BANK_IT, latchreg, &val); + set_register_interruptible(ab, AB5500_BANK_IT, maskreg, 0xff); } - /* We probably already got an irq here, but if not, - * we force a first time and save the startup events here.*/ - disable_irq_nosync(res->start); - schedule_work(&ab->irq_work); -#else - err = request_threaded_irq(res->start, ab5500_irq_handler, NULL, - IRQF_SAMPLE_RANDOM, "ab5500-core", ab); + for (i = 0; i < AB5500_NUM_IRQ_REGS; i++) + ab->mask[i] = ab->oldmask[i] = 0xff; + + err = request_threaded_irq(res->start, NULL, ab5500_irq, + IRQF_NO_SUSPEND | IRQF_ONESHOT, + "ab5500-core", ab); + /* This real unpredictable IRQ is of course sampled for entropy */ rand_initialize_irq(res->start); @@ -2081,7 +2004,6 @@ static int __init ab5500_probe(struct platform_device *pdev) dev_err(&pdev->dev, "ab5500_request_irq error\n"); goto exit_no_irq; } -#endif err = abx500_register_ops(&pdev->dev, &ab5500_ops); if (err) { @@ -2091,8 +2013,7 @@ static int __init ab5500_probe(struct platform_device *pdev) /* Set up and register the platform devices. */ for (i = 0; i < AB5500_NUM_DEVICES; i++) { - ab5500_devs[i].platform_data = ab5500_plf_data->dev_data[i]; - ab5500_devs[i].data_size = ab5500_plf_data->dev_data_sz[i]; + ab5500_devs[i].mfd_data = ab5500_plf_data->dev_data[i]; } err = mfd_add_devices(&pdev->dev, 0, ab5500_devs, -- cgit v1.2.3 From cb6e98b76d45876f31a16faeea3ce3f38af2d4f9 Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Thu, 24 Mar 2011 17:24:19 +0530 Subject: rtc: add AB5500 RTC driver Add a driver for the RTC block on the AB5500. The AB5500 RTC has similarities with both the AB8500 and the AB3100, so this driver is made generic in order to replace the other two drivers by adding some variant specific information. ST-Ericsson Linux next: - ST-Ericsson ID: WP332221 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I35c7ee245ea68e435c800151dbb8f6e10c7abe5d Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/19331 Reviewed-by: Vijaya Kumar K-1 Reviewed-by: Bibek BASU Reviewed-by: Srinidhi KASAGAR --- drivers/rtc/Kconfig | 7 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-ab.c | 480 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 488 insertions(+) create mode 100644 drivers/rtc/rtc-ab.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 5a538fc1cc8..15d4eebd734 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -697,6 +697,13 @@ config RTC_DRV_PCF50633 If you say yes here you get support for the RTC subsystem of the NXP PCF50633 used in embedded systems. +config RTC_DRV_AB + tristate "ST-Ericsson AB5500 RTC" + depends on AB5500_CORE + help + Select this to enable the ST-Ericsson AB5500 Mixed Signal IC RTC + support. This chip contains a battery- and capacitor-backed RTC. + config RTC_DRV_AB3100 tristate "ST-Ericsson AB3100 RTC" depends on AB3100_CORE diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 6e6982335c1..a69992dd1cb 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -16,6 +16,7 @@ rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o # Keep the list ordered. obj-$(CONFIG_RTC_DRV_88PM860X) += rtc-88pm860x.o +obj-$(CONFIG_RTC_DRV_AB) += rtc-ab.o obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o obj-$(CONFIG_RTC_DRV_AB8500) += rtc-ab8500.o obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o diff --git a/drivers/rtc/rtc-ab.c b/drivers/rtc/rtc-ab.c new file mode 100644 index 00000000000..3d3c216995d --- /dev/null +++ b/drivers/rtc/rtc-ab.c @@ -0,0 +1,480 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + * Author: Rabin Vincent + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define AB5500_RTC_CLOCK_RATE 32768 +#define AB5500_RTC 0x00 +#define AB5500_RTC_ALARM (1 << 1) +#define AB5500_READREQ 0x01 +#define AB5500_READREQ_REQ 0x01 +#define AB5500_AL0 0x02 +#define AB5500_TI0 0x06 + +/** + * struct ab_rtc - variant specific data + * @irqname: optional name for the alarm interrupt resource + * @epoch: epoch to adjust year to + * @bank: AB bank where this block is present + * @rtc: address of the "RTC" (control) register + * @rtc_alarmon: mask of the alarm enable bit in the above register + * @ti0: address of the TI0 register. The rest of the TI + * registers are assumed to contiguously follow this one. + * @nr_ti: number of TI* registers + * @al0: address of the AL0 register. The rest of the + * AL registers are assumed to contiguously follow this one. + * @nr_al: number of AL* registers + * @startup: optional function to initialize the RTC + * @alarm_to_regs: function to convert alarm time in seconds + * to a list of AL register values + * @time_to_regs: function to convert alarm time in seconds + * to a list of TI register values + * @regs_to_alarm: function to convert a list of AL register + * values to the alarm time in seconds + * @regs_to_time: function to convert a list of TI register + * values to the alarm time in seconds + * @request_read: optional function to request a read from the TI* registers + * @request_write: optional function to request a write to the TI* registers + */ +struct ab_rtc { + const char *irqname; + unsigned int epoch; + + u8 bank; + u8 rtc; + u8 rtc_alarmon; + u8 ti0; + int nr_ti; + u8 al0; + int nr_al; + + int (*startup)(struct device *dev); + void (*alarm_to_regs)(struct device *dev, unsigned long secs, u8 *regs); + void (*time_to_regs)(struct device *dev, unsigned long secs, u8 *regs); + unsigned long (*regs_to_alarm)(struct device *dev, u8 *regs); + unsigned long (*regs_to_time)(struct device *dev, u8 *regs); + int (*request_read)(struct device *dev); + int (*request_write)(struct device *dev); +}; + +static const struct ab_rtc *to_ab_rtc(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + return (struct ab_rtc *)pdev->id_entry->driver_data; +} + +/* Calculate the number of seconds since year, for epoch adjustment */ +static unsigned long ab_rtc_get_elapsed_seconds(unsigned int year) +{ + unsigned long secs; + struct rtc_time tm = { + .tm_year = year - 1900, + .tm_mday = 1, + }; + + rtc_tm_to_time(&tm, &secs); + + return secs; +} + +static int ab5500_rtc_request_read(struct device *dev) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + unsigned long timeout; + int err; + + err = abx500_set_register_interruptible(dev, variant->bank, + AB5500_READREQ, + AB5500_READREQ_REQ); + if (err < 0) + return err; + + timeout = jiffies + HZ; + while (time_before(jiffies, timeout)) { + u8 value; + + err = abx500_get_register_interruptible(dev, variant->bank, + AB5500_READREQ, &value); + if (err < 0) + return err; + + if (!(value & AB5500_READREQ_REQ)) + return 0; + + msleep(1); + } + + return -EIO; +} + +static void +ab5500_rtc_time_to_regs(struct device *dev, unsigned long secs, u8 *regs) +{ + unsigned long mins = secs / 60; + u64 fat_time; + + secs %= 60; + + fat_time = secs * AB5500_RTC_CLOCK_RATE; + fat_time |= (u64)mins << 21; + + regs[0] = (fat_time) & 0xFF; + regs[1] = (fat_time >> 8) & 0xFF; + regs[2] = (fat_time >> 16) & 0xFF; + regs[3] = (fat_time >> 24) & 0xFF; + regs[4] = (fat_time >> 32) & 0xFF; + regs[5] = (fat_time >> 40) & 0xFF; +} + +static unsigned long +ab5500_rtc_regs_to_time(struct device *dev, u8 *regs) +{ + u64 fat_time = ((u64)regs[5] << 40) | ((u64)regs[4] << 32) | + (regs[3] << 24) | (regs[2] << 16) | + (regs[1] << 8) | regs[0]; + unsigned long secs = (fat_time & 0x1fffff) / AB5500_RTC_CLOCK_RATE; + unsigned long mins = fat_time >> 21; + + return mins * 60 + secs; +} + +static void +ab5500_rtc_alarm_to_regs(struct device *dev, unsigned long secs, u8 *regs) +{ + unsigned long mins = secs / 60; + +#ifdef CONFIG_ANDROID + /* + * Needed because Android believes all hw have a wake-up resolution in + * seconds. + */ + mins++; +#endif + + regs[0] = mins & 0xFF; + regs[1] = (mins >> 8) & 0xFF; + regs[2] = (mins >> 16) & 0xFF; +} + +static unsigned long +ab5500_rtc_regs_to_alarm(struct device *dev, u8 *regs) +{ + unsigned long mins = (regs[2] << 16) | (regs[1] << 8) | regs[0]; + unsigned long secs = mins * 60; + + return secs; +} + +static const struct ab_rtc ab5500_rtc = { + .irqname = "RTC_Alarm", + .bank = AB5500_BANK_RTC, + .rtc = AB5500_RTC, + .rtc_alarmon = AB5500_RTC_ALARM, + .ti0 = AB5500_TI0, + .nr_ti = 6, + .al0 = AB5500_AL0, + .nr_al = 3, + .epoch = 2000, + .time_to_regs = ab5500_rtc_time_to_regs, + .regs_to_time = ab5500_rtc_regs_to_time, + .alarm_to_regs = ab5500_rtc_alarm_to_regs, + .regs_to_alarm = ab5500_rtc_regs_to_alarm, + .request_read = ab5500_rtc_request_read, +}; + +static int ab_rtc_request_read(struct device *dev) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + + if (!variant->request_read) + return 0; + + return variant->request_read(dev); +} + +static int ab_rtc_request_write(struct device *dev) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + + if (!variant->request_write) + return 0; + + return variant->request_write(dev); +} + +static bool ab_rtc_valid_time(struct device *dev, struct rtc_time *time) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + + if (!variant->epoch) + return true; + + return time->tm_year >= variant->epoch - 1900; +} + +static int +ab_rtc_tm_to_time(struct device *dev, struct rtc_time *tm, unsigned long *secs) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + + rtc_tm_to_time(tm, secs); + + if (variant->epoch) + *secs -= ab_rtc_get_elapsed_seconds(variant->epoch); + + return 0; +} + +static int +ab_rtc_time_to_tm(struct device *dev, unsigned long secs, struct rtc_time *tm) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + + if (variant->epoch) + secs += ab_rtc_get_elapsed_seconds(variant->epoch); + + rtc_time_to_tm(secs, tm); + + return 0; +} + +static int ab_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + unsigned char buf[variant->nr_ti]; + unsigned long secs; + int err; + + err = ab_rtc_request_read(dev); + if (err) + return err; + + err = abx500_get_register_page_interruptible(dev, variant->bank, + variant->ti0, + buf, variant->nr_ti); + if (err) + return err; + + secs = variant->regs_to_time(dev, buf); + ab_rtc_time_to_tm(dev, secs, tm); + + return rtc_valid_tm(tm); +} + +static int ab_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + unsigned char buf[variant->nr_ti]; + unsigned long secs; + u8 reg = variant->ti0; + int err; + int i; + + if (!ab_rtc_valid_time(dev, tm)) + return -EINVAL; + + ab_rtc_tm_to_time(dev, tm, &secs); + variant->time_to_regs(dev, secs, buf); + + for (i = 0; i < variant->nr_ti; i++, reg++) { + err = abx500_set_register_interruptible(dev, variant->bank, + reg, buf[i]); + if (err) + return err; + } + + return ab_rtc_request_write(dev); +} + +static int ab_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + unsigned long secs; + u8 buf[variant->nr_al]; + u8 rtcval; + int err; + + err = abx500_get_register_interruptible(dev, variant->bank, + variant->rtc, &rtcval); + if (err) + return err; + + alarm->enabled = !!(rtcval & variant->rtc_alarmon); + alarm->pending = 0; + + err = abx500_get_register_page_interruptible(dev, variant->bank, + variant->al0, buf, + variant->nr_al); + if (err) + return err; + + secs = variant->regs_to_alarm(dev, buf); + ab_rtc_time_to_tm(dev, secs, &alarm->time); + + return rtc_valid_tm(&alarm->time); +} + +static int ab_rtc_alarm_enable(struct device *dev, unsigned int enabled) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + u8 mask = variant->rtc_alarmon; + u8 value = enabled ? mask : 0; + + return abx500_mask_and_set_register_interruptible(dev, variant->bank, + variant->rtc, mask, + value); +} + +static int ab_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + const struct ab_rtc *variant = to_ab_rtc(dev); + unsigned char buf[variant->nr_al]; + unsigned long secs; + u8 reg = variant->al0; + int err; + int i; + + if (!ab_rtc_valid_time(dev, &alarm->time)) + return -EINVAL; + + ab_rtc_tm_to_time(dev, &alarm->time, &secs); + variant->alarm_to_regs(dev, secs, buf); + + /* + * Disable alarm first. Otherwise the RTC may not detect an alarm + * reprogrammed for the same time without disabling the alarm in + * between the programmings. + */ + err = ab_rtc_alarm_enable(dev, false); + if (err) + return err; + + for (i = 0; i < variant->nr_al; i++, reg++) { + err = abx500_set_register_interruptible(dev, variant->bank, + reg, buf[i]); + if (err) + return err; + } + + return ab_rtc_alarm_enable(dev, true); +} + +static const struct rtc_class_ops ab_rtc_ops = { + .read_time = ab_rtc_read_time, + .set_time = ab_rtc_set_time, + .read_alarm = ab_rtc_read_alarm, + .set_alarm = ab_rtc_set_alarm, + .alarm_irq_enable = ab_rtc_alarm_enable, +}; + +static irqreturn_t ab_rtc_irq(int irq, void *dev_id) +{ + unsigned long events = RTC_IRQF | RTC_AF; + struct rtc_device *rtc = dev_id; + + rtc_update_irq(rtc, 1, events); + + return IRQ_HANDLED; +} + +static int __devinit ab_rtc_probe(struct platform_device *pdev) +{ + const struct ab_rtc *variant = to_ab_rtc(&pdev->dev); + int err; + struct rtc_device *rtc; + int irq = -ENXIO; + + if (variant->irqname) { + irq = platform_get_irq_byname(pdev, variant->irqname); + if (irq < 0) + return irq; + } + + if (variant->startup) { + err = variant->startup(&pdev->dev); + if (err) + return err; + } + + rtc = rtc_device_register("ab8500-rtc", &pdev->dev, &ab_rtc_ops, + THIS_MODULE); + if (IS_ERR(rtc)) { + dev_err(&pdev->dev, "Registration failed\n"); + err = PTR_ERR(rtc); + return err; + } + + if (irq >= 0) { + err = request_any_context_irq(irq, ab_rtc_irq, + IRQF_NO_SUSPEND, + pdev->id_entry->name, + rtc); + if (err < 0) { + dev_err(&pdev->dev, "could not get irq: %d\n", err); + goto out_unregister; + } + } + + platform_set_drvdata(pdev, rtc); + + return 0; + +out_unregister: + rtc_device_unregister(rtc); + return err; +} + +static int __devexit ab_rtc_remove(struct platform_device *pdev) +{ + const struct ab_rtc *variant = to_ab_rtc(&pdev->dev); + struct rtc_device *rtc = platform_get_drvdata(pdev); + int irq = platform_get_irq_byname(pdev, variant->irqname); + + if (irq >= 0) + free_irq(irq, rtc); + rtc_device_unregister(rtc); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_device_id ab_rtc_id_table[] = { + { "ab5500-rtc", (kernel_ulong_t)&ab5500_rtc, }, + { }, +}; +MODULE_DEVICE_TABLE(platform, ab_rtc_id_table); + +static struct platform_driver ab_rtc_driver = { + .driver.name = "ab-rtc", + .driver.owner = THIS_MODULE, + .id_table = ab_rtc_id_table, + .probe = ab_rtc_probe, + .remove = __devexit_p(ab_rtc_remove), +}; + +static int __init ab_rtc_init(void) +{ + return platform_driver_register(&ab_rtc_driver); +} +module_init(ab_rtc_init); + +static void __exit ab_rtc_exit(void) +{ + platform_driver_unregister(&ab_rtc_driver); +} +module_exit(ab_rtc_exit); + +MODULE_AUTHOR("Rabin Vincent "); +MODULE_DESCRIPTION("AB5500 RTC Driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 01830ed7b0821da7b44129c6a05d4aea83f0840f Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Fri, 1 Apr 2011 11:33:40 +0530 Subject: rtc-ab: add missing casts Only the << 24 one is strictly required, but let's just be explicit about all of them. ST-Ericsson Linux next: - ST-Ericsson ID: WP332221 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ib8ae749d6ad21c12160fb4af62b79c347bf1dc75 Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/19672 Reviewed-by: Jonas ABERG --- drivers/rtc/rtc-ab.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-ab.c b/drivers/rtc/rtc-ab.c index 3d3c216995d..8e595e05d99 100644 --- a/drivers/rtc/rtc-ab.c +++ b/drivers/rtc/rtc-ab.c @@ -141,8 +141,8 @@ static unsigned long ab5500_rtc_regs_to_time(struct device *dev, u8 *regs) { u64 fat_time = ((u64)regs[5] << 40) | ((u64)regs[4] << 32) | - (regs[3] << 24) | (regs[2] << 16) | - (regs[1] << 8) | regs[0]; + ((u64)regs[3] << 24) | ((u64)regs[2] << 16) | + ((u64)regs[1] << 8) | regs[0]; unsigned long secs = (fat_time & 0x1fffff) / AB5500_RTC_CLOCK_RATE; unsigned long mins = fat_time >> 21; @@ -170,7 +170,9 @@ ab5500_rtc_alarm_to_regs(struct device *dev, unsigned long secs, u8 *regs) static unsigned long ab5500_rtc_regs_to_alarm(struct device *dev, u8 *regs) { - unsigned long mins = (regs[2] << 16) | (regs[1] << 8) | regs[0]; + unsigned long mins = ((unsigned long)regs[2] << 16) | + ((unsigned long)regs[1] << 8) | + regs[0]; unsigned long secs = mins * 60; return secs; -- cgit v1.2.3 From d42db7ea0ecdd5a3b3aa3121bac9c8babadc02bd Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 12 Oct 2011 11:20:04 +0200 Subject: power: ab8500_bm: Removal of maintenance charging This patch makes it possible to remove the maintenance A and B charging states and replaces them with a state that restarts the charging when the battery voltage level dropped below a certain level. The maintenance charging can now be turned of by setting a flag in the board configuration. ST-Ericsson Linux Next: - ST-Ericsson ID: CR333019 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ie5e8ab20a3b57a1028d544371b10ffbb1fd77660 Signed-off-by: Kalle Komierowski Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/19916 Reviewed-by: Johan GARDSMARK Reviewed-by: Johan PALSSON Reviewed-by: Jonas ABERG Tested-by: Karl KOMIEROWSKI Conflicts: arch/arm/mach-ux500/board-mop500-bm.c --- drivers/power/ab8500_chargalg.c | 55 ++++++++++++++++++++++++++++++++++++++--- drivers/power/ab8500_fg.c | 3 +-- include/linux/mfd/ab8500/bm.h | 7 ++++++ 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/drivers/power/ab8500_chargalg.c b/drivers/power/ab8500_chargalg.c index 788d81aa7cd..cb56c67475c 100644 --- a/drivers/power/ab8500_chargalg.c +++ b/drivers/power/ab8500_chargalg.c @@ -30,6 +30,9 @@ /* End-of-charge criteria counter */ #define EOC_COND_CNT 10 +/* Recharge criteria counter */ +#define RCH_COND_CNT 3 + #define to_ab8500_chargalg_device_info(x) container_of((x), \ struct ab8500_chargalg, chargalg_psy); @@ -80,6 +83,8 @@ enum ab8500_chargalg_states { STATE_HW_TEMP_PROTECT, STATE_NORMAL_INIT, STATE_NORMAL, + STATE_WAIT_FOR_RECHARGE_INIT, + STATE_WAIT_FOR_RECHARGE, STATE_MAINTENANCE_A_INIT, STATE_MAINTENANCE_A, STATE_MAINTENANCE_B_INIT, @@ -109,6 +114,8 @@ static const char *states[] = { "HW_TEMP_PROTECT", "NORMAL_INIT", "NORMAL", + "WAIT_FOR_RECHARGE_INIT", + "WAIT_FOR_RECHARGE", "MAINTENANCE_A_INIT", "MAINTENANCE_A", "MAINTENANCE_B_INIT", @@ -186,6 +193,7 @@ enum maxim_ret { * @dev: pointer to the structure device * @charge_status: battery operating status * @eoc_cnt: counter used to determine end-of_charge + * @rch_cnt: counter used to determine start of recharge * @maintenance_chg: indicate if maintenance charge is active * @t_hyst_norm temperature hysteresis when the temperature has been * over or under normal limits @@ -214,6 +222,7 @@ struct ab8500_chargalg { struct device *dev; int charge_status; int eoc_cnt; + int rch_cnt; bool maintenance_chg; int t_hyst_norm; int t_hyst_lowhigh; @@ -568,6 +577,26 @@ static void ab8500_chargalg_stop_charging(struct ab8500_chargalg *di) power_supply_changed(&di->chargalg_psy); } +/** + * ab8500_chargalg_hold_charging() - Pauses charging + * @di: pointer to the ab8500_chargalg structure + * + * This function is called in the case where maintenance charging has been + * disabled and instead a battery voltage mode is entered to check when the + * battery voltage has reached a certain recharge voltage + */ +static void ab8500_chargalg_hold_charging(struct ab8500_chargalg *di) +{ + ab8500_chargalg_ac_en(di, false, 0, 0); + ab8500_chargalg_usb_en(di, false, 0, 0); + ab8500_chargalg_stop_safety_timer(di); + ab8500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(&di->chargalg_psy); +} + /** * ab8500_chargalg_start_charging() - Start the charger * @di: pointer to the ab8500_chargalg structure @@ -1384,11 +1413,31 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) case STATE_NORMAL: handle_maxim_chg_curr(di); - if (di->charge_status == POWER_SUPPLY_STATUS_FULL && - di->maintenance_chg) - ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A_INIT); + di->maintenance_chg) { + if (di->bat->no_maintenance) + ab8500_chargalg_state_to(di, + STATE_WAIT_FOR_RECHARGE_INIT); + else + ab8500_chargalg_state_to(di, + STATE_MAINTENANCE_A_INIT); + } + break; + /* This state will be used when the maintenance state is disabled */ + case STATE_WAIT_FOR_RECHARGE_INIT: + ab8500_chargalg_hold_charging(di); + ab8500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE); + di->rch_cnt = RCH_COND_CNT; + /* Intentional fallthrough */ + + case STATE_WAIT_FOR_RECHARGE: + if (di->batt_data.volt <= + di->bat->bat_type[di->bat->batt_id].recharge_vol) { + if (di->rch_cnt-- == 0) + ab8500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else + di->rch_cnt = RCH_COND_CNT; break; case STATE_MAINTENANCE_A_INIT: diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index a42ce95d48f..8af5216b90b 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -590,8 +590,7 @@ static void ab8500_fg_acc_cur_work(struct work_struct *work) mutex_unlock(&di->cc_lock); - queue_work(di->fg_wq, - &di->fg_work); + queue_work(di->fg_wq, &di->fg_work); return; exit: diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h index 068b63a93ce..2ca4d8779cb 100644 --- a/include/linux/mfd/ab8500/bm.h +++ b/include/linux/mfd/ab8500/bm.h @@ -312,6 +312,10 @@ struct ab8500_maxim_parameters { * @charge_full_design: Maximum battery capacity in mAh * @nominal_voltage: Nominal voltage of the battery in mV * @termination_vol: max voltage upto which battery can be charged + * @termination_curr battery charging termination current in mA + * @recharge_vol battery voltage limit that will trigger a new + * full charging cycle in the case where maintenan- + * -ce charging has been disabled * @normal_cur_lvl: charger current in normal state in mA * @normal_vol_lvl: charger voltage in normal state in mV * @maint_a_cur_lvl: charger current in maintenance A state in mA @@ -336,6 +340,7 @@ struct battery_type { int nominal_voltage; int termination_vol; int termination_curr; + int recharge_vol; int normal_cur_lvl; int normal_vol_lvl; int maint_a_cur_lvl; @@ -393,6 +398,7 @@ struct ab8500_bm_charger_parameters { * @usb_safety_tmr_h safety timer for usb charger * @bkup_bat_v voltage which we charge the backup battery with * @bkup_bat_i current which we charge the backup battery with + * @no_maintenance indicates that maintenance charging is disabled * @adc_therm placement of thermistor, batctrl or battemp adc * @chg_unknown_bat flag to enable charging of unknown batteries * @enable_overshoot flag to enable VBAT overshoot control @@ -417,6 +423,7 @@ struct ab8500_bm_data { int usb_safety_tmr_h; int bkup_bat_v; int bkup_bat_i; + bool no_maintenance; bool chg_unknown_bat; bool enable_overshoot; enum adc_therm adc_therm; -- cgit v1.2.3 From 7a1bf7608d737cdc98651eaa937a1e531b46fc91 Mon Sep 17 00:00:00 2001 From: Carlos Chinea Date: Tue, 14 Dec 2010 10:09:42 +0000 Subject: HSI: Add HSI API documentation Add an entry for HSI in the device-drivers section of the kernel documentation. Change-Id: I5b5a25d0798ec50f8aad38f004b232ff2e0cde43 Signed-off-by: Carlos Chinea Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20580 Reviewed-by: Pawel SZYSZUK Tested-by: Pawel SZYSZUK Reviewed-by: Jonas ABERG --- Documentation/DocBook/device-drivers.tmpl | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Documentation/DocBook/device-drivers.tmpl b/Documentation/DocBook/device-drivers.tmpl index b638e50cf8f..5f70f734e8b 100644 --- a/Documentation/DocBook/device-drivers.tmpl +++ b/Documentation/DocBook/device-drivers.tmpl @@ -437,4 +437,21 @@ X!Idrivers/video/console/fonts.c !Edrivers/i2c/i2c-core.c + + High Speed Synchronous Serial Interface (HSI) + + + High Speed Synchronous Serial Interface (HSI) is a + serial interface mainly used for connecting application + engines (APE) with cellular modem engines (CMT) in cellular + handsets. + + HSI provides multiplexing for up to 16 logical channels, + low-latency and full duplex communication. + + +!Iinclude/linux/hsi/hsi.h +!Edrivers/hsi/hsi.c + + -- cgit v1.2.3 From b1b067ea6e4c3261f45ce0d7adf67cb7a0b3c0a5 Mon Sep 17 00:00:00 2001 From: Andras Domokos Date: Tue, 14 Dec 2010 10:09:43 +0000 Subject: HSI: hsi_char: Update ioctl-number.txt Added ioctl range for HSI char devices to the documentation Change-Id: I51836e3a2f7bc557b55f83aef8fb3065ccd55324 Signed-off-by: Andras Domokos Signed-off-by: Carlos Chinea Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20581 Reviewed-by: Pawel SZYSZUK Tested-by: Pawel SZYSZUK Reviewed-by: Jonas ABERG --- Documentation/ioctl/ioctl-number.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index 54078ed96b3..af76fdef604 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -223,6 +223,7 @@ Code Seq#(hex) Include File Comments 'j' 00-3F linux/joystick.h 'k' 00-0F linux/spi/spidev.h conflict! 'k' 00-05 video/kyro.h conflict! +'k' 10-17 linux/hsi/hsi_char.h HSI character device 'l' 00-3F linux/tcfs_fs.h transparent cryptographic file system 'l' 40-7F linux/udf_fs_i.h in development: -- cgit v1.2.3 From cfd768edbf4a2843d10b9d4853bca6b754163598 Mon Sep 17 00:00:00 2001 From: carriere etienne Date: Fri, 8 Apr 2011 16:26:36 +0200 Subject: ab8500 debugfs: formated access AB8500 registers from debugfs entry Add debugfs entry ab8500/hwreg to read/write bit-field in AB8500 registers. Check the debugfs entries usage from heading comments in ab8500-debugfs.c ST-Ericsson Linux next: - ST-Ericsson ID: ER 334167 ST-Ericsson FOSS-OUT ID: Trivial Patch tested on: relC 'u8500-android-2.3_v0.44' Signed-off-by: carriere etienne Change-Id: Ibb59f3e0bd3dc4e67f82b080aea66bf63b306b45 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20179 Reviewed-by: Mattias WALLIN --- drivers/mfd/ab8500-debugfs.c | 375 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 331 insertions(+), 44 deletions(-) diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index 584c7472fca..712b2d064b7 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -4,6 +4,72 @@ * Author: Mattias Wallin for ST-Ericsson. * License Terms: GNU General Public License v2 */ +/* + * AB8500 register access + * ====================== + * + * read: + * # echo BANK > /ab8500/register-bank + * # echo ADDR > /ab8500/register-address + * # cat /ab8500/register-value + * + * write: + * # echo BANK > /ab8500/register-bank + * # echo ADDR > /ab8500/register-address + * # echo VALUE > /ab8500/register-value + * + * read all registers from a bank: + * # echo BANK > /ab8500/register-bank + * # cat /ab8500/all-bank-register + * + * BANK target AB8500 register bank + * ADDR target AB8500 register address + * VALUE decimal or 0x-prefixed hexadecimal + * + * + * User Space notification on AB8500 IRQ + * ===================================== + * + * Allows user space entity to be notified when target AB8500 IRQ occurs. + * When subscribed, a sysfs entry is created in ab8500.i2c platform device. + * One can pool this file to get target IRQ occurence information. + * + * subscribe to an AB8500 IRQ: + * # echo IRQ > /ab8500/irq-subscribe + * + * unsubscribe from an AB8500 IRQ: + * # echo IRQ > /ab8500/irq-unsubscribe + * + * + * AB8500 register formated read/write access + * ========================================== + * + * Read: read data, data>>SHIFT, data&=MASK, output data + * [0xABCDEF98] shift=12 mask=0xFFF => 0x00000CDE + * Write: read data, data &= ~(MASK< [0xAB123F98] + * + * Usage: + * # echo "CMD [OPTIONS] BANK ADRESS [VALUE]" > $debugfs/ab8500/hwreg + * + * CMD read read access + * write write access + * + * BANK target reg bank + * ADDRESS target reg address + * VALUE (write) value to be updated + * + * OPTIONS + * -d|-dec (read) output in decimal + * -h|-hexa (read) output in 0x-hexa (default) + * -l|-w|-b 32bit (default), 16bit or 8bit reg access + * -m|-mask MASK 0x-hexa mask (default 0xFFFFFFFF) + * -s|-shift SHIFT bit shift value (read:left, write:right) + * -o|-offset OFFSET address offset to add to ADDRESS value + * + * Warning: bit shift operation is applied to bit-mask. + * Warning: bit shift direction depends on read or right command. + */ #include #include @@ -17,6 +83,11 @@ #include #include +#ifdef CONFIG_DEBUG_FS +#include +#include +#endif + static u32 debug_bank; static u32 debug_address; @@ -51,6 +122,25 @@ struct ab8500_i2c_ranges { const struct ab8500_reg_range *range; }; +/* hwreg- "mask" and "shift" entries ressources */ +struct hwreg_cfg { + u32 bank; /* target bank */ + u32 addr; /* target address */ + uint fmt; /* format */ + uint mask; /* read/write mask, applied before any bit shift */ + int shift; /* bit shift (read:right shift, write:left shift */ +}; +/* fmt bit #0: 0=hexa, 1=dec */ +#define REG_FMT_DEC(c) ((c)->fmt & 0x1) +#define REG_FMT_HEX(c) (!REG_FMT_DEC(c)) + +static struct hwreg_cfg hwreg_cfg = { + .addr = 0, /* default: invalid phys addr */ + .fmt = 0, /* default: 32bit access, hex output */ + .mask = 0xFFFFFFFF, /* default: no mask */ + .shift = 0, /* default: no bit shift */ +}; + #define AB8500_NAME_STRING "ab8500" #define AB8500_NUM_BANKS 22 @@ -545,6 +635,203 @@ static ssize_t ab8500_val_write(struct file *file, return count; } +/* + * - HWREG DB8500 formated routines + */ +static int ab8500_hwreg_print(struct seq_file *s, void *d) +{ + struct device *dev = s->private; + int ret; + u8 regvalue; + + ret = abx500_get_register_interruptible(dev, + (u8)hwreg_cfg.bank, (u8)hwreg_cfg.addr, ®value); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + + if (hwreg_cfg.shift >= 0) + regvalue >>= hwreg_cfg.shift; + else + regvalue <<= -hwreg_cfg.shift; + regvalue &= hwreg_cfg.mask; + + if (REG_FMT_DEC(&hwreg_cfg)) + seq_printf(s, "%d\n", regvalue); + else + seq_printf(s, "0x%02X\n", regvalue); + return 0; +} + +static int ab8500_hwreg_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_hwreg_print, inode->i_private); +} + +/* + * return length of an ASCII numerical value, 0 is string is not a + * numerical value. + * string shall start at value 1st char. + * string can be tailed with \0 or space or newline chars only. + * value can be decimal or hexadecimal (prefixed 0x or 0X). + */ +static int strval_len(char *b) +{ + char *s = b; + if ((*s == '0') && ((*(s+1) == 'x') || (*(s+1) == 'X'))) { + s += 2; + for (; *s && (*s != ' ') && (*s != '\n'); s++) { + if (!isxdigit(*s)) + return 0; + } + } else { + if (*s == '-') + s++; + for (; *s && (*s != ' ') && (*s != '\n'); s++) { + if (!isdigit(*s)) + return 0; + } + } + return (int) (s-b); +} + +/* + * parse hwreg input data. + * update global hwreg_cfg only if input data syntax is ok. + */ +static ssize_t hwreg_common_write(char *b, struct hwreg_cfg *cfg, + struct device *dev) +{ + uint write, val = 0; + struct hwreg_cfg loc = { + .bank = 0, /* default: invalid phys addr */ + .addr = 0, /* default: invalid phys addr */ + .fmt = 0, /* default: 32bit access, hex output */ + .mask = 0xFFFFFFFF, /* default: no mask */ + .shift = 0, /* default: no bit shift */ + }; + + /* read or write ? */ + if (!strncmp(b, "read ", 5)) { + write = 0; + b += 5; + } else if (!strncmp(b, "write ", 6)) { + write = 1; + b += 6; + } else + return -EINVAL; + + /* OPTIONS -l|-w|-b -s -m -o */ + while ((*b == ' ') || (*b == '-')) { + if (*(b-1) != ' ') { + b++; + continue; + } + if ((!strncmp(b, "-d ", 3)) || + (!strncmp(b, "-dec ", 5))) { + b += (*(b+2) == ' ') ? 3 : 5; + loc.fmt |= (1<<0); + } else if ((!strncmp(b, "-h ", 3)) || + (!strncmp(b, "-hex ", 5))) { + b += (*(b+2) == ' ') ? 3 : 5; + loc.fmt &= ~(1<<0); + } else if ((!strncmp(b, "-m ", 3)) || + (!strncmp(b, "-mask ", 6))) { + b += (*(b+2) == ' ') ? 3 : 6; + if (strval_len(b) == 0) + return -EINVAL; + loc.mask = simple_strtoul(b, &b, 0); + } else if ((!strncmp(b, "-s ", 3)) || + (!strncmp(b, "-shift ", 7))) { + b += (*(b+2) == ' ') ? 3 : 7; + if (strval_len(b) == 0) + return -EINVAL; + loc.shift = simple_strtol(b, &b, 0); + } else { + return -EINVAL; + } + } + /* get arg BANK and ADDRESS */ + if (strval_len(b) == 0) + return -EINVAL; + loc.bank = simple_strtoul(b, &b, 0); + while (*b == ' ') + b++; + if (strval_len(b) == 0) + return -EINVAL; + loc.addr = simple_strtoul(b, &b, 0); + + if (write) { + while (*b == ' ') + b++; + if (strval_len(b) == 0) + return -EINVAL; + val = simple_strtoul(b, &b, 0); + } + + /* args are ok, update target cfg (mainly for read) */ + *cfg = loc; + +#if ABB_HWREG_DEBUG + pr_warn("HWREG request: %s, %s, addr=0x%08X, mask=0x%X, shift=%d + value=0x%X\n", (write) ? "write" : "read", + REG_FMT_DEC(cfg) ? "decimal" : "hexa", + cfg->addr, cfg->mask, cfg->shift, val); +#endif + + if (write) { + u8 regvalue; + int ret = abx500_get_register_interruptible(dev, + (u8)cfg->bank, (u8)cfg->addr, ®value); + if (ret < 0) { + dev_err(dev, "abx500_get_reg fail %d, %d\n", + ret, __LINE__); + return -EINVAL; + } + + if (cfg->shift >= 0) { + regvalue &= ~(cfg->mask << (cfg->shift)); + val = (val & cfg->mask) << (cfg->shift); + } else { + regvalue &= ~(cfg->mask >> (-cfg->shift)); + val = (val & cfg->mask) >> (-cfg->shift); + } + val = val | regvalue; + + ret = abx500_set_register_interruptible(dev, + (u8)cfg->bank, (u8)cfg->addr, (u8)val); + if (ret < 0) { + pr_err("abx500_set_reg failed %d, %d", ret, __LINE__); + return -EINVAL; + } + + } + return 0; +} + +static ssize_t ab8500_hwreg_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + char buf[128]; + int buf_size, ret; + + /* Get userspace string and assure termination */ + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + buf[buf_size] = 0; + + /* get args and process */ + ret = hwreg_common_write(buf, &hwreg_cfg, dev); + return (ret) ? ret : buf_size; +} + +/* + * - irq subscribe/unsubscribe stuff + */ static int ab8500_subscribe_unsubscribe_print(struct seq_file *s, void *p) { seq_printf(s, "%d\n", irq_first); @@ -691,6 +978,10 @@ static ssize_t ab8500_unsubscribe_write(struct file *file, return count; } +/* + * - several deubgfs nodes fops + */ + static const struct file_operations ab8500_bank_fops = { .open = ab8500_bank_open, .write = ab8500_bank_write, @@ -736,16 +1027,20 @@ static const struct file_operations ab8500_unsubscribe_fops = { .owner = THIS_MODULE, }; +static const struct file_operations ab8500_hwreg_fops = { + .open = ab8500_hwreg_open, + .write = ab8500_hwreg_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + static struct dentry *ab8500_dir; -static struct dentry *ab8500_reg_file; -static struct dentry *ab8500_bank_file; -static struct dentry *ab8500_address_file; -static struct dentry *ab8500_val_file; -static struct dentry *ab8500_subscribe_file; -static struct dentry *ab8500_unsubscribe_file; static int __devinit ab8500_debug_probe(struct platform_device *plf) { + struct dentry *file; debug_bank = AB8500_MISC; debug_address = AB8500_REV_REG & 0x00FF; @@ -765,67 +1060,59 @@ static int __devinit ab8500_debug_probe(struct platform_device *plf) ab8500_dir = debugfs_create_dir(AB8500_NAME_STRING, NULL); if (!ab8500_dir) - goto exit_no_debugfs; + goto err; - ab8500_reg_file = debugfs_create_file("all-bank-registers", + file = debugfs_create_file("all-bank-registers", S_IRUGO, ab8500_dir, &plf->dev, &ab8500_registers_fops); - if (!ab8500_reg_file) - goto exit_destroy_dir; + if (!file) + goto err; - ab8500_bank_file = debugfs_create_file("register-bank", + file = debugfs_create_file("register-bank", (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_bank_fops); - if (!ab8500_bank_file) - goto exit_destroy_reg; + if (!file) + goto err; - ab8500_address_file = debugfs_create_file("register-address", + file = debugfs_create_file("register-address", (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_address_fops); - if (!ab8500_address_file) - goto exit_destroy_bank; + if (!file) + goto err; - ab8500_val_file = debugfs_create_file("register-value", + file = debugfs_create_file("register-value", (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_val_fops); - if (!ab8500_val_file) - goto exit_destroy_address; + if (!file) + goto err; - ab8500_subscribe_file = debugfs_create_file("irq-subscribe", + file = debugfs_create_file("irq-subscribe", (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_subscribe_fops); - if (!ab8500_subscribe_file) - goto exit_destroy_val; + if (!file) + goto err; - ab8500_unsubscribe_file = debugfs_create_file("irq-unsubscribe", + file = debugfs_create_file("irq-unsubscribe", (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_unsubscribe_fops); - if (!ab8500_unsubscribe_file) - goto exit_destroy_subscribe; + if (!file) + goto err; + + file = debugfs_create_file("hwreg", + (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, + &ab8500_hwreg_fops); + if (!file) + goto err; + return 0; -exit_destroy_subscribe: - debugfs_remove(ab8500_subscribe_file); -exit_destroy_val: - debugfs_remove(ab8500_val_file); -exit_destroy_address: - debugfs_remove(ab8500_address_file); -exit_destroy_bank: - debugfs_remove(ab8500_bank_file); -exit_destroy_reg: - debugfs_remove(ab8500_reg_file); -exit_destroy_dir: - debugfs_remove(ab8500_dir); -exit_no_debugfs: +err: + if (ab8500_dir) + debugfs_remove_recursive(ab8500_dir); dev_err(&plf->dev, "failed to create debugfs entries.\n"); return -ENOMEM; } static int __devexit ab8500_debug_remove(struct platform_device *plf) { - debugfs_remove(ab8500_val_file); - debugfs_remove(ab8500_address_file); - debugfs_remove(ab8500_bank_file); - debugfs_remove(ab8500_reg_file); - debugfs_remove(ab8500_dir); - + debugfs_remove_recursive(ab8500_dir); return 0; } -- cgit v1.2.3 From 185688b6be01fabd48dd6fc9f9e9cc059d10b526 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 12 Oct 2011 14:51:50 +0200 Subject: ab5500-core: regulator: add AB5500 support ST-Ericsson Linux next: - ST-Ericsson ID: WP257121 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I91faae392552b8d0e993e1935115b63db9e7e268 Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20654 Reviewed-by: Bengt JONSSON Conflicts: drivers/regulator/Kconfig drivers/regulator/Makefile --- drivers/mfd/ab5500-core.c | 15 +++++++++++++-- include/linux/mfd/abx500.h | 3 +++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 2c4451ea5aa..45c3f34f313 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -214,7 +214,7 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { }, }, [AB5500_DEVID_REGULATORS] = { - .nbanks = 1, + .nbanks = 2, .bank = (struct ab5500_i2c_ranges[]) { { .bankid = AB5500_BANK_STARTUP, @@ -227,6 +227,17 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { }, }, }, + { + .bankid = AB5500_BANK_SIM_USBSIM, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x14, + .last = 0x14, + .perm = AB5500_PERM_RW, + }, + }, + }, }, }, [AB5500_DEVID_SIM] = { @@ -364,7 +375,7 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { .id = AB5500_DEVID_POWER, }, [AB5500_DEVID_REGULATORS] = { - .name = "ab5500-regulators", + .name = "ab5500-regulator", .id = AB5500_DEVID_REGULATORS, }, [AB5500_DEVID_SIM] = { diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 78fde125403..a3b3a5e380b 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -242,12 +242,15 @@ enum ab5500_banks { AB5500_NUM_BANKS = 15, }; +struct ab5500_regulator_platform_data; + struct ab5500_platform_data { struct {unsigned int base; unsigned int count; } irq; void *dev_data[AB5500_NUM_DEVICES]; size_t dev_data_sz[AB5500_NUM_DEVICES]; struct abx500_init_settings *init_settings; unsigned int init_settings_sz; + struct ab5500_regulator_platform_data *regulator; }; int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, -- cgit v1.2.3 From bf72df33c1a1ba8e56874e5d33b9657b1a063ef9 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 10 May 2011 09:33:09 +0200 Subject: ARM: enable CONFIG_KTIME_SCALAR Use straight 64-bit values as 64-bit operations are fairly efficient on ARM. Comparing the asm output with and without KTIME_SCALAR, using 64-bit math generates clearly better code. Comparing kernel/hrtimer.c .text size, it goes from 0x1414 to 0x119c with this change. ST-Ericsson ID: 329353 Signed-off-by: Rob Herring Signed-off-by: Mian Yousaf Kaukab Change-Id: Ic9d31ff64d7c60354f85055e3a74efd84d447e7f Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/21192 Reviewed-by: Jonas ABERG --- arch/arm/Kconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index cb4afed0d87..661276a9632 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -71,6 +71,10 @@ config KTIME_SCALAR bool default y +config KTIME_SCALAR + bool + default y + config HAVE_TCM bool select GENERIC_ALLOCATOR -- cgit v1.2.3 From cf3caac6deafcd4897c29093e17b219425a57fed Mon Sep 17 00:00:00 2001 From: Mattias Wallin Date: Thu, 7 Apr 2011 12:19:48 +0200 Subject: ARM: Fix buffer overflow if HZ=1000 This patch fix a buffer overflow bug in timer based delay code if HZ=1000. ST-Ericsson ID: ER 334336 ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Mattias Wallin Change-Id: I83326c808d992d76ec74fbb86958ca63f80e6f0c Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20261 Reviewed-by: QATEST Reviewed-by: Jonas ABERG --- arch/arm/lib/delay.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/lib/delay.c b/arch/arm/lib/delay.c index f24c956e4fd..b8d636e8ef8 100644 --- a/arch/arm/lib/delay.c +++ b/arch/arm/lib/delay.c @@ -76,6 +76,6 @@ EXPORT_SYMBOL(__const_udelay); */ void __udelay(unsigned long usecs) { - __const_udelay(usecs * ((2199023*HZ)>>11)); + __const_udelay(usecs * ((2199023UL*HZ)>>11)); } EXPORT_SYMBOL(__udelay); -- cgit v1.2.3 From 1ac0c37359cacaecb5527985133dc3ca0c92feda Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 10 May 2011 11:07:02 +0200 Subject: i2c-nomadik: Reset the hw after status check In case of I2C timeout, reset the HW only after the HW status is read, otherwise the staus will be lost. ST-Ericsson ID: ER334949 Change-Id: I93295122fa38d0ea82780d54af7f5de1fa099369 Signed-off-by: Virupax Sadashivpetimath Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20986 Reviewed-by: Jonas ABERG Reviewed-by: Srinidhi KASAGAR Conflicts: drivers/i2c/busses/i2c-nomadik.c --- drivers/i2c/busses/i2c-nomadik.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index ea04acb2f41..3cfad094612 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -429,10 +429,9 @@ static int read_i2c(struct nmk_i2c_dev *dev) } if (timeout == 0) { - /* controller has timedout, re-init the h/w */ + /* Controller timed out */ dev_err(&dev->pdev->dev, "Read from Slave 0x%x timed out\n", dev->cli.slave_adr); - (void) init_hw(dev); status = -ETIMEDOUT; } return status; @@ -517,10 +516,9 @@ static int write_i2c(struct nmk_i2c_dev *dev) } if (timeout == 0) { - /* controller has timedout, re-init the h/w */ + /* Controller timed out */ dev_err(&dev->pdev->dev, "Write to slave 0x%x timed out\n", dev->cli.slave_adr); - (void) init_hw(dev); status = -ETIMEDOUT; } -- cgit v1.2.3 From c40d2941861b7eac6d1671d9ffc9f6761915f838 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 12 Oct 2011 15:11:01 +0200 Subject: U5500 : AB5500 Core driver update AB5500 core driver is updated for cut1.0. It includes code clean up. ST-Ericsson Linux next: - ST-Ericsson ID: WP257121 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I1f61561836acc12cdd970cb9f4d7e79ab3901e95 Signed-off-by: Bibek Basu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/20844 Reviewed-by: Vijaya Kumar K-1 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR Conflicts: arch/arm/mach-ux500/board-u5500.c drivers/mfd/ab5500-core.c Conflicts: drivers/regulator/ab5500.c --- drivers/mfd/ab5500-core.c | 2322 ++++++++++++++++++------------------- drivers/rtc/rtc-ab.c | 1 + include/linux/mfd/abx500.h | 55 +- include/linux/mfd/abx500/ab5500.h | 140 +++ 4 files changed, 1252 insertions(+), 1266 deletions(-) create mode 100644 include/linux/mfd/abx500/ab5500.h diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 45c3f34f313..df61dff7968 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2010 ST-Ericsson + * Copyright (C) 2007-2011 ST-Ericsson * License terms: GNU General Public License (GPL) version 2 * Low-level core for exclusive access to the AB5500 IC on the I2C bus * and some basic chip-configuration. @@ -8,6 +8,7 @@ * Author: Mattias Wallin * Author: Rickard Andersson * Author: Karl Komierowski + * Author: Bibek Basu */ #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +34,8 @@ #define AB5500_NAME_STRING "ab5500" #define AB5500_ID_FORMAT_STRING "AB5500 %s" #define AB5500_NUM_EVENT_REG 23 - +#define AB5500_IT_LATCH0_REG 0x40 +#define AB5500_IT_MASK0_REG 0x60 /* These are the only registers inside AB5500 used in this main file */ /* Read/write operation values. */ @@ -47,45 +50,6 @@ #define AB5500_MASK_END (0x79) #define AB5500_CHIP_ID (0x20) -#define AB5500_IT_LATCH0_REG 0x40 -#define AB5500_IT_MASK0_REG 0x60 -#define AB5500_NUM_IRQ_REGS 23 -#define AB5500_NR_IRQS (23 * 8) - -/** - * struct ab5500 - * @access_mutex: lock out concurrent accesses to the AB registers - * @dev: a pointer to the device struct for this chip driver - * @ab5500_irq: the analog baseband irq - * @irq_base: the platform configuration irq base for subdevices - * @chip_name: name of this chip variant - * @chip_id: 8 bit chip ID for this chip variant - * @irq_lock: a lock to protect the mask - * @abb_events: a local bit mask of the prcmu wakeup events - * @event_mask: a local copy of the mask event registers - * @last_event_mask: a copy of the last event_mask written to hardware - * @startup_events: a copy of the first reading of the event registers - * @startup_events_read: whether the first events have been read - */ -struct ab5500 { - struct mutex access_mutex; - struct device *dev; - unsigned int ab5500_irq; - unsigned int irq_base; - char chip_name[32]; - u8 chip_id; - struct mutex irq_lock; - u32 abb_events; - u8 mask[AB5500_NUM_IRQ_REGS]; - u8 oldmask[AB5500_NUM_IRQ_REGS]; - u8 startup_events[AB5500_NUM_IRQ_REGS]; - bool startup_events_read; -#ifdef CONFIG_DEBUG_FS - unsigned int debug_bank; - unsigned int debug_address; -#endif -}; - /** * struct ab5500_bank * @slave_addr: I2C slave_addr found in AB5500 specification @@ -97,23 +61,28 @@ struct ab5500_bank { }; static const struct ab5500_bank bankinfo[AB5500_NUM_BANKS] = { - [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = {0x4A, "VIT_IO_I2C_CLK_TST_OTP"}, - [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = {0x4B, "VDDDIG_IO_I2C_CLK_TST"}, - [AB5500_BANK_VDENC] = {0x06, "VDENC"}, - [AB5500_BANK_SIM_USBSIM] = {0x04, "SIM_USBSIM"}, - [AB5500_BANK_LED] = {0x10, "LED"}, - [AB5500_BANK_ADC] = {0x0A, "ADC"}, - [AB5500_BANK_RTC] = {0x0F, "RTC"}, - [AB5500_BANK_STARTUP] = {0x03, "STARTUP"}, - [AB5500_BANK_DBI_ECI] = {0x07, "DBI-ECI"}, - [AB5500_BANK_CHG] = {0x0B, "CHG"}, - [AB5500_BANK_FG_BATTCOM_ACC] = {0x0C, "FG_BATCOM_ACC"}, - [AB5500_BANK_USB] = {0x05, "USB"}, - [AB5500_BANK_IT] = {0x0E, "IT"}, - [AB5500_BANK_VIBRA] = {0x02, "VIBRA"}, - [AB5500_BANK_AUDIO_HEADSETUSB] = {0x0D, "AUDIO_HEADSETUSB"}, + [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { + AB5500_ADDR_VIT_IO_I2C_CLK_TST_OTP, "VIT_IO_I2C_CLK_TST_OTP"}, + [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { + AB5500_ADDR_VDDDIG_IO_I2C_CLK_TST, "VDDDIG_IO_I2C_CLK_TST"}, + [AB5500_BANK_VDENC] = {AB5500_ADDR_VDENC, "VDENC"}, + [AB5500_BANK_SIM_USBSIM] = {AB5500_ADDR_SIM_USBSIM, "SIM_USBSIM"}, + [AB5500_BANK_LED] = {AB5500_ADDR_LED, "LED"}, + [AB5500_BANK_ADC] = {AB5500_ADDR_ADC, "ADC"}, + [AB5500_BANK_RTC] = {AB5500_ADDR_RTC, "RTC"}, + [AB5500_BANK_STARTUP] = {AB5500_ADDR_STARTUP, "STARTUP"}, + [AB5500_BANK_DBI_ECI] = {AB5500_ADDR_DBI_ECI, "DBI-ECI"}, + [AB5500_BANK_CHG] = {AB5500_ADDR_CHG, "CHG"}, + [AB5500_BANK_FG_BATTCOM_ACC] = { + AB5500_ADDR_FG_BATTCOM_ACC, "FG_BATCOM_ACC"}, + [AB5500_BANK_USB] = {AB5500_ADDR_USB, "USB"}, + [AB5500_BANK_IT] = {AB5500_ADDR_IT, "IT"}, + [AB5500_BANK_VIBRA] = {AB5500_ADDR_VIBRA, "VIBRA"}, + [AB5500_BANK_AUDIO_HEADSETUSB] = { + AB5500_ADDR_AUDIO_HEADSETUSB, "AUDIO_HEADSETUSB"}, }; + /** * struct ab5500_reg_range * @first: the first address of the range @@ -157,1350 +126,1283 @@ struct ab5500_i2c_banks { */ #define NO_RANGE {.count = 0, .range = NULL,} - -static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { - [AB5500_DEVID_ADC] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges[]) { - { - .bankid = AB5500_BANK_ADC, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x20, - .last = 0x58, - .perm = AB5500_PERM_RW, - }, - }, +static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { + [AB5500_BANK_LED] = { + .bankid = AB5500_BANK_LED, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0C, + .perm = AB5500_PERM_RW, }, - }, - }, - [AB5500_DEVID_LEDS] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges[]) { - { - .bankid = AB5500_BANK_LED, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x0C, - .perm = AB5500_PERM_RW, - }, - }, }, - }, }, - /* What registers should this device access?*/ - [AB5500_DEVID_POWER] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges[]) { - { - .bankid = AB5500_BANK_CHG, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x03, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x10, - .last = 0x30, - .perm = AB5500_PERM_RW, - }, - }, + [AB5500_BANK_ADC] = { + .bankid = AB5500_BANK_ADC, + .nranges = 6, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x1F, + .last = 0x22, + .perm = AB5500_PERM_RO, }, - }, - }, - [AB5500_DEVID_REGULATORS] = { - .nbanks = 2, - .bank = (struct ab5500_i2c_ranges[]) { - { - .bankid = AB5500_BANK_STARTUP, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x50, - .last = 0xE0, - .perm = AB5500_PERM_RW, - }, - }, - }, - { - .bankid = AB5500_BANK_SIM_USBSIM, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x14, - .last = 0x14, - .perm = AB5500_PERM_RW, - }, - }, + { + .first = 0x23, + .last = 0x24, + .perm = AB5500_PERM_RW, }, - }, - }, - [AB5500_DEVID_SIM] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges[]) { - { - .bankid = AB5500_BANK_SIM_USBSIM, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x13, - .last = 0x19, - .perm = AB5500_PERM_RW, - }, - }, + { + .first = 0x26, + .last = 0x2D, + .perm = AB5500_PERM_RO, }, - }, - }, - [AB5500_DEVID_RTC] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges[]) { - { - .bankid = AB5500_BANK_RTC, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x0C, - .perm = AB5500_PERM_RW, - }, - }, + { + .first = 0x2F, + .last = 0x34, + .perm = AB5500_PERM_RW, }, - }, - }, - [AB5500_DEVID_CHARGER] = { - .nbanks = 2, - .bank = (struct ab5500_i2c_ranges[]) { - { - .bankid = AB5500_BANK_CHG, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x10, - .last = 0x18, - .perm = AB5500_PERM_RW, - }, - }, - }, - { - .bankid = AB5500_BANK_ADC, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x1F, - .last = 0x58, - .perm = AB5500_PERM_RW, - }, - }, + { + .first = 0x37, + .last = 0x57, + .perm = AB5500_PERM_RW, }, - }, - }, - [AB5500_DEVID_FUELGAUGE] = { - .nbanks = 2, - .bank = (struct ab5500_i2c_ranges[]) { - { - .bankid = AB5500_BANK_FG_BATTCOM_ACC, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x10, - .perm = AB5500_PERM_RW, - }, - }, - }, - { - .bankid = AB5500_BANK_ADC, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x20, - .last = 0x58, - .perm = AB5500_PERM_RW, - }, - }, + { + .first = 0x58, + .last = 0x58, + .perm = AB5500_PERM_RO, }, }, }, - [AB5500_DEVID_VIBRATOR] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges[]) { - { - .bankid = AB5500_BANK_VIBRA, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x10, - .last = 0x13, - .perm = AB5500_PERM_RW, - }, - }, + [AB5500_BANK_RTC] = { + .bankid = AB5500_BANK_RTC, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + .perm = AB5500_PERM_RW, }, - }, - }, - [AB5500_DEVID_CODEC] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges[]) { { - .bankid = AB5500_BANK_AUDIO_HEADSETUSB, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x48, - .perm = AB5500_PERM_RW, - }, - - }, + .first = 0x06, + .last = 0x0C, + .perm = AB5500_PERM_RW, }, }, }, -}; - -#define AB5500_IRQ(bank, bit) ((bank) * 8 + (bit)) - -/* I appologize for the resource names beeing a mix of upper case - * and lower case but I want them to be exact as the documentation */ -static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { - [AB5500_DEVID_LEDS] = { - .name = "ab5500-leds", - .id = AB5500_DEVID_LEDS, - }, - [AB5500_DEVID_POWER] = { - .name = "ab5500-power", - .id = AB5500_DEVID_POWER, - }, - [AB5500_DEVID_REGULATORS] = { - .name = "ab5500-regulator", - .id = AB5500_DEVID_REGULATORS, - }, - [AB5500_DEVID_SIM] = { - .name = "ab5500-sim", - .id = AB5500_DEVID_SIM, - .num_resources = 1, - .resources = (struct resource[]) { + [AB5500_BANK_STARTUP] = { + .bankid = AB5500_BANK_STARTUP, + .nranges = 12, + .range = (struct ab5500_reg_range[]) { { - .name = "SIMOFF", - .flags = IORESOURCE_IRQ, - .start = 16, /*rising*/ - .end = 17, /*falling*/ + .first = 0x00, + .last = 0x01, + .perm = AB5500_PERM_RW, }, - }, - }, - [AB5500_DEVID_RTC] = { - .name = "ab5500-rtc", - .id = AB5500_DEVID_RTC, - .num_resources = 1, - .resources = (struct resource[]) { { - .name = "RTC_Alarm", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(1, 7), - .end = AB5500_IRQ(1, 7), - } - }, - }, - [AB5500_DEVID_CHARGER] = { - .name = "ab5500-charger", - .id = AB5500_DEVID_CHARGER, - }, - [AB5500_DEVID_ADC] = { - .name = "ab5500-adc", - .id = AB5500_DEVID_ADC, - .num_resources = 10, - .resources = (struct resource[]) { + .first = 0x1F, + .last = 0x1F, + .perm = AB5500_PERM_RW, + }, { - .name = "TRIGGER-0", - .flags = IORESOURCE_IRQ, - .start = 0, - .end = 0, + .first = 0x2E, + .last = 0x2E, + .perm = AB5500_PERM_RO, }, { - .name = "TRIGGER-1", - .flags = IORESOURCE_IRQ, - .start = 1, - .end = 1, + .first = 0x2F, + .last = 0x30, + .perm = AB5500_PERM_RW, }, { - .name = "TRIGGER-2", - .flags = IORESOURCE_IRQ, - .start = 2, - .end = 2, + .first = 0x50, + .last = 0x51, + .perm = AB5500_PERM_RW, }, { - .name = "TRIGGER-3", - .flags = IORESOURCE_IRQ, - .start = 3, - .end = 3, + .first = 0x60, + .last = 0x61, + .perm = AB5500_PERM_RW, }, { - .name = "TRIGGER-4", - .flags = IORESOURCE_IRQ, - .start = 4, - .end = 4, + .first = 0x66, + .last = 0x8A, + .perm = AB5500_PERM_RW, }, { - .name = "TRIGGER-5", - .flags = IORESOURCE_IRQ, - .start = 5, - .end = 5, + .first = 0x8C, + .last = 0x96, + .perm = AB5500_PERM_RW, }, { - .name = "TRIGGER-6", - .flags = IORESOURCE_IRQ, - .start = 6, - .end = 6, + .first = 0xAA, + .last = 0xB4, + .perm = AB5500_PERM_RW, }, { - .name = "TRIGGER-7", - .flags = IORESOURCE_IRQ, - .start = 7, - .end = 7, + .first = 0xB7, + .last = 0xBF, + .perm = AB5500_PERM_RW, }, { - .name = "TRIGGER-VBAT-TXON", - .flags = IORESOURCE_IRQ, - .start = 9, - .end = 9, + .first = 0xC1, + .last = 0xCA, + .perm = AB5500_PERM_RW, }, { - .name = "TRIGGER-VBAT", - .flags = IORESOURCE_IRQ, - .start = 8, - .end = 8, + .first = 0xD3, + .last = 0xE0, + .perm = AB5500_PERM_RW, }, }, }, - [AB5500_DEVID_FUELGAUGE] = { - .name = "ab5500-fuelgauge", - .id = AB5500_DEVID_FUELGAUGE, - .num_resources = 6, - .resources = (struct resource[]) { - { - .name = "Batt_attach", - .flags = IORESOURCE_IRQ, - .start = 61, - .end = 61, - }, + [AB5500_BANK_DBI_ECI] = { + .bankid = AB5500_BANK_DBI_ECI, + .nranges = 3, + .range = (struct ab5500_reg_range[]) { { - .name = "Batt_removal", - .flags = IORESOURCE_IRQ, - .start = 62, - .end = 62, + .first = 0x00, + .last = 0x07, + .perm = AB5500_PERM_RW, }, { - .name = "UART_framing", - .flags = IORESOURCE_IRQ, - .start = 63, - .end = 63, + .first = 0x10, + .last = 0x10, + .perm = AB5500_PERM_RW, }, { - .name = "UART_overrun", - .flags = IORESOURCE_IRQ, - .start = 64, - .end = 64, + .first = 0x13, + .last = 0x13, + .perm = AB5500_PERM_RW, }, + }, + }, + [AB5500_BANK_CHG] = { + .bankid = AB5500_BANK_CHG, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { { - .name = "UART_Rdy_RX", - .flags = IORESOURCE_IRQ, - .start = 65, - .end = 65, + .first = 0x11, + .last = 0x11, + .perm = AB5500_PERM_RO, }, { - .name = "UART_Rdy_TX", - .flags = IORESOURCE_IRQ, - .start = 66, - .end = 66, + .first = 0x12, + .last = 0x1B, + .perm = AB5500_PERM_RW, }, }, }, - [AB5500_DEVID_VIBRATOR] = { - .name = "ab5500-vibrator", - .id = AB5500_DEVID_VIBRATOR, - }, - [AB5500_DEVID_CODEC] = { - .name = "ab5500-codec", - .id = AB5500_DEVID_CODEC, - .num_resources = 3, - .resources = (struct resource[]) { - { - .name = "audio_spkr1_ovc", - .flags = IORESOURCE_IRQ, - .start = 77, - .end = 77, - }, + [AB5500_BANK_FG_BATTCOM_ACC] = { + .bankid = AB5500_BANK_FG_BATTCOM_ACC, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { { - .name = "audio_plllocked", - .flags = IORESOURCE_IRQ, - .start = 78, - .end = 78, + .first = 0x00, + .last = 0x0B, + .perm = AB5500_PERM_RO, }, { - .name = "audio_spkr2_ovc", - .flags = IORESOURCE_IRQ, - .start = 140, - .end = 140, + .first = 0x0C, + .last = 0x10, + .perm = AB5500_PERM_RW, }, }, }, - [AB5500_DEVID_USB] = { - .name = "ab5500-usb", - .id = AB5500_DEVID_USB, - .num_resources = 35, - .resources = (struct resource[]) { + [AB5500_BANK_USB] = { + .bankid = AB5500_BANK_USB, + .nranges = 12, + .range = (struct ab5500_reg_range[]) { { - .name = "DCIO", - .flags = IORESOURCE_IRQ, - .start = 67, - .end = 68, + .first = 0x01, + .last = 0x01, + .perm = AB5500_PERM_RW, }, { - .name = "VBUS", - .flags = IORESOURCE_IRQ, - .start = 69, - .end = 70, + .first = 0x80, + .last = 0x83, + .perm = AB5500_PERM_RW, }, { - .name = "CHGstate_10_PCVBUSchg", - .flags = IORESOURCE_IRQ, - .start = 71, - .end = 71, + .first = 0x87, + .last = 0x8A, + .perm = AB5500_PERM_RW, }, { - .name = "DCIOreverse_ovc", - .flags = IORESOURCE_IRQ, - .start = 72, - .end = 72, + .first = 0x8B, + .last = 0x8B, + .perm = AB5500_PERM_RO, }, { - .name = "USBCharDetDone", - .flags = IORESOURCE_IRQ, - .start = 73, - .end = 73, + .first = 0x91, + .last = 0x92, + .perm = AB5500_PERM_RO, }, { - .name = "DCIO_no_limit", - .flags = IORESOURCE_IRQ, - .start = 74, - .end = 74, + .first = 0x93, + .last = 0x93, + .perm = AB5500_PERM_RW, }, { - .name = "USB_suspend", - .flags = IORESOURCE_IRQ, - .start = 75, - .end = 75, + .first = 0x94, + .last = 0x94, + .perm = AB5500_PERM_RO, }, { - .name = "DCIOreverse_fwdcurrent", - .flags = IORESOURCE_IRQ, - .start = 76, - .end = 76, + .first = 0xA8, + .last = 0xB0, + .perm = AB5500_PERM_RO, }, { - .name = "Vbus_Imeasmax_change", - .flags = IORESOURCE_IRQ, - .start = 79, - .end = 80, + .first = 0xB2, + .last = 0xB2, + .perm = AB5500_PERM_RO, }, { - .name = "OVV", - .flags = IORESOURCE_IRQ, - .start = 117, - .end = 117, + .first = 0xB4, + .last = 0xBC, + .perm = AB5500_PERM_RO, }, { - .name = "USBcharging_NOTok", - .flags = IORESOURCE_IRQ, - .start = 123, - .end = 123, + .first = 0xBF, + .last = 0xBF, + .perm = AB5500_PERM_RO, }, { - .name = "usb_adp_sensoroff", - .flags = IORESOURCE_IRQ, - .start = 126, - .end = 126, + .first = 0xC1, + .last = 0xC5, + .perm = AB5500_PERM_RO, }, + }, + }, + [AB5500_BANK_IT] = { + .bankid = AB5500_BANK_IT, + .nranges = 4, + .range = (struct ab5500_reg_range[]) { { - .name = "usb_adp_probeplug", - .flags = IORESOURCE_IRQ, - .start = 127, - .end = 127, + .first = 0x00, + .last = 0x02, + .perm = AB5500_PERM_RO, }, { - .name = "usb_adp_sinkerror", - .flags = IORESOURCE_IRQ, - .start = 128, - .end = 128, + .first = 0x20, + .last = 0x36, + .perm = AB5500_PERM_RO, }, { - .name = "usb_adp_sourceerror", - .flags = IORESOURCE_IRQ, - .start = 129, - .end = 129, + .first = 0x40, + .last = 0x56, + .perm = AB5500_PERM_RO, }, { - .name = "usb_idgnd", - .flags = IORESOURCE_IRQ, - .start = 130, - .end = 131, + .first = 0x60, + .last = 0x76, + .perm = AB5500_PERM_RO, }, + }, + }, + [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { + .bankid = AB5500_BANK_VDDDIG_IO_I2C_CLK_TST, + .nranges = 7, + .range = (struct ab5500_reg_range[]) { { - .name = "usb_iddetR1", - .flags = IORESOURCE_IRQ, - .start = 132, - .end = 133, + .first = 0x02, + .last = 0x02, + .perm = AB5500_PERM_RW, }, { - .name = "usb_iddetR2", - .flags = IORESOURCE_IRQ, - .start = 134, - .end = 135, + .first = 0x12, + .last = 0x12, + .perm = AB5500_PERM_RW, }, { - .name = "usb_iddetR3", - .flags = IORESOURCE_IRQ, - .start = 136, - .end = 137, + .first = 0x30, + .last = 0x34, + .perm = AB5500_PERM_RW, }, { - .name = "usb_iddetR4", - .flags = IORESOURCE_IRQ, - .start = 138, - .end = 139, + .first = 0x40, + .last = 0x44, + .perm = AB5500_PERM_RW, }, { - .name = "CharTempWindowOk", - .flags = IORESOURCE_IRQ, - .start = 143, - .end = 144, + .first = 0x50, + .last = 0x54, + .perm = AB5500_PERM_RW, }, { - .name = "USB_SprDetect", - .flags = IORESOURCE_IRQ, - .start = 145, - .end = 145, + .first = 0x60, + .last = 0x64, + .perm = AB5500_PERM_RW, }, { - .name = "usb_adp_probe_unplug", - .flags = IORESOURCE_IRQ, - .start = 146, - .end = 146, + .first = 0x70, + .last = 0x74, + .perm = AB5500_PERM_RW, }, + }, + }, + [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { + .bankid = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + .nranges = 13, + .range = (struct ab5500_reg_range[]) { { - .name = "VBUSChDrop", - .flags = IORESOURCE_IRQ, - .start = 147, - .end = 148, + .first = 0x01, + .last = 0x01, + .perm = AB5500_PERM_RW, }, { - .name = "dcio_char_rec_done", - .flags = IORESOURCE_IRQ, - .start = 149, - .end = 149, + .first = 0x02, + .last = 0x02, + .perm = AB5500_PERM_RO, }, { - .name = "Charging_stopped_by_temp", - .flags = IORESOURCE_IRQ, - .start = 150, - .end = 150, + .first = 0x0D, + .last = 0x0F, + .perm = AB5500_PERM_RW, }, { - .name = "CHGstate_11_SafeModeVBUS", - .flags = IORESOURCE_IRQ, - .start = 169, - .end = 169, + .first = 0x1C, + .last = 0x1C, + .perm = AB5500_PERM_RW, }, { - .name = "CHGstate_12_comletedVBUS", - .flags = IORESOURCE_IRQ, - .start = 170, - .end = 170, + .first = 0x1E, + .last = 0x1E, + .perm = AB5500_PERM_RW, }, { - .name = "CHGstate_13_completedVBUS", - .flags = IORESOURCE_IRQ, - .start = 171, - .end = 171, + .first = 0x20, + .last = 0x21, + .perm = AB5500_PERM_RW, }, { - .name = "CHGstate_14_FullChgDCIO", - .flags = IORESOURCE_IRQ, - .start = 172, - .end = 172, + .first = 0x25, + .last = 0x25, + .perm = AB5500_PERM_RW, }, { - .name = "CHGstate_15_SafeModeDCIO", - .flags = IORESOURCE_IRQ, - .start = 173, - .end = 173, + .first = 0x28, + .last = 0x2A, + .perm = AB5500_PERM_RW, }, { - .name = "CHGstate_16_OFFsuspendDCIO", - .flags = IORESOURCE_IRQ, - .start = 174, - .end = 174, + .first = 0x30, + .last = 0x33, + .perm = AB5500_PERM_RW, }, { - .name = "CHGstate_17_completedDCIO", - .flags = IORESOURCE_IRQ, - .start = 175, - .end = 175, + .first = 0x40, + .last = 0x43, + .perm = AB5500_PERM_RW, }, { - .name = "o_it_dcio_char_rec_notok", - .flags = IORESOURCE_IRQ, - .start = 176, - .end = 176, + .first = 0x50, + .last = 0x53, + .perm = AB5500_PERM_RW, }, { - .name = "usb_link_update", - .flags = IORESOURCE_IRQ, - .start = 177, - .end = 177, + .first = 0x60, + .last = 0x63, + .perm = AB5500_PERM_RW, }, - }, - }, - [AB5500_DEVID_OTP] = { - .name = "ab5500-otp", - .id = AB5500_DEVID_OTP, - }, - [AB5500_DEVID_VIDEO] = { - .name = "ab5500-video", - .id = AB5500_DEVID_VIDEO, - .num_resources = 1, - .resources = (struct resource[]) { { - .name = "plugTVdet", - .flags = IORESOURCE_IRQ, - .start = 111, - .end = 111, + .first = 0x70, + .last = 0x73, + .perm = AB5500_PERM_RW, }, }, }, - [AB5500_DEVID_DBIECI] = { - .name = "ab5500-dbieci", - .id = AB5500_DEVID_DBIECI, - .num_resources = 10, - .resources = (struct resource[]) { - { - .name = "COLL", - .flags = IORESOURCE_IRQ, - .start = 112, - .end = 112, - }, - { - .name = "RESERR", - .flags = IORESOURCE_IRQ, - .start = 113, - .end = 113, - }, - { - .name = "FRAERR", - .flags = IORESOURCE_IRQ, - .start = 114, - .end = 114, - }, - { - .name = "COMERR", - .flags = IORESOURCE_IRQ, - .start = 115, - .end = 115, - }, - { - .name = "BSI_indicator", - .flags = IORESOURCE_IRQ, - .start = 116, - .end = 116, - }, + [AB5500_BANK_VIBRA] = { + .bankid = AB5500_BANK_VIBRA, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { { - .name = "SPDSET", - .flags = IORESOURCE_IRQ, - .start = 118, - .end = 118, + .first = 0x10, + .last = 0x13, + .perm = AB5500_PERM_RW, }, { - .name = "DSENT", - .flags = IORESOURCE_IRQ, - .start = 119, - .end = 119, + .first = 0xFE, + .last = 0xFE, + .perm = AB5500_PERM_RW, }, + }, + }, + [AB5500_BANK_AUDIO_HEADSETUSB] = { + .bankid = AB5500_BANK_AUDIO_HEADSETUSB, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { { - .name = "DREC", - .flags = IORESOURCE_IRQ, - .start = 120, - .end = 120, + .first = 0x00, + .last = 0x48, + .perm = AB5500_PERM_RW, }, { - .name = "ACCINT", - .flags = IORESOURCE_IRQ, - .start = 121, - .end = 121, + .first = 0xEB, + .last = 0xFB, + .perm = AB5500_PERM_RW, }, + }, + }, + [AB5500_BANK_SIM_USBSIM] = { + .bankid = AB5500_BANK_SIM_USBSIM, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { { - .name = "NOPINT", - .flags = IORESOURCE_IRQ, - .start = 122, - .end = 122, + .first = 0x13, + .last = 0x19, + .perm = AB5500_PERM_RW, }, }, }, }; -/* - * This stubbed prcmu functionality should be removed when the prcmu driver - * implements it. - */ -static u8 prcmu_event_buf[AB5500_NUM_EVENT_REG]; -void prcmu_get_abb_event_buf(u8 **buf) -{ - *buf = prcmu_event_buf; -} +static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { + [AB5500_DEVID_USB] = { + .nbanks = 1, + .bank = &ab5500_reg_ranges[AB5500_BANK_USB], + }, + [AB5500_DEVID_ADC] = { + .nbanks = 1, + .bank = &ab5500_reg_ranges[AB5500_BANK_ADC], + }, + [AB5500_DEVID_LEDS] = { + .nbanks = 1, + .bank = &ab5500_reg_ranges[AB5500_BANK_LED], + }, + [AB5500_DEVID_VIDEO] = { + .nbanks = 1, + .bank = &ab5500_reg_ranges[AB5500_BANK_VDENC], + }, + [AB5500_DEVID_REGULATORS] = { + .nbanks = 2, + .bank = (struct ab5500_i2c_ranges * []) { + &ab5500_reg_ranges[AB5500_BANK_STARTUP], + &ab5500_reg_ranges[AB5500_BANK_SIM_USBSIM], + }, + }, + [AB5500_DEVID_SIM] = { + .nbanks = 1, + .bank = &ab5500_reg_ranges[AB5500_BANK_SIM_USBSIM], + }, + [AB5500_DEVID_RTC] = { + .nbanks = 1, + .bank = &ab5500_reg_ranges[AB5500_BANK_RTC], + }, + [AB5500_DEVID_CHARGER] = { + .nbanks = 1, + .bank = &ab5500_reg_ranges[AB5500_BANK_CHG], + }, + [AB5500_DEVID_FUELGAUGE] = { + .nbanks = 1, + .bank = &ab5500_reg_ranges[AB5500_BANK_FG_BATTCOM_ACC], + }, + [AB5500_DEVID_VIBRATOR] = { + .nbanks = 1, + .bank = &ab5500_reg_ranges[AB5500_BANK_VIBRA], + }, + [AB5500_DEVID_CODEC] = { + .nbanks = 1, + .bank = &ab5500_reg_ranges[AB5500_BANK_AUDIO_HEADSETUSB], + }, +}; -/* - * Functionality for getting/setting register values. - */ -static int get_register_interruptible(struct ab5500 *ab, u8 bank, u8 reg, - u8 *value) -{ - int err; - - if (bank >= AB5500_NUM_BANKS) - return -EINVAL; - - err = mutex_lock_interruptible(&ab->access_mutex); - if (err) - return err; - err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, reg, value, 1); - - mutex_unlock(&ab->access_mutex); - return err; -} - -static int get_register_page_interruptible(struct ab5500 *ab, u8 bank, - u8 first_reg, u8 *regvals, u8 numregs) -{ - int err; - - if (bank >= AB5500_NUM_BANKS) - return -EINVAL; - - err = mutex_lock_interruptible(&ab->access_mutex); - if (err) - return err; - - while (numregs) { - /* The hardware limit for get page is 4 */ - u8 curnum = min_t(u8, numregs, 4u); - - err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, - first_reg, regvals, curnum); - if (err) - goto out; - - numregs -= curnum; - first_reg += curnum; - regvals += curnum; - } - -out: - mutex_unlock(&ab->access_mutex); - return err; -} - -static int mask_and_set_register_interruptible(struct ab5500 *ab, u8 bank, - u8 reg, u8 bitmask, u8 bitvalues) -{ - int err = 0; - - if (bank >= AB5500_NUM_BANKS) - return -EINVAL; - - if (bitmask) { - u8 buf; - - err = mutex_lock_interruptible(&ab->access_mutex); - if (err) - return err; - - if (bitmask == 0xFF) /* No need to read in this case. */ - buf = bitvalues; - else { /* Read and modify the register value. */ - err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, - reg, &buf, 1); - if (err) - return err; - - buf = ((~bitmask & buf) | (bitmask & bitvalues)); - } - /* Write the new value. */ - err = db5500_prcmu_abb_write(bankinfo[bank].slave_addr, reg, - &buf, 1); - - mutex_unlock(&ab->access_mutex); - } - return err; -} - -static int -set_register_interruptible(struct ab5500 *ab, u8 bank, u8 reg, u8 value) -{ - return mask_and_set_register_interruptible(ab, bank, reg, 0xff, value); -} - -/* - * Read/write permission checking functions. - */ -static const struct ab5500_i2c_ranges *get_bankref(u8 devid, u8 bank) -{ - u8 i; - - if (devid < AB5500_NUM_DEVICES) { - for (i = 0; i < ab5500_bank_ranges[devid].nbanks; i++) { - if (ab5500_bank_ranges[devid].bank[i].bankid == bank) - return &ab5500_bank_ranges[devid].bank[i]; - } - } - return NULL; -} - -static bool page_write_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg) -{ - u8 i; /* range loop index */ - const struct ab5500_i2c_ranges *bankref; - - bankref = get_bankref(devid, bank); - if (bankref == NULL || last_reg < first_reg) - return false; - - for (i = 0; i < bankref->nranges; i++) { - if (first_reg < bankref->range[i].first) - break; - if ((last_reg <= bankref->range[i].last) && - (bankref->range[i].perm & AB5500_PERM_WR)) - return true; - } - return false; -} - -static bool reg_write_allowed(u8 devid, u8 bank, u8 reg) -{ - return page_write_allowed(devid, bank, reg, reg); -} - -static bool page_read_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg) -{ - u8 i; - const struct ab5500_i2c_ranges *bankref; - - bankref = get_bankref(devid, bank); - if (bankref == NULL || last_reg < first_reg) - return false; - - - /* Find the range (if it exists in the list) that includes first_reg. */ - for (i = 0; i < bankref->nranges; i++) { - if (first_reg < bankref->range[i].first) - return false; - if (first_reg <= bankref->range[i].last) - break; - } - /* Make sure that the entire range up to and including last_reg is - * readable. This may span several of the ranges in the list. - */ - while ((i < bankref->nranges) && - (bankref->range[i].perm & AB5500_PERM_RD)) { - if (last_reg <= bankref->range[i].last) - return true; - if ((++i >= bankref->nranges) || - (bankref->range[i].first != - (bankref->range[i - 1].last + 1))) { - break; - } - } - return false; -} - -static bool reg_read_allowed(u8 devid, u8 bank, u8 reg) -{ - return page_read_allowed(devid, bank, reg, reg); -} - - -/* - * The exported register access functionality. - */ -static int ab5500_get_chip_id(struct device *dev) -{ - struct ab5500 *ab = dev_get_drvdata(dev->parent); - - return (int)ab->chip_id; -} - -static int ab5500_mask_and_set_register_interruptible(struct device *dev, - u8 bank, u8 reg, u8 bitmask, u8 bitvalues) -{ - struct ab5500 *ab; - struct platform_device *pdev = to_platform_device(dev); - - if ((AB5500_NUM_BANKS <= bank) || - !reg_write_allowed(pdev->id, bank, reg)) - return -EINVAL; - - ab = dev_get_drvdata(dev->parent); - return mask_and_set_register_interruptible(ab, bank, reg, - bitmask, bitvalues); -} - -static int ab5500_set_register_interruptible(struct device *dev, u8 bank, - u8 reg, u8 value) -{ - return ab5500_mask_and_set_register_interruptible(dev, bank, reg, 0xFF, - value); -} - -static int ab5500_get_register_interruptible(struct device *dev, u8 bank, - u8 reg, u8 *value) -{ - struct ab5500 *ab; - struct platform_device *pdev = to_platform_device(dev); - - if ((AB5500_NUM_BANKS <= bank) || - !reg_read_allowed(pdev->id, bank, reg)) - return -EINVAL; - - ab = dev_get_drvdata(dev->parent); - return get_register_interruptible(ab, bank, reg, value); -} - -static int ab5500_get_register_page_interruptible(struct device *dev, u8 bank, - u8 first_reg, u8 *regvals, u8 numregs) -{ - struct ab5500 *ab; - struct platform_device *pdev = to_platform_device(dev); - - if ((AB5500_NUM_BANKS <= bank) || - !page_read_allowed(pdev->id, bank, - first_reg, (first_reg + numregs - 1))) - return -EINVAL; - - ab = dev_get_drvdata(dev->parent); - return get_register_page_interruptible(ab, bank, first_reg, regvals, - numregs); -} - -static int -ab5500_event_registers_startup_state_get(struct device *dev, u8 *event) -{ - struct ab5500 *ab; - - ab = dev_get_drvdata(dev->parent); - if (!ab->startup_events_read) - return -EAGAIN; /* Try again later */ - - memcpy(event, ab->startup_events, AB5500_NUM_EVENT_REG); - return 0; -} - -static int ab5500_startup_irq_enabled(struct device *dev, unsigned int irq) -{ - struct ab5500 *ab; - bool val; - - ab = get_irq_chip_data(irq); - irq -= ab->irq_base; - val = ((ab->startup_events[irq / 8] & BIT(irq % 8)) != 0); - - return val; -} - -static struct abx500_ops ab5500_ops = { - .get_chip_id = ab5500_get_chip_id, - .get_register = ab5500_get_register_interruptible, - .set_register = ab5500_set_register_interruptible, - .get_register_page = ab5500_get_register_page_interruptible, - .set_register_page = NULL, - .mask_and_set_register = ab5500_mask_and_set_register_interruptible, - .event_registers_startup_state_get = - ab5500_event_registers_startup_state_get, - .startup_irq_enabled = ab5500_startup_irq_enabled, -}; - -static irqreturn_t ab5500_irq(int irq, void *data) -{ - struct ab5500 *ab = data; - u8 i; - - /* - * TODO: use the ITMASTER registers to reduce the number of i2c reads. - */ - - for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { - int status; - u8 value; - - status = get_register_interruptible(ab, AB5500_BANK_IT, - AB5500_IT_LATCH0_REG + i, &value); - if (status < 0 || value == 0) - continue; - - do { - int bit = __ffs(value); - int line = i * 8 + bit; - - handle_nested_irq(ab->irq_base + line); - value &= ~(1 << bit); - } while (value); - } - - return IRQ_HANDLED; -} +#define AB5500_IRQ(bank, bit) ((bank) * 8 + (bit)) -#ifdef CONFIG_DEBUG_FS -static struct ab5500_i2c_ranges debug_ranges[AB5500_NUM_BANKS] = { - [AB5500_BANK_LED] = { - .nranges = 1, - .range = (struct ab5500_reg_range[]) { +/* I appologize for the resource names beeing a mix of upper case + * and lower case but I want them to be exact as the documentation */ +static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { + [AB5500_DEVID_LEDS] = { + .name = "ab5500-leds", + .id = AB5500_DEVID_LEDS, + }, + [AB5500_DEVID_POWER] = { + .name = "ab5500-power", + .id = AB5500_DEVID_POWER, + }, + [AB5500_DEVID_REGULATORS] = { + .name = "ab5500-regulators", + .id = AB5500_DEVID_REGULATORS, + }, + [AB5500_DEVID_SIM] = { + .name = "ab5500-sim", + .id = AB5500_DEVID_SIM, + .num_resources = 1, + .resources = (struct resource[]) { { - .first = 0x00, - .last = 0x0C, + .name = "SIMOFF", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 0), /*rising*/ + .end = AB5500_IRQ(2, 1), /*falling*/ }, }, }, - [AB5500_BANK_ADC] = { - .nranges = 4, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x1F, - .last = 0x24, - }, + [AB5500_DEVID_RTC] = { + .name = "ab5500-rtc", + .id = AB5500_DEVID_RTC, + .num_resources = 1, + .resources = (struct resource[]) { { - .first = 0x26, - .last = 0x2D, - }, + .name = "RTC_Alarm", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 7), + .end = AB5500_IRQ(1, 7), + } + }, + }, + [AB5500_DEVID_CHARGER] = { + .name = "ab5500-charger", + .id = AB5500_DEVID_CHARGER, + }, + [AB5500_DEVID_ADC] = { + .name = "ab5500-adc", + .id = AB5500_DEVID_ADC, + .num_resources = 10, + .resources = (struct resource[]) { { - .first = 0x2F, - .last = 0x35, + .name = "TRIGGER-0", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 0), + .end = AB5500_IRQ(0, 0), }, { - .first = 0x37, - .last = 0x58, + .name = "TRIGGER-1", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 1), + .end = AB5500_IRQ(0, 1), }, - }, - }, - [AB5500_BANK_RTC] = { - .nranges = 2, - .range = (struct ab5500_reg_range[]) { { - .first = 0x00, - .last = 0x04, + .name = "TRIGGER-2", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 2), + .end = AB5500_IRQ(0, 2), }, { - .first = 0x06, - .last = 0x0C, + .name = "TRIGGER-3", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 3), + .end = AB5500_IRQ(0, 3), }, - }, - }, - [AB5500_BANK_STARTUP] = { - .nranges = 12, - .range = (struct ab5500_reg_range[]) { { - .first = 0x00, - .last = 0x01, + .name = "TRIGGER-4", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 4), + .end = AB5500_IRQ(0, 4), }, { - .first = 0x1F, - .last = 0x1F, + .name = "TRIGGER-5", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 5), + .end = AB5500_IRQ(0, 5), }, { - .first = 0x2E, - .last = 0x30, + .name = "TRIGGER-6", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 6), + .end = AB5500_IRQ(0, 6), }, { - .first = 0x50, - .last = 0x51, + .name = "TRIGGER-7", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 7), + .end = AB5500_IRQ(0, 7), }, { - .first = 0x60, - .last = 0x61, + .name = "TRIGGER-VBAT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 8), + .end = AB5500_IRQ(0, 8), }, { - .first = 0x66, - .last = 0x8A, + .name = "TRIGGER-VBAT-TXON", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 9), + .end = AB5500_IRQ(0, 9), }, + }, + }, + [AB5500_DEVID_FUELGAUGE] = { + .name = "ab5500-fuelgauge", + .id = AB5500_DEVID_FUELGAUGE, + .num_resources = 6, + .resources = (struct resource[]) { { - .first = 0x8C, - .last = 0x96, + .name = "Batt_attach", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 5), + .end = AB5500_IRQ(7, 5), }, { - .first = 0xAA, - .last = 0xB4, + .name = "Batt_removal", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 6), + .end = AB5500_IRQ(7, 6), }, { - .first = 0xB7, - .last = 0xBF, + .name = "UART_framing", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 7), + .end = AB5500_IRQ(7, 7), }, { - .first = 0xC1, - .last = 0xCA, + .name = "UART_overrun", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 0), + .end = AB5500_IRQ(8, 0), }, { - .first = 0xD3, - .last = 0xE0, + .name = "UART_Rdy_RX", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 1), + .end = AB5500_IRQ(8, 1), }, { - .first = 0xF0, - .last = 0xF8, + .name = "UART_Rdy_TX", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 2), + .end = AB5500_IRQ(8, 2), }, }, }, - [AB5500_BANK_DBI_ECI] = { - .nranges = 3, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x07, - }, + [AB5500_DEVID_VIBRATOR] = { + .name = "ab5500-vibrator", + .id = AB5500_DEVID_VIBRATOR, + }, + [AB5500_DEVID_CODEC] = { + .name = "ab5500-codec", + .id = AB5500_DEVID_CODEC, + .num_resources = 3, + .resources = (struct resource[]) { { - .first = 0x10, - .last = 0x10, + .name = "audio_spkr1_ovc", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 5), + .end = AB5500_IRQ(9, 5), }, { - .first = 0x13, - .last = 0x13, + .name = "audio_plllocked", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 6), + .end = AB5500_IRQ(9, 6), }, - }, - }, - [AB5500_BANK_CHG] = { - .nranges = 1, - .range = (struct ab5500_reg_range[]) { { - .first = 0x11, - .last = 0x1B, + .name = "audio_spkr2_ovc", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 4), + .end = AB5500_IRQ(17, 4), }, }, }, - [AB5500_BANK_FG_BATTCOM_ACC] = { - .nranges = 5, - .range = (struct ab5500_reg_range[]) { + [AB5500_DEVID_USB] = { + .name = "ab5500-usb", + .id = AB5500_DEVID_USB, + .num_resources = 35, + .resources = (struct resource[]) { { - .first = 0x00, - .last = 0x10, + .name = "Link_Update", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(22, 1), + .end = AB5500_IRQ(22, 1), }, { - .first = 0x1A, - .last = 0x1D, + .name = "DCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 3), + .end = AB5500_IRQ(8, 4), }, { - .first = 0x20, - .last = 0x21, + .name = "VBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 5), + .end = AB5500_IRQ(8, 6), }, { - .first = 0x23, - .last = 0x24, + .name = "CHGstate_10_PCVBUSchg", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 7), + .end = AB5500_IRQ(8, 7), }, { - .first = 0xFC, - .last = 0xFE, + .name = "DCIOreverse_ovc", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 0), + .end = AB5500_IRQ(9, 0), }, - }, - }, - [AB5500_BANK_USB] = { - .nranges = 13, - .range = (struct ab5500_reg_range[]) { { - .first = 0x01, - .last = 0x01, + .name = "USBCharDetDone", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 1), + .end = AB5500_IRQ(9, 1), }, { - .first = 0x80, - .last = 0x83, + .name = "DCIO_no_limit", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 2), + .end = AB5500_IRQ(9, 2), }, { - .first = 0x87, - .last = 0x8B, + .name = "USB_suspend", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 3), + .end = AB5500_IRQ(9, 3), }, { - .first = 0x91, - .last = 0x94, + .name = "DCIOreverse_fwdcurrent", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 4), + .end = AB5500_IRQ(9, 4), }, { - .first = 0xA8, - .last = 0xB0, + .name = "Vbus_Imeasmax_change", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 5), + .end = AB5500_IRQ(9, 6), }, { - .first = 0xB2, - .last = 0xB2, + .name = "OVV", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 5), + .end = AB5500_IRQ(14, 5), }, { - .first = 0xB4, - .last = 0xBC, + .name = "USBcharging_NOTok", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 3), + .end = AB5500_IRQ(15, 3), }, { - .first = 0xBF, - .last = 0xBF, + .name = "usb_adp_sensoroff", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 6), + .end = AB5500_IRQ(15, 6), }, { - .first = 0xC1, - .last = 0xC6, + .name = "usb_adp_probeplug", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 7), + .end = AB5500_IRQ(15, 7), }, { - .first = 0xCD, - .last = 0xCD, + .name = "usb_adp_sinkerror", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 0), + .end = AB5500_IRQ(16, 6), }, { - .first = 0xD6, - .last = 0xDA, + .name = "usb_adp_sourceerror", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 1), + .end = AB5500_IRQ(16, 1), }, { - .first = 0xDC, - .last = 0xDC, + .name = "usb_idgnd", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 2), + .end = AB5500_IRQ(16, 3), }, { - .first = 0xE0, - .last = 0xE4, + .name = "usb_iddetR1", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 4), + .end = AB5500_IRQ(16, 5), }, - }, - }, - [AB5500_BANK_IT] = { - .nranges = 4, - .range = (struct ab5500_reg_range[]) { { - .first = 0x00, - .last = 0x02, + .name = "usb_iddetR2", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 6), + .end = AB5500_IRQ(16, 7), }, { - .first = 0x20, - .last = 0x36, + .name = "usb_iddetR3", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 0), + .end = AB5500_IRQ(17, 1), }, { - .first = 0x60, - .last = 0x76, + .name = "usb_iddetR4", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 2), + .end = AB5500_IRQ(17, 3), }, { - .first = 0x80, - .last = 0x80, + .name = "CharTempWindowOk", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 7), + .end = AB5500_IRQ(18, 0), }, - }, - }, - [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { - .nranges = 7, - .range = (struct ab5500_reg_range[]) { { - .first = 0x02, - .last = 0x02, + .name = "USB_SprDetect", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 1), + .end = AB5500_IRQ(18, 1), }, { - .first = 0x12, - .last = 0x12, + .name = "usb_adp_probe_unplug", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 2), + .end = AB5500_IRQ(18, 2), }, { - .first = 0x30, - .last = 0x34, + .name = "VBUSChDrop", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 3), + .end = AB5500_IRQ(18, 4), }, { - .first = 0x40, - .last = 0x44, + .name = "dcio_char_rec_done", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 5), + .end = AB5500_IRQ(18, 5), }, { - .first = 0x50, - .last = 0x54, + .name = "Charging_stopped_by_temp", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 6), + .end = AB5500_IRQ(18, 6), }, { - .first = 0x60, - .last = 0x64, + .name = "CHGstate_11_SafeModeVBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 1), + .end = AB5500_IRQ(21, 2), }, { - .first = 0x70, - .last = 0x74, + .name = "CHGstate_12_comletedVBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 2), + .end = AB5500_IRQ(21, 2), }, - }, - }, - [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { - .nranges = 12, - .range = (struct ab5500_reg_range[]) { { - .first = 0x01, - .last = 0x02, + .name = "CHGstate_13_completedVBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 3), + .end = AB5500_IRQ(21, 3), }, { - .first = 0x0D, - .last = 0x0F, + .name = "CHGstate_14_FullChgDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 4), + .end = AB5500_IRQ(21, 4), }, { - .first = 0x1C, - .last = 0x1C, + .name = "CHGstate_15_SafeModeDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 5), + .end = AB5500_IRQ(21, 5), }, { - .first = 0x1E, - .last = 0x1E, + .name = "CHGstate_16_OFFsuspendDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 6), + .end = AB5500_IRQ(21, 6), }, { - .first = 0x20, - .last = 0x21, + .name = "CHGstate_17_completedDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 7), + .end = AB5500_IRQ(21, 7), }, + }, + }, + [AB5500_DEVID_OTP] = { + .name = "ab5500-otp", + .id = AB5500_DEVID_OTP, + }, + [AB5500_DEVID_VIDEO] = { + .name = "ab5500-video", + .id = AB5500_DEVID_VIDEO, + .num_resources = 1, + .resources = (struct resource[]) { { - .first = 0x25, - .last = 0x25, + .name = "plugTVdet", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(22, 2), + .end = AB5500_IRQ(22, 2), }, + }, + }, + [AB5500_DEVID_DBIECI] = { + .name = "ab5500-dbieci", + .id = AB5500_DEVID_DBIECI, + .num_resources = 10, + .resources = (struct resource[]) { { - .first = 0x28, - .last = 0x2A, + .name = "COLL", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 0), + .end = AB5500_IRQ(14, 0), }, { - .first = 0x30, - .last = 0x33, + .name = "RESERR", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 1), + .end = AB5500_IRQ(14, 1), }, { - .first = 0x40, - .last = 0x43, + .name = "FRAERR", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 2), + .end = AB5500_IRQ(14, 2), }, { - .first = 0x50, - .last = 0x53, + .name = "COMERR", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 3), + .end = AB5500_IRQ(14, 3), }, { - .first = 0x60, - .last = 0x63, + .name = "BSI_indicator", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 4), + .end = AB5500_IRQ(14, 4), }, { - .first = 0x70, - .last = 0x73, + .name = "SPDSET", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 6), + .end = AB5500_IRQ(14, 6), }, - }, - }, - [AB5500_BANK_VIBRA] = { - .nranges = 2, - .range = (struct ab5500_reg_range[]) { { - .first = 0x10, - .last = 0x13, + .name = "DSENT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 7), + .end = AB5500_IRQ(14, 7), }, { - .first = 0xFE, - .last = 0xFE, + .name = "DREC", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 0), + .end = AB5500_IRQ(15, 0), }, - }, - }, - [AB5500_BANK_AUDIO_HEADSETUSB] = { - .nranges = 2, - .range = (struct ab5500_reg_range[]) { { - .first = 0x00, - .last = 0x48, + .name = "ACCINT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 1), + .end = AB5500_IRQ(15, 1), }, { - .first = 0xEB, - .last = 0xFB, + .name = "NOPINT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 2), + .end = AB5500_IRQ(15, 2), }, }, }, }; +/* + * This stubbed prcmu functionality should be removed when the prcmu driver + * implements it. + */ +static u8 prcmu_event_buf[AB5500_NUM_EVENT_REG]; + +void prcmu_get_abb_event_buf(u8 **buf) +{ + *buf = prcmu_event_buf; +} + +/* + * Functionality for getting/setting register values. + */ +static int get_register_interruptible(struct ab5500 *ab, u8 bank, u8 reg, + u8 *value) +{ + int err; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, reg, value, 1); + + mutex_unlock(&ab->access_mutex); + return err; +} + +static int get_register_page_interruptible(struct ab5500 *ab, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + int err; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + + while (numregs) { + /* The hardware limit for get page is 4 */ + u8 curnum = min_t(u8, numregs, 4u); + + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, + first_reg, regvals, curnum); + if (err) + goto out; + + numregs -= curnum; + first_reg += curnum; + regvals += curnum; + } + +out: + mutex_unlock(&ab->access_mutex); + return err; +} + +static int mask_and_set_register_interruptible(struct ab5500 *ab, u8 bank, + u8 reg, u8 bitmask, u8 bitvalues) +{ + int err = 0; + + if (bank >= AB5500_NUM_BANKS) + return -EINVAL; + + if (bitmask) { + u8 buf; + + err = mutex_lock_interruptible(&ab->access_mutex); + if (err) + return err; + + if (bitmask == 0xFF) /* No need to read in this case. */ + buf = bitvalues; + else { /* Read and modify the register value. */ + err = db5500_prcmu_abb_read(bankinfo[bank].slave_addr, + reg, &buf, 1); + if (err) + return err; + + buf = ((~bitmask & buf) | (bitmask & bitvalues)); + } + /* Write the new value. */ + err = db5500_prcmu_abb_write(bankinfo[bank].slave_addr, reg, + &buf, 1); + + mutex_unlock(&ab->access_mutex); + } + return err; +} + +static int +set_register_interruptible(struct ab5500 *ab, u8 bank, u8 reg, u8 value) +{ + return mask_and_set_register_interruptible(ab, bank, reg, 0xff, value); +} + +/* + * Read/write permission checking functions. + */ +static const struct ab5500_i2c_ranges *get_bankref(u8 devid, u8 bank) +{ + u8 i; + + if (devid < AB5500_NUM_DEVICES) { + for (i = 0; i < ab5500_bank_ranges[devid].nbanks; i++) { + if (ab5500_bank_ranges[devid].bank[i].bankid == bank) + return &ab5500_bank_ranges[devid].bank[i]; + } + } + return NULL; +} + +static bool page_write_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg) +{ + u8 i; /* range loop index */ + const struct ab5500_i2c_ranges *bankref; + + bankref = get_bankref(devid, bank); + if (bankref == NULL || last_reg < first_reg) + return false; + + for (i = 0; i < bankref->nranges; i++) { + if (first_reg < bankref->range[i].first) + break; + if ((last_reg <= bankref->range[i].last) && + (bankref->range[i].perm & AB5500_PERM_WR)) + return true; + } + return false; +} + +static bool reg_write_allowed(u8 devid, u8 bank, u8 reg) +{ + return page_write_allowed(devid, bank, reg, reg); +} + +static bool page_read_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg) +{ + u8 i; + const struct ab5500_i2c_ranges *bankref; + + bankref = get_bankref(devid, bank); + if (bankref == NULL || last_reg < first_reg) + return false; + + + /* Find the range (if it exists in the list) that includes first_reg. */ + for (i = 0; i < bankref->nranges; i++) { + if (first_reg < bankref->range[i].first) + return false; + if (first_reg <= bankref->range[i].last) + break; + } + /* Make sure that the entire range up to and including last_reg is + * readable. This may span several of the ranges in the list. + */ + while ((i < bankref->nranges) && + (bankref->range[i].perm & AB5500_PERM_RD)) { + if (last_reg <= bankref->range[i].last) + return true; + if ((++i >= bankref->nranges) || + (bankref->range[i].first != + (bankref->range[i - 1].last + 1))) { + break; + } + } + return false; +} + +static bool reg_read_allowed(u8 devid, u8 bank, u8 reg) +{ + return page_read_allowed(devid, bank, reg, reg); +} + + +/* + * The exported register access functionality. + */ +static int ab5500_get_chip_id(struct device *dev) +{ + struct ab5500 *ab = dev_get_drvdata(dev->parent); + + return (int)ab->chip_id; +} + +static int ab5500_mask_and_set_register_interruptible(struct device *dev, + u8 bank, u8 reg, u8 bitmask, u8 bitvalues) +{ + struct ab5500 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB5500_NUM_BANKS <= bank) || + !reg_write_allowed(pdev->id, bank, reg)) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return mask_and_set_register_interruptible(ab, bank, reg, + bitmask, bitvalues); +} + +static int ab5500_set_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 value) +{ + return ab5500_mask_and_set_register_interruptible(dev, bank, reg, 0xFF, + value); +} + +static int ab5500_get_register_interruptible(struct device *dev, u8 bank, + u8 reg, u8 *value) +{ + struct ab5500 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB5500_NUM_BANKS <= bank) || + !reg_read_allowed(pdev->id, bank, reg)) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return get_register_interruptible(ab, bank, reg, value); +} + +static int ab5500_get_register_page_interruptible(struct device *dev, u8 bank, + u8 first_reg, u8 *regvals, u8 numregs) +{ + struct ab5500 *ab; + struct platform_device *pdev = to_platform_device(dev); + + if ((AB5500_NUM_BANKS <= bank) || + !page_read_allowed(pdev->id, bank, + first_reg, (first_reg + numregs - 1))) + return -EINVAL; + + ab = dev_get_drvdata(dev->parent); + return get_register_page_interruptible(ab, bank, first_reg, regvals, + numregs); +} + +static int +ab5500_event_registers_startup_state_get(struct device *dev, u8 *event) +{ + struct ab5500 *ab; + + ab = dev_get_drvdata(dev->parent); + if (!ab->startup_events_read) + return -EAGAIN; /* Try again later */ + + memcpy(event, ab->startup_events, AB5500_NUM_EVENT_REG); + return 0; +} + +static int ab5500_startup_irq_enabled(struct device *dev, unsigned int irq) +{ + struct ab5500 *ab; + bool val; + + ab = get_irq_chip_data(irq); + irq -= ab->irq_base; + val = ((ab->startup_events[irq / 8] & BIT(irq % 8)) != 0); + + return val; +} + +static struct abx500_ops ab5500_ops = { + .get_chip_id = ab5500_get_chip_id, + .get_register = ab5500_get_register_interruptible, + .set_register = ab5500_set_register_interruptible, + .get_register_page = ab5500_get_register_page_interruptible, + .set_register_page = NULL, + .mask_and_set_register = ab5500_mask_and_set_register_interruptible, + .event_registers_startup_state_get = + ab5500_event_registers_startup_state_get, + .startup_irq_enabled = ab5500_startup_irq_enabled, +}; + +static irqreturn_t ab5500_irq(int irq, void *data) +{ + struct ab5500 *ab = data; + u8 i; + + /* + * TODO: use the ITMASTER registers to reduce the number of i2c reads. + */ + + for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { + int status; + u8 value; + + status = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + i, &value); + if (status < 0 || value == 0) + continue; + + do { + int bit = __ffs(value); + int line = i * 8 + bit; + + handle_nested_irq(ab->irq_base + line); + value &= ~(1 << bit); + } while (value); + } + + return IRQ_HANDLED; +} + +#ifdef CONFIG_DEBUG_FS static int ab5500_registers_print(struct seq_file *s, void *p) { struct ab5500 *ab = s->private; @@ -1508,36 +1410,39 @@ static int ab5500_registers_print(struct seq_file *s, void *p) u8 bank = (u8)ab->debug_bank; seq_printf(s, AB5500_NAME_STRING " register values:\n"); - - seq_printf(s, " bank %u, %s (0x%x):\n", bank, - bankinfo[bank].name, - bankinfo[bank].slave_addr); - for (i = 0; i < debug_ranges[bank].nranges; i++) { - u8 reg; - int err; - - for (reg = debug_ranges[bank].range[i].first; - reg <= debug_ranges[bank].range[i].last; - reg++) { - u8 value; - - err = get_register_interruptible(ab, bank, reg, - &value); - if (err < 0) { - dev_err(ab->dev, "get_reg failed %d, bank 0x%x" - ", reg 0x%x\n", err, bank, reg); - return err; - } - - err = seq_printf(s, " [%d/0x%02X]: 0x%02X\n", bank, - reg, value); - if (err < 0) { - dev_err(ab->dev, "seq_printf overflow\n"); - /* - * Error is not returned here since - * the output is wanted in any case - */ - return 0; + for (bank = 0; bank < AB5500_NUM_BANKS; bank++) { + seq_printf(s, " bank %u, %s (0x%x):\n", bank, + bankinfo[bank].name, + bankinfo[bank].slave_addr); + for (i = 0; i < ab5500_reg_ranges[bank].nranges; i++) { + u8 reg; + int err; + + for (reg = ab5500_reg_ranges[bank].range[i].first; + reg <= ab5500_reg_ranges[bank].range[i].last; + reg++) { + u8 value; + + err = get_register_interruptible(ab, bank, reg, + &value); + if (err < 0) { + dev_err(ab->dev, "get_reg failed %d" + "bank 0x%x reg 0x%x\n", + err, bank, reg); + return err; + } + + err = seq_printf(s, "[%d/0x%02X]: 0x%02X\n", + bank, reg, value); + if (err < 0) { + dev_err(ab->dev, + "seq_printf overflow\n"); + /* + * Error is not returned here since + * the output is wanted in any case + */ + return 0; + } } } } @@ -1800,37 +1705,6 @@ static inline void ab5500_remove_debugfs(void) } #endif -/* - * Basic set-up, datastructure creation/destruction and I2C interface. - * This sets up a default config in the AB5500 chip so that it - * will work as expected. - */ -static int __init ab5500_setup(struct ab5500 *ab, - struct abx500_init_settings *settings, unsigned int size) -{ - int err = 0; - int i; - - for (i = 0; i < size; i++) { - err = mask_and_set_register_interruptible(ab, - settings[i].bank, - settings[i].reg, - 0xFF, settings[i].setting); - if (err) - goto exit_no_setup; - - /* If event mask register update the event mask in ab5500 */ - if ((settings[i].bank == AB5500_BANK_IT) && - (AB5500_MASK_BASE <= settings[i].reg) && - (settings[i].reg <= AB5500_MASK_END)) { - ab->mask[settings[i].reg - AB5500_MASK_BASE] = - settings[i].setting; - } - } -exit_no_setup: - return err; -} - static void ab5500_irq_mask(struct irq_data *data) { struct ab5500 *ab = irq_data_get_irq_chip_data(data); @@ -1909,6 +1783,46 @@ static const struct ab_family_id ids[] __initdata = { } }; +static int ab5500_irq_init(struct ab5500 *ab) +{ + struct ab5500_platform_data *ab5500_plf_data = + dev_get_platdata(ab->dev); + int i; + unsigned int irq; + + for (i = 0; i < ab5500_plf_data->irq.count; i++) { + + irq = ab5500_plf_data->irq.base + i; + set_irq_chip_data(irq, ab); + set_irq_chip_and_handler(irq, &ab5500_irq_chip, + handle_simple_irq); + set_irq_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + } + return 0; +} + +static void ab5500_irq_remove(struct ab5500 *ab) +{ + struct ab5500_platform_data *ab5500_plf_data = + dev_get_platdata(ab->dev); + int i; + unsigned int irq; + + for (i = 0; i < ab5500_plf_data->irq.count; i++) { + irq = ab5500_plf_data->irq.base + i; +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + set_irq_chip_and_handler(irq, NULL, NULL); + set_irq_chip_data(irq, NULL); + } +} + static int __init ab5500_probe(struct platform_device *pdev) { struct ab5500 *ab; @@ -1962,32 +1876,10 @@ static int __init ab5500_probe(struct platform_device *pdev) /* Readout ab->starup_events when prcmu driver is in place */ ab->startup_events[0] = 0; - err = ab5500_setup(ab, ab5500_plf_data->init_settings, - ab5500_plf_data->init_settings_sz); - if (err) { - dev_err(&pdev->dev, "ab5500_setup error\n"); - goto exit_no_setup; - } - - for (i = 0; i < ab5500_plf_data->irq.count; i++) { - unsigned int irq; - - irq = ab5500_plf_data->irq.base + i; - set_irq_chip_data(irq, ab); - set_irq_chip_and_handler(irq, &ab5500_irq_chip, - handle_simple_irq); - set_irq_nested_thread(irq, 1); -#ifdef CONFIG_ARM - set_irq_flags(irq, IRQF_VALID); -#else - set_irq_noprobe(irq); -#endif - } - res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { dev_err(&pdev->dev, "ab5500_platform_get_resource error\n"); - goto exit_no_irq; + goto exit_no_detect; } ab->ab5500_irq = res->start; @@ -1999,32 +1891,29 @@ static int __init ab5500_probe(struct platform_device *pdev) get_register_interruptible(ab, AB5500_BANK_IT, latchreg, &val); set_register_interruptible(ab, AB5500_BANK_IT, maskreg, 0xff); + ab->mask[i] = ab->oldmask[i] = 0xff; } - for (i = 0; i < AB5500_NUM_IRQ_REGS; i++) - ab->mask[i] = ab->oldmask[i] = 0xff; + if (ab->irq_base) { + err = ab5500_irq_init(ab); + if (err) + return err; err = request_threaded_irq(res->start, NULL, ab5500_irq, IRQF_NO_SUSPEND | IRQF_ONESHOT, "ab5500-core", ab); + if (err) + goto exit_remove_irq; + + } /* This real unpredictable IRQ is of course sampled for entropy */ rand_initialize_irq(res->start); - if (err) { - dev_err(&pdev->dev, "ab5500_request_irq error\n"); - goto exit_no_irq; - } - err = abx500_register_ops(&pdev->dev, &ab5500_ops); if (err) { dev_err(&pdev->dev, "ab5500_register ops error\n"); - goto exit_no_ops; - } - - /* Set up and register the platform devices. */ - for (i = 0; i < AB5500_NUM_DEVICES; i++) { - ab5500_devs[i].mfd_data = ab5500_plf_data->dev_data[i]; + goto exit_no_irq; } err = mfd_add_devices(&pdev->dev, 0, ab5500_devs, @@ -2032,16 +1921,18 @@ static int __init ab5500_probe(struct platform_device *pdev) ab5500_plf_data->irq.base); if (err) { dev_err(&pdev->dev, "ab5500_mfd_add_device error\n"); - goto exit_no_ops; + goto exit_no_irq; } ab5500_setup_debugfs(ab); - return 0; -exit_no_ops: exit_no_irq: -exit_no_setup: + if (ab->irq_base) { + free_irq(ab->ab5500_irq, ab); +exit_remove_irq: + ab5500_irq_remove(ab); + } exit_no_detect: kfree(ab); return err; @@ -2056,12 +1947,13 @@ static int __exit ab5500_remove(struct platform_device *pdev) * At this point, all subscribers should have unregistered * their notifiers so deactivate IRQ */ - res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); - free_irq(res->start, ab); - - mfd_remove_devices(&pdev->dev); ab5500_remove_debugfs(); - + mfd_remove_devices(&pdev->dev); + if (ab->irq_base) { + free_irq(ab->ab5500_irq, ab); + ab5500_irq_remove(ab); + } + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); kfree(ab); return 0; } diff --git a/drivers/rtc/rtc-ab.c b/drivers/rtc/rtc-ab.c index 8e595e05d99..db1992632fa 100644 --- a/drivers/rtc/rtc-ab.c +++ b/drivers/rtc/rtc-ab.c @@ -13,6 +13,7 @@ #include #include #include +#include #define AB5500_RTC_CLOCK_RATE 32768 #define AB5500_RTC 0x00 diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index a3b3a5e380b..bf8cc970b4c 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -1,12 +1,13 @@ /* - * Copyright (C) 2007-2009 ST-Ericsson AB - * License terms: GNU General Public License (GPL) version 2 + * Copyright (C) ST-Ericsson SA 2010 + * License terms: GNU General Public License v2 * AB3100 core access functions * Author: Linus Walleij * * ABX500 core access functions. * The abx500 interface is used for the Analog Baseband chip - * ab3100, ab3550, ab5500, and ab8500. + * ab3100, ab3550, ab5500 and possibly comming. It is not used for + * ab4500 and ab8500 since they are another family of chip. * * Author: Mattias Wallin * Author: Mattias Nilsson @@ -205,54 +206,6 @@ struct ab3550_platform_data { unsigned int init_settings_sz; }; -enum ab5500_devid { - AB5500_DEVID_ADC, - AB5500_DEVID_LEDS, - AB5500_DEVID_POWER, - AB5500_DEVID_REGULATORS, - AB5500_DEVID_SIM, - AB5500_DEVID_RTC, - AB5500_DEVID_CHARGER, - AB5500_DEVID_FUELGAUGE, - AB5500_DEVID_VIBRATOR, - AB5500_DEVID_CODEC, - AB5500_DEVID_USB, - AB5500_DEVID_OTP, - AB5500_DEVID_VIDEO, - AB5500_DEVID_DBIECI, - AB5500_NUM_DEVICES, -}; - -enum ab5500_banks { - AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP = 0, - AB5500_BANK_VDDDIG_IO_I2C_CLK_TST = 1, - AB5500_BANK_VDENC = 2, - AB5500_BANK_SIM_USBSIM = 3, - AB5500_BANK_LED = 4, - AB5500_BANK_ADC = 5, - AB5500_BANK_RTC = 6, - AB5500_BANK_STARTUP = 7, - AB5500_BANK_DBI_ECI = 8, - AB5500_BANK_CHG = 9, - AB5500_BANK_FG_BATTCOM_ACC = 10, - AB5500_BANK_USB = 11, - AB5500_BANK_IT = 12, - AB5500_BANK_VIBRA = 13, - AB5500_BANK_AUDIO_HEADSETUSB = 14, - AB5500_NUM_BANKS = 15, -}; - -struct ab5500_regulator_platform_data; - -struct ab5500_platform_data { - struct {unsigned int base; unsigned int count; } irq; - void *dev_data[AB5500_NUM_DEVICES]; - size_t dev_data_sz[AB5500_NUM_DEVICES]; - struct abx500_init_settings *init_settings; - unsigned int init_settings_sz; - struct ab5500_regulator_platform_data *regulator; -}; - int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, u8 value); int abx500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, diff --git a/include/linux/mfd/abx500/ab5500.h b/include/linux/mfd/abx500/ab5500.h new file mode 100644 index 00000000000..a2ac05a8ebc --- /dev/null +++ b/include/linux/mfd/abx500/ab5500.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) ST-Ericsson BLR 2011 + * + * License Terms: GNU General Public License v2 + * Author: Bibek Basu + */ +#ifndef MFD_AB5500_H +#define MFD_AB5500_H + +#include + + +enum ab5500_devid { + AB5500_DEVID_ADC, + AB5500_DEVID_LEDS, + AB5500_DEVID_POWER, + AB5500_DEVID_REGULATORS, + AB5500_DEVID_SIM, + AB5500_DEVID_RTC, + AB5500_DEVID_CHARGER, + AB5500_DEVID_FUELGAUGE, + AB5500_DEVID_VIBRATOR, + AB5500_DEVID_CODEC, + AB5500_DEVID_USB, + AB5500_DEVID_OTP, + AB5500_DEVID_VIDEO, + AB5500_DEVID_DBIECI, + AB5500_NUM_DEVICES, +}; + +enum ab5500_banks { + AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP = 0, + AB5500_BANK_VDDDIG_IO_I2C_CLK_TST = 1, + AB5500_BANK_VDENC = 2, + AB5500_BANK_SIM_USBSIM = 3, + AB5500_BANK_LED = 4, + AB5500_BANK_ADC = 5, + AB5500_BANK_RTC = 6, + AB5500_BANK_STARTUP = 7, + AB5500_BANK_DBI_ECI = 8, + AB5500_BANK_CHG = 9, + AB5500_BANK_FG_BATTCOM_ACC = 10, + AB5500_BANK_USB = 11, + AB5500_BANK_IT = 12, + AB5500_BANK_VIBRA = 13, + AB5500_BANK_AUDIO_HEADSETUSB = 14, + AB5500_NUM_BANKS = 15, +}; + +enum ab5500_banks_addr { + AB5500_ADDR_VIT_IO_I2C_CLK_TST_OTP = 0x4A, + AB5500_ADDR_VDDDIG_IO_I2C_CLK_TST = 0x4B, + AB5500_ADDR_VDENC = 0x06, + AB5500_ADDR_SIM_USBSIM = 0x04, + AB5500_ADDR_LED = 0x10, + AB5500_ADDR_ADC = 0x0A, + AB5500_ADDR_RTC = 0x0F, + AB5500_ADDR_STARTUP = 0x03, + AB5500_ADDR_DBI_ECI = 0x07, + AB5500_ADDR_CHG = 0x0B, + AB5500_ADDR_FG_BATTCOM_ACC = 0x0C, + AB5500_ADDR_USB = 0x05, + AB5500_ADDR_IT = 0x0E, + AB5500_ADDR_VIBRA = 0x02, + AB5500_ADDR_AUDIO_HEADSETUSB = 0x0D, +}; + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB5500_IT_SOURCE0_REG 0x20 +#define AB5500_IT_SOURCE1_REG 0x21 +#define AB5500_IT_SOURCE2_REG 0x22 +#define AB5500_IT_SOURCE3_REG 0x23 +#define AB5500_IT_SOURCE4_REG 0x24 +#define AB5500_IT_SOURCE5_REG 0x25 +#define AB5500_IT_SOURCE6_REG 0x26 +#define AB5500_IT_SOURCE7_REG 0x27 +#define AB5500_IT_SOURCE8_REG 0x28 +#define AB5500_IT_SOURCE9_REG 0x29 +#define AB5500_IT_SOURCE10_REG 0x2A +#define AB5500_IT_SOURCE11_REG 0x2B +#define AB5500_IT_SOURCE12_REG 0x2C +#define AB5500_IT_SOURCE13_REG 0x2D +#define AB5500_IT_SOURCE14_REG 0x2E +#define AB5500_IT_SOURCE15_REG 0x2F +#define AB5500_IT_SOURCE16_REG 0x30 +#define AB5500_IT_SOURCE17_REG 0x31 +#define AB5500_IT_SOURCE18_REG 0x32 +#define AB5500_IT_SOURCE19_REG 0x33 +#define AB5500_IT_SOURCE20_REG 0x34 +#define AB5500_IT_SOURCE21_REG 0x35 +#define AB5500_IT_SOURCE22_REG 0x36 +#define AB5500_IT_SOURCE23_REG 0x37 + +#define AB5500_NUM_IRQ_REGS 23 + +/** + * struct ab5500 + * @access_mutex: lock out concurrent accesses to the AB registers + * @dev: a pointer to the device struct for this chip driver + * @ab5500_irq: the analog baseband irq + * @irq_base: the platform configuration irq base for subdevices + * @chip_name: name of this chip variant + * @chip_id: 8 bit chip ID for this chip variant + * @irq_lock: a lock to protect the mask + * @abb_events: a local bit mask of the prcmu wakeup events + * @event_mask: a local copy of the mask event registers + * @last_event_mask: a copy of the last event_mask written to hardware + * @startup_events: a copy of the first reading of the event registers + * @startup_events_read: whether the first events have been read + */ +struct ab5500 { + struct mutex access_mutex; + struct device *dev; + unsigned int ab5500_irq; + unsigned int irq_base; + char chip_name[32]; + u8 chip_id; + struct mutex irq_lock; + u32 abb_events; + u8 mask[AB5500_NUM_IRQ_REGS]; + u8 oldmask[AB5500_NUM_IRQ_REGS]; + u8 startup_events[AB5500_NUM_IRQ_REGS]; + bool startup_events_read; +#ifdef CONFIG_DEBUG_FS + unsigned int debug_bank; + unsigned int debug_address; +#endif +}; + +struct ab5500_regulator_platform_data; +struct ab5500_platform_data { + struct {unsigned int base; unsigned int count; } irq; + struct ab5500_regulator_platform_data *regulator; + }; + + +#endif /* MFD_AB5500_H */ -- cgit v1.2.3 From 501fec638fd7772a20a3b5b1b39273d1f4ad29d1 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 12 Oct 2011 15:17:07 +0200 Subject: mfd: input: AB5500 ONSWa support ST-Ericsson Linux next: - ST-Ericsson ID: WP257121 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I19122bc76c83545ffa15e6997320eacf58b8c0f6 Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/21180 Reviewed-by: Vijaya Kumar K-1 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR Conflicts: drivers/input/misc/ab8500-ponkey.c --- drivers/mfd/ab5500-core.c | 19 +++++++++++++++++++ include/linux/mfd/abx500/ab5500.h | 1 + 2 files changed, 20 insertions(+) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index df61dff7968..6b80fdbd52a 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -1090,6 +1090,25 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, }, }, + [AB5500_DEVID_ONSWA] = { + .name = "ab5500-onswa", + .id = AB5500_DEVID_ONSWA, + .num_resources = 2, + .resources = (struct resource[]) { + { + .name = "ONSWAn_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 3), + .end = AB5500_IRQ(1, 3), + }, + { + .name = "ONSWAn_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 4), + .end = AB5500_IRQ(1, 4), + }, + }, + }, }; /* diff --git a/include/linux/mfd/abx500/ab5500.h b/include/linux/mfd/abx500/ab5500.h index a2ac05a8ebc..72c2baf1b83 100644 --- a/include/linux/mfd/abx500/ab5500.h +++ b/include/linux/mfd/abx500/ab5500.h @@ -25,6 +25,7 @@ enum ab5500_devid { AB5500_DEVID_OTP, AB5500_DEVID_VIDEO, AB5500_DEVID_DBIECI, + AB5500_DEVID_ONSWA, AB5500_NUM_DEVICES, }; -- cgit v1.2.3 From f14b43d40e9118ab3090ab9becea153a462b174f Mon Sep 17 00:00:00 2001 From: Bibek Basu Date: Thu, 28 Apr 2011 14:22:42 +0530 Subject: U5500 : ab5500 update for regulator driver regulator driver got broke due to the updated ab5500 core driver.This patch fixes that. ST-Ericsson Linux next: - ST-Ericsson ID: WP257121 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Id393e52c76c3e894f302d418f1245945d758b2fd Signed-off-by: Bibek Basu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/21828 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/mfd/ab5500-core.c | 2031 +++++++++++++++++++++++++++------------------ 1 file changed, 1225 insertions(+), 806 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 6b80fdbd52a..aef7387aaab 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -126,986 +126,908 @@ struct ab5500_i2c_banks { */ #define NO_RANGE {.count = 0, .range = NULL,} -static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { - [AB5500_BANK_LED] = { - .bankid = AB5500_BANK_LED, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x0C, - .perm = AB5500_PERM_RW, +static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { + [AB5500_DEVID_USB] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_USB, + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x01, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x80, + .last = 0x83, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x87, + .last = 0x8A, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x8B, + .last = 0x8B, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x91, + .last = 0x92, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x93, + .last = 0x93, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x94, + .last = 0x94, + .perm = AB5500_PERM_RO, + }, + { + .first = 0xA8, + .last = 0xB0, + .perm = AB5500_PERM_RO, + }, + { + .first = 0xB2, + .last = 0xB2, + .perm = AB5500_PERM_RO, + }, + { + .first = 0xB4, + .last = 0xBC, + .perm = AB5500_PERM_RO, + }, + { + .first = 0xBF, + .last = 0xBF, + .perm = AB5500_PERM_RO, + }, + { + .first = 0xC1, + .last = 0xC5, + .perm = AB5500_PERM_RO, + }, + }, }, + }, + }, + [AB5500_DEVID_ADC] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_ADC, + .nranges = 6, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x1F, + .last = 0x22, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x23, + .last = 0x24, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x26, + .last = 0x2D, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x2F, + .last = 0x34, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x37, + .last = 0x57, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x58, + .last = 0x58, + .perm = AB5500_PERM_RO, + }, + }, }, + }, }, - [AB5500_BANK_ADC] = { - .bankid = AB5500_BANK_ADC, - .nranges = 6, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x1F, - .last = 0x22, - .perm = AB5500_PERM_RO, + [AB5500_DEVID_LEDS] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_LED, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0C, + .perm = AB5500_PERM_RW, + }, + }, }, - { - .first = 0x23, - .last = 0x24, - .perm = AB5500_PERM_RW, + }, + }, + [AB5500_DEVID_VIDEO] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_VDENC, + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x08, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x09, + .last = 0x09, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x0A, + .last = 0x12, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x15, + .last = 0x19, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x1B, + .last = 0x21, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x27, + .last = 0x2C, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x41, + .last = 0x41, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x45, + .last = 0x5B, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x5D, + .last = 0x5D, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x69, + .last = 0x69, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x6C, + .last = 0x6D, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x80, + .last = 0x81, + .perm = AB5500_PERM_RW, + }, + }, }, - { - .first = 0x26, - .last = 0x2D, - .perm = AB5500_PERM_RO, + }, + }, + [AB5500_DEVID_REGULATORS] = { + .nbanks = 2, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_STARTUP, + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x1F, + .last = 0x1F, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x2E, + .last = 0x2E, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x2F, + .last = 0x30, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x50, + .last = 0x51, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x60, + .last = 0x61, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x66, + .last = 0x8A, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x8C, + .last = 0x96, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xAA, + .last = 0xB4, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xB7, + .last = 0xBF, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xC1, + .last = 0xCA, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xD3, + .last = 0xE0, + .perm = AB5500_PERM_RW, + }, + }, + }, + { + .bankid = AB5500_BANK_SIM_USBSIM, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x13, + .last = 0x19, + .perm = AB5500_PERM_RW, + }, + }, }, - { - .first = 0x2F, - .last = 0x34, - .perm = AB5500_PERM_RW, + }, + }, + [AB5500_DEVID_SIM] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_SIM_USBSIM, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x13, + .last = 0x19, + .perm = AB5500_PERM_RW, + }, + }, }, - { - .first = 0x37, - .last = 0x57, - .perm = AB5500_PERM_RW, + }, + }, + [AB5500_DEVID_RTC] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_RTC, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x06, + .last = 0x0C, + .perm = AB5500_PERM_RW, + }, + }, }, - { - .first = 0x58, - .last = 0x58, - .perm = AB5500_PERM_RO, + }, + }, + [AB5500_DEVID_CHARGER] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_CHG, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x11, + .last = 0x11, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x12, + .last = 0x1B, + .perm = AB5500_PERM_RW, + }, + }, }, }, }, - [AB5500_BANK_RTC] = { - .bankid = AB5500_BANK_RTC, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x04, - .perm = AB5500_PERM_RW, + [AB5500_DEVID_FUELGAUGE] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_FG_BATTCOM_ACC, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0B, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x0C, + .last = 0x10, + .perm = AB5500_PERM_RW, + }, + }, }, - { - .first = 0x06, - .last = 0x0C, - .perm = AB5500_PERM_RW, + }, + }, + [AB5500_DEVID_VIBRATOR] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_VIBRA, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x10, + .last = 0x13, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xFE, + .last = 0xFE, + .perm = AB5500_PERM_RW, + }, + }, }, }, }, - [AB5500_BANK_STARTUP] = { - .bankid = AB5500_BANK_STARTUP, - .nranges = 12, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x01, - .perm = AB5500_PERM_RW, + [AB5500_DEVID_CODEC] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_AUDIO_HEADSETUSB, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x48, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xEB, + .last = 0xFB, + .perm = AB5500_PERM_RW, + }, + }, }, + }, + }, +}; + +#define AB5500_IRQ(bank, bit) ((bank) * 8 + (bit)) + +/* I appologize for the resource names beeing a mix of upper case + * and lower case but I want them to be exact as the documentation */ +static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { + [AB5500_DEVID_LEDS] = { + .name = "ab5500-leds", + .id = AB5500_DEVID_LEDS, + }, + [AB5500_DEVID_POWER] = { + .name = "ab5500-power", + .id = AB5500_DEVID_POWER, + }, + [AB5500_DEVID_REGULATORS] = { + .name = "ab5500-regulator", + .id = AB5500_DEVID_REGULATORS, + }, + [AB5500_DEVID_SIM] = { + .name = "ab5500-sim", + .id = AB5500_DEVID_SIM, + .num_resources = 1, + .resources = (struct resource[]) { { - .first = 0x1F, - .last = 0x1F, - .perm = AB5500_PERM_RW, + .name = "SIMOFF", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 0), /*rising*/ + .end = AB5500_IRQ(2, 1), /*falling*/ }, + }, + }, + [AB5500_DEVID_RTC] = { + .name = "ab5500-rtc", + .id = AB5500_DEVID_RTC, + .num_resources = 1, + .resources = (struct resource[]) { { - .first = 0x2E, - .last = 0x2E, - .perm = AB5500_PERM_RO, + .name = "RTC_Alarm", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 7), + .end = AB5500_IRQ(1, 7), + } + }, + }, + [AB5500_DEVID_CHARGER] = { + .name = "ab5500-charger", + .id = AB5500_DEVID_CHARGER, + }, + [AB5500_DEVID_ADC] = { + .name = "ab5500-adc", + .id = AB5500_DEVID_ADC, + .num_resources = 10, + .resources = (struct resource[]) { + { + .name = "TRIGGER-0", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 0), + .end = AB5500_IRQ(0, 0), }, { - .first = 0x2F, - .last = 0x30, - .perm = AB5500_PERM_RW, + .name = "TRIGGER-1", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 1), + .end = AB5500_IRQ(0, 1), }, { - .first = 0x50, - .last = 0x51, - .perm = AB5500_PERM_RW, + .name = "TRIGGER-2", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 2), + .end = AB5500_IRQ(0, 2), }, { - .first = 0x60, - .last = 0x61, - .perm = AB5500_PERM_RW, + .name = "TRIGGER-3", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 3), + .end = AB5500_IRQ(0, 3), }, { - .first = 0x66, - .last = 0x8A, - .perm = AB5500_PERM_RW, + .name = "TRIGGER-4", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 4), + .end = AB5500_IRQ(0, 4), }, { - .first = 0x8C, - .last = 0x96, - .perm = AB5500_PERM_RW, + .name = "TRIGGER-5", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 5), + .end = AB5500_IRQ(0, 5), }, { - .first = 0xAA, - .last = 0xB4, - .perm = AB5500_PERM_RW, + .name = "TRIGGER-6", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 6), + .end = AB5500_IRQ(0, 6), }, { - .first = 0xB7, - .last = 0xBF, - .perm = AB5500_PERM_RW, + .name = "TRIGGER-7", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 7), + .end = AB5500_IRQ(0, 7), }, { - .first = 0xC1, - .last = 0xCA, - .perm = AB5500_PERM_RW, + .name = "TRIGGER-VBAT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 8), + .end = AB5500_IRQ(0, 8), }, { - .first = 0xD3, - .last = 0xE0, - .perm = AB5500_PERM_RW, + .name = "TRIGGER-VBAT-TXON", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(0, 9), + .end = AB5500_IRQ(0, 9), }, }, }, - [AB5500_BANK_DBI_ECI] = { - .bankid = AB5500_BANK_DBI_ECI, - .nranges = 3, - .range = (struct ab5500_reg_range[]) { + [AB5500_DEVID_FUELGAUGE] = { + .name = "ab5500-fuelgauge", + .id = AB5500_DEVID_FUELGAUGE, + .num_resources = 6, + .resources = (struct resource[]) { { - .first = 0x00, - .last = 0x07, - .perm = AB5500_PERM_RW, + .name = "Batt_attach", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 5), + .end = AB5500_IRQ(7, 5), }, { - .first = 0x10, - .last = 0x10, - .perm = AB5500_PERM_RW, + .name = "Batt_removal", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 6), + .end = AB5500_IRQ(7, 6), }, { - .first = 0x13, - .last = 0x13, - .perm = AB5500_PERM_RW, + .name = "UART_framing", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 7), + .end = AB5500_IRQ(7, 7), }, - }, - }, - [AB5500_BANK_CHG] = { - .bankid = AB5500_BANK_CHG, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { { - .first = 0x11, - .last = 0x11, - .perm = AB5500_PERM_RO, + .name = "UART_overrun", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 0), + .end = AB5500_IRQ(8, 0), }, { - .first = 0x12, - .last = 0x1B, - .perm = AB5500_PERM_RW, + .name = "UART_Rdy_RX", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 1), + .end = AB5500_IRQ(8, 1), + }, + { + .name = "UART_Rdy_TX", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 2), + .end = AB5500_IRQ(8, 2), }, }, }, - [AB5500_BANK_FG_BATTCOM_ACC] = { - .bankid = AB5500_BANK_FG_BATTCOM_ACC, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { + [AB5500_DEVID_VIBRATOR] = { + .name = "ab5500-vibrator", + .id = AB5500_DEVID_VIBRATOR, + }, + [AB5500_DEVID_CODEC] = { + .name = "ab5500-codec", + .id = AB5500_DEVID_CODEC, + .num_resources = 3, + .resources = (struct resource[]) { { - .first = 0x00, - .last = 0x0B, - .perm = AB5500_PERM_RO, + .name = "audio_spkr1_ovc", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 5), + .end = AB5500_IRQ(9, 5), }, { - .first = 0x0C, - .last = 0x10, - .perm = AB5500_PERM_RW, + .name = "audio_plllocked", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 6), + .end = AB5500_IRQ(9, 6), + }, + { + .name = "audio_spkr2_ovc", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 4), + .end = AB5500_IRQ(17, 4), }, }, }, - [AB5500_BANK_USB] = { - .bankid = AB5500_BANK_USB, - .nranges = 12, - .range = (struct ab5500_reg_range[]) { + [AB5500_DEVID_USB] = { + .name = "ab5500-usb", + .id = AB5500_DEVID_USB, + .num_resources = 35, + .resources = (struct resource[]) { { - .first = 0x01, - .last = 0x01, - .perm = AB5500_PERM_RW, + .name = "Link_Update", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(22, 1), + .end = AB5500_IRQ(22, 1), }, { - .first = 0x80, - .last = 0x83, - .perm = AB5500_PERM_RW, + .name = "DCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 3), + .end = AB5500_IRQ(8, 4), }, { - .first = 0x87, - .last = 0x8A, - .perm = AB5500_PERM_RW, + .name = "VBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 5), + .end = AB5500_IRQ(8, 6), }, { - .first = 0x8B, - .last = 0x8B, - .perm = AB5500_PERM_RO, + .name = "CHGstate_10_PCVBUSchg", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 7), + .end = AB5500_IRQ(8, 7), }, { - .first = 0x91, - .last = 0x92, - .perm = AB5500_PERM_RO, + .name = "DCIOreverse_ovc", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 0), + .end = AB5500_IRQ(9, 0), }, { - .first = 0x93, - .last = 0x93, - .perm = AB5500_PERM_RW, + .name = "USBCharDetDone", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 1), + .end = AB5500_IRQ(9, 1), }, { - .first = 0x94, - .last = 0x94, - .perm = AB5500_PERM_RO, + .name = "DCIO_no_limit", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 2), + .end = AB5500_IRQ(9, 2), }, { - .first = 0xA8, - .last = 0xB0, - .perm = AB5500_PERM_RO, + .name = "USB_suspend", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 3), + .end = AB5500_IRQ(9, 3), }, { - .first = 0xB2, - .last = 0xB2, - .perm = AB5500_PERM_RO, + .name = "DCIOreverse_fwdcurrent", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 4), + .end = AB5500_IRQ(9, 4), }, { - .first = 0xB4, - .last = 0xBC, - .perm = AB5500_PERM_RO, + .name = "Vbus_Imeasmax_change", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 5), + .end = AB5500_IRQ(9, 6), }, { - .first = 0xBF, - .last = 0xBF, - .perm = AB5500_PERM_RO, + .name = "OVV", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 5), + .end = AB5500_IRQ(14, 5), }, { - .first = 0xC1, - .last = 0xC5, - .perm = AB5500_PERM_RO, + .name = "USBcharging_NOTok", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 3), + .end = AB5500_IRQ(15, 3), }, - }, - }, - [AB5500_BANK_IT] = { - .bankid = AB5500_BANK_IT, - .nranges = 4, - .range = (struct ab5500_reg_range[]) { { - .first = 0x00, - .last = 0x02, - .perm = AB5500_PERM_RO, + .name = "usb_adp_sensoroff", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 6), + .end = AB5500_IRQ(15, 6), }, { - .first = 0x20, - .last = 0x36, - .perm = AB5500_PERM_RO, + .name = "usb_adp_probeplug", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 7), + .end = AB5500_IRQ(15, 7), }, { - .first = 0x40, - .last = 0x56, - .perm = AB5500_PERM_RO, + .name = "usb_adp_sinkerror", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 0), + .end = AB5500_IRQ(16, 6), }, { - .first = 0x60, - .last = 0x76, - .perm = AB5500_PERM_RO, + .name = "usb_adp_sourceerror", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 1), + .end = AB5500_IRQ(16, 1), }, - }, - }, - [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { - .bankid = AB5500_BANK_VDDDIG_IO_I2C_CLK_TST, - .nranges = 7, - .range = (struct ab5500_reg_range[]) { { - .first = 0x02, - .last = 0x02, - .perm = AB5500_PERM_RW, + .name = "usb_idgnd", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 2), + .end = AB5500_IRQ(16, 3), }, { - .first = 0x12, - .last = 0x12, - .perm = AB5500_PERM_RW, + .name = "usb_iddetR1", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 4), + .end = AB5500_IRQ(16, 5), }, { - .first = 0x30, - .last = 0x34, - .perm = AB5500_PERM_RW, + .name = "usb_iddetR2", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 6), + .end = AB5500_IRQ(16, 7), }, { - .first = 0x40, - .last = 0x44, - .perm = AB5500_PERM_RW, + .name = "usb_iddetR3", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 0), + .end = AB5500_IRQ(17, 1), }, { - .first = 0x50, - .last = 0x54, - .perm = AB5500_PERM_RW, + .name = "usb_iddetR4", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 2), + .end = AB5500_IRQ(17, 3), }, { - .first = 0x60, - .last = 0x64, - .perm = AB5500_PERM_RW, + .name = "CharTempWindowOk", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 7), + .end = AB5500_IRQ(18, 0), }, { - .first = 0x70, - .last = 0x74, - .perm = AB5500_PERM_RW, + .name = "USB_SprDetect", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 1), + .end = AB5500_IRQ(18, 1), }, - }, - }, - [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { - .bankid = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, - .nranges = 13, - .range = (struct ab5500_reg_range[]) { { - .first = 0x01, - .last = 0x01, - .perm = AB5500_PERM_RW, + .name = "usb_adp_probe_unplug", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 2), + .end = AB5500_IRQ(18, 2), }, { - .first = 0x02, - .last = 0x02, - .perm = AB5500_PERM_RO, + .name = "VBUSChDrop", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 3), + .end = AB5500_IRQ(18, 4), }, { - .first = 0x0D, - .last = 0x0F, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x1C, - .last = 0x1C, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x1E, - .last = 0x1E, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x20, - .last = 0x21, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x25, - .last = 0x25, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x28, - .last = 0x2A, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x30, - .last = 0x33, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x40, - .last = 0x43, - .perm = AB5500_PERM_RW, + .name = "dcio_char_rec_done", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 5), + .end = AB5500_IRQ(18, 5), }, { - .first = 0x50, - .last = 0x53, - .perm = AB5500_PERM_RW, + .name = "Charging_stopped_by_temp", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 6), + .end = AB5500_IRQ(18, 6), }, { - .first = 0x60, - .last = 0x63, - .perm = AB5500_PERM_RW, + .name = "CHGstate_11_SafeModeVBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 1), + .end = AB5500_IRQ(21, 2), }, { - .first = 0x70, - .last = 0x73, - .perm = AB5500_PERM_RW, + .name = "CHGstate_12_comletedVBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 2), + .end = AB5500_IRQ(21, 2), }, - }, - }, - [AB5500_BANK_VIBRA] = { - .bankid = AB5500_BANK_VIBRA, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { { - .first = 0x10, - .last = 0x13, - .perm = AB5500_PERM_RW, + .name = "CHGstate_13_completedVBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 3), + .end = AB5500_IRQ(21, 3), }, { - .first = 0xFE, - .last = 0xFE, - .perm = AB5500_PERM_RW, + .name = "CHGstate_14_FullChgDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 4), + .end = AB5500_IRQ(21, 4), }, - }, - }, - [AB5500_BANK_AUDIO_HEADSETUSB] = { - .bankid = AB5500_BANK_AUDIO_HEADSETUSB, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { { - .first = 0x00, - .last = 0x48, - .perm = AB5500_PERM_RW, + .name = "CHGstate_15_SafeModeDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 5), + .end = AB5500_IRQ(21, 5), }, { - .first = 0xEB, - .last = 0xFB, - .perm = AB5500_PERM_RW, + .name = "CHGstate_16_OFFsuspendDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 6), + .end = AB5500_IRQ(21, 6), }, - }, - }, - [AB5500_BANK_SIM_USBSIM] = { - .bankid = AB5500_BANK_SIM_USBSIM, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { { - .first = 0x13, - .last = 0x19, - .perm = AB5500_PERM_RW, + .name = "CHGstate_17_completedDCIO", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 7), + .end = AB5500_IRQ(21, 7), }, }, }, -}; - - -static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { - [AB5500_DEVID_USB] = { - .nbanks = 1, - .bank = &ab5500_reg_ranges[AB5500_BANK_USB], - }, - [AB5500_DEVID_ADC] = { - .nbanks = 1, - .bank = &ab5500_reg_ranges[AB5500_BANK_ADC], - }, - [AB5500_DEVID_LEDS] = { - .nbanks = 1, - .bank = &ab5500_reg_ranges[AB5500_BANK_LED], - }, - [AB5500_DEVID_VIDEO] = { - .nbanks = 1, - .bank = &ab5500_reg_ranges[AB5500_BANK_VDENC], - }, - [AB5500_DEVID_REGULATORS] = { - .nbanks = 2, - .bank = (struct ab5500_i2c_ranges * []) { - &ab5500_reg_ranges[AB5500_BANK_STARTUP], - &ab5500_reg_ranges[AB5500_BANK_SIM_USBSIM], - }, - }, - [AB5500_DEVID_SIM] = { - .nbanks = 1, - .bank = &ab5500_reg_ranges[AB5500_BANK_SIM_USBSIM], - }, - [AB5500_DEVID_RTC] = { - .nbanks = 1, - .bank = &ab5500_reg_ranges[AB5500_BANK_RTC], - }, - [AB5500_DEVID_CHARGER] = { - .nbanks = 1, - .bank = &ab5500_reg_ranges[AB5500_BANK_CHG], - }, - [AB5500_DEVID_FUELGAUGE] = { - .nbanks = 1, - .bank = &ab5500_reg_ranges[AB5500_BANK_FG_BATTCOM_ACC], - }, - [AB5500_DEVID_VIBRATOR] = { - .nbanks = 1, - .bank = &ab5500_reg_ranges[AB5500_BANK_VIBRA], - }, - [AB5500_DEVID_CODEC] = { - .nbanks = 1, - .bank = &ab5500_reg_ranges[AB5500_BANK_AUDIO_HEADSETUSB], - }, -}; - -#define AB5500_IRQ(bank, bit) ((bank) * 8 + (bit)) - -/* I appologize for the resource names beeing a mix of upper case - * and lower case but I want them to be exact as the documentation */ -static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { - [AB5500_DEVID_LEDS] = { - .name = "ab5500-leds", - .id = AB5500_DEVID_LEDS, - }, - [AB5500_DEVID_POWER] = { - .name = "ab5500-power", - .id = AB5500_DEVID_POWER, - }, - [AB5500_DEVID_REGULATORS] = { - .name = "ab5500-regulators", - .id = AB5500_DEVID_REGULATORS, + [AB5500_DEVID_OTP] = { + .name = "ab5500-otp", + .id = AB5500_DEVID_OTP, }, - [AB5500_DEVID_SIM] = { - .name = "ab5500-sim", - .id = AB5500_DEVID_SIM, + [AB5500_DEVID_VIDEO] = { + .name = "ab5500-video", + .id = AB5500_DEVID_VIDEO, .num_resources = 1, .resources = (struct resource[]) { { - .name = "SIMOFF", + .name = "plugTVdet", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(2, 0), /*rising*/ - .end = AB5500_IRQ(2, 1), /*falling*/ + .start = AB5500_IRQ(22, 2), + .end = AB5500_IRQ(22, 2), }, }, }, - [AB5500_DEVID_RTC] = { - .name = "ab5500-rtc", - .id = AB5500_DEVID_RTC, - .num_resources = 1, - .resources = (struct resource[]) { - { - .name = "RTC_Alarm", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(1, 7), - .end = AB5500_IRQ(1, 7), - } - }, - }, - [AB5500_DEVID_CHARGER] = { - .name = "ab5500-charger", - .id = AB5500_DEVID_CHARGER, - }, - [AB5500_DEVID_ADC] = { - .name = "ab5500-adc", - .id = AB5500_DEVID_ADC, + [AB5500_DEVID_DBIECI] = { + .name = "ab5500-dbieci", + .id = AB5500_DEVID_DBIECI, .num_resources = 10, .resources = (struct resource[]) { { - .name = "TRIGGER-0", + .name = "COLL", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 0), - .end = AB5500_IRQ(0, 0), + .start = AB5500_IRQ(14, 0), + .end = AB5500_IRQ(14, 0), }, { - .name = "TRIGGER-1", + .name = "RESERR", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 1), - .end = AB5500_IRQ(0, 1), + .start = AB5500_IRQ(14, 1), + .end = AB5500_IRQ(14, 1), }, { - .name = "TRIGGER-2", + .name = "FRAERR", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 2), - .end = AB5500_IRQ(0, 2), + .start = AB5500_IRQ(14, 2), + .end = AB5500_IRQ(14, 2), }, { - .name = "TRIGGER-3", + .name = "COMERR", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 3), - .end = AB5500_IRQ(0, 3), + .start = AB5500_IRQ(14, 3), + .end = AB5500_IRQ(14, 3), }, { - .name = "TRIGGER-4", + .name = "BSI_indicator", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 4), - .end = AB5500_IRQ(0, 4), + .start = AB5500_IRQ(14, 4), + .end = AB5500_IRQ(14, 4), }, { - .name = "TRIGGER-5", + .name = "SPDSET", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 5), - .end = AB5500_IRQ(0, 5), + .start = AB5500_IRQ(14, 6), + .end = AB5500_IRQ(14, 6), }, { - .name = "TRIGGER-6", + .name = "DSENT", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 6), - .end = AB5500_IRQ(0, 6), + .start = AB5500_IRQ(14, 7), + .end = AB5500_IRQ(14, 7), }, { - .name = "TRIGGER-7", + .name = "DREC", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 7), - .end = AB5500_IRQ(0, 7), - }, - { - .name = "TRIGGER-VBAT", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 8), - .end = AB5500_IRQ(0, 8), - }, - { - .name = "TRIGGER-VBAT-TXON", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 9), - .end = AB5500_IRQ(0, 9), - }, - }, - }, - [AB5500_DEVID_FUELGAUGE] = { - .name = "ab5500-fuelgauge", - .id = AB5500_DEVID_FUELGAUGE, - .num_resources = 6, - .resources = (struct resource[]) { - { - .name = "Batt_attach", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(7, 5), - .end = AB5500_IRQ(7, 5), - }, - { - .name = "Batt_removal", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(7, 6), - .end = AB5500_IRQ(7, 6), - }, - { - .name = "UART_framing", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(7, 7), - .end = AB5500_IRQ(7, 7), - }, - { - .name = "UART_overrun", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(8, 0), - .end = AB5500_IRQ(8, 0), + .start = AB5500_IRQ(15, 0), + .end = AB5500_IRQ(15, 0), }, { - .name = "UART_Rdy_RX", + .name = "ACCINT", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(8, 1), - .end = AB5500_IRQ(8, 1), + .start = AB5500_IRQ(15, 1), + .end = AB5500_IRQ(15, 1), }, { - .name = "UART_Rdy_TX", + .name = "NOPINT", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(8, 2), - .end = AB5500_IRQ(8, 2), + .start = AB5500_IRQ(15, 2), + .end = AB5500_IRQ(15, 2), }, }, }, - [AB5500_DEVID_VIBRATOR] = { - .name = "ab5500-vibrator", - .id = AB5500_DEVID_VIBRATOR, - }, - [AB5500_DEVID_CODEC] = { - .name = "ab5500-codec", - .id = AB5500_DEVID_CODEC, - .num_resources = 3, + [AB5500_DEVID_ONSWA] = { + .name = "ab5500-onswa", + .id = AB5500_DEVID_ONSWA, + .num_resources = 2, .resources = (struct resource[]) { { - .name = "audio_spkr1_ovc", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(9, 5), - .end = AB5500_IRQ(9, 5), - }, - { - .name = "audio_plllocked", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(9, 6), - .end = AB5500_IRQ(9, 6), + .name = "ONSWAn_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 3), + .end = AB5500_IRQ(1, 3), }, { - .name = "audio_spkr2_ovc", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(17, 4), - .end = AB5500_IRQ(17, 4), - }, - }, - }, - [AB5500_DEVID_USB] = { - .name = "ab5500-usb", - .id = AB5500_DEVID_USB, - .num_resources = 35, - .resources = (struct resource[]) { - { - .name = "Link_Update", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(22, 1), - .end = AB5500_IRQ(22, 1), - }, - { - .name = "DCIO", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(8, 3), - .end = AB5500_IRQ(8, 4), - }, - { - .name = "VBUS", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(8, 5), - .end = AB5500_IRQ(8, 6), - }, - { - .name = "CHGstate_10_PCVBUSchg", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(8, 7), - .end = AB5500_IRQ(8, 7), - }, - { - .name = "DCIOreverse_ovc", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(9, 0), - .end = AB5500_IRQ(9, 0), - }, - { - .name = "USBCharDetDone", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(9, 1), - .end = AB5500_IRQ(9, 1), - }, - { - .name = "DCIO_no_limit", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(9, 2), - .end = AB5500_IRQ(9, 2), - }, - { - .name = "USB_suspend", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(9, 3), - .end = AB5500_IRQ(9, 3), - }, - { - .name = "DCIOreverse_fwdcurrent", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(9, 4), - .end = AB5500_IRQ(9, 4), - }, - { - .name = "Vbus_Imeasmax_change", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(9, 5), - .end = AB5500_IRQ(9, 6), - }, - { - .name = "OVV", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(14, 5), - .end = AB5500_IRQ(14, 5), - }, - { - .name = "USBcharging_NOTok", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(15, 3), - .end = AB5500_IRQ(15, 3), - }, - { - .name = "usb_adp_sensoroff", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(15, 6), - .end = AB5500_IRQ(15, 6), - }, - { - .name = "usb_adp_probeplug", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(15, 7), - .end = AB5500_IRQ(15, 7), - }, - { - .name = "usb_adp_sinkerror", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(16, 0), - .end = AB5500_IRQ(16, 6), - }, - { - .name = "usb_adp_sourceerror", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(16, 1), - .end = AB5500_IRQ(16, 1), - }, - { - .name = "usb_idgnd", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(16, 2), - .end = AB5500_IRQ(16, 3), - }, - { - .name = "usb_iddetR1", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(16, 4), - .end = AB5500_IRQ(16, 5), - }, - { - .name = "usb_iddetR2", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(16, 6), - .end = AB5500_IRQ(16, 7), - }, - { - .name = "usb_iddetR3", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(17, 0), - .end = AB5500_IRQ(17, 1), - }, - { - .name = "usb_iddetR4", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(17, 2), - .end = AB5500_IRQ(17, 3), - }, - { - .name = "CharTempWindowOk", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(17, 7), - .end = AB5500_IRQ(18, 0), - }, - { - .name = "USB_SprDetect", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(18, 1), - .end = AB5500_IRQ(18, 1), - }, - { - .name = "usb_adp_probe_unplug", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(18, 2), - .end = AB5500_IRQ(18, 2), - }, - { - .name = "VBUSChDrop", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(18, 3), - .end = AB5500_IRQ(18, 4), - }, - { - .name = "dcio_char_rec_done", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(18, 5), - .end = AB5500_IRQ(18, 5), - }, - { - .name = "Charging_stopped_by_temp", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(18, 6), - .end = AB5500_IRQ(18, 6), - }, - { - .name = "CHGstate_11_SafeModeVBUS", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(21, 1), - .end = AB5500_IRQ(21, 2), - }, - { - .name = "CHGstate_12_comletedVBUS", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(21, 2), - .end = AB5500_IRQ(21, 2), - }, - { - .name = "CHGstate_13_completedVBUS", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(21, 3), - .end = AB5500_IRQ(21, 3), - }, - { - .name = "CHGstate_14_FullChgDCIO", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(21, 4), - .end = AB5500_IRQ(21, 4), - }, - { - .name = "CHGstate_15_SafeModeDCIO", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(21, 5), - .end = AB5500_IRQ(21, 5), - }, - { - .name = "CHGstate_16_OFFsuspendDCIO", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(21, 6), - .end = AB5500_IRQ(21, 6), - }, - { - .name = "CHGstate_17_completedDCIO", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(21, 7), - .end = AB5500_IRQ(21, 7), - }, - }, - }, - [AB5500_DEVID_OTP] = { - .name = "ab5500-otp", - .id = AB5500_DEVID_OTP, - }, - [AB5500_DEVID_VIDEO] = { - .name = "ab5500-video", - .id = AB5500_DEVID_VIDEO, - .num_resources = 1, - .resources = (struct resource[]) { - { - .name = "plugTVdet", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(22, 2), - .end = AB5500_IRQ(22, 2), - }, - }, - }, - [AB5500_DEVID_DBIECI] = { - .name = "ab5500-dbieci", - .id = AB5500_DEVID_DBIECI, - .num_resources = 10, - .resources = (struct resource[]) { - { - .name = "COLL", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(14, 0), - .end = AB5500_IRQ(14, 0), - }, - { - .name = "RESERR", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(14, 1), - .end = AB5500_IRQ(14, 1), - }, - { - .name = "FRAERR", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(14, 2), - .end = AB5500_IRQ(14, 2), - }, - { - .name = "COMERR", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(14, 3), - .end = AB5500_IRQ(14, 3), - }, - { - .name = "BSI_indicator", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(14, 4), - .end = AB5500_IRQ(14, 4), - }, - { - .name = "SPDSET", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(14, 6), - .end = AB5500_IRQ(14, 6), - }, - { - .name = "DSENT", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(14, 7), - .end = AB5500_IRQ(14, 7), - }, - { - .name = "DREC", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(15, 0), - .end = AB5500_IRQ(15, 0), - }, - { - .name = "ACCINT", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(15, 1), - .end = AB5500_IRQ(15, 1), - }, - { - .name = "NOPINT", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(15, 2), - .end = AB5500_IRQ(15, 2), - }, - }, - }, - [AB5500_DEVID_ONSWA] = { - .name = "ab5500-onswa", - .id = AB5500_DEVID_ONSWA, - .num_resources = 2, - .resources = (struct resource[]) { - { - .name = "ONSWAn_rising", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(1, 3), - .end = AB5500_IRQ(1, 3), - }, - { - .name = "ONSWAn_falling", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(1, 4), - .end = AB5500_IRQ(1, 4), + .name = "ONSWAn_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(1, 4), + .end = AB5500_IRQ(1, 4), }, }, }, @@ -1422,6 +1344,503 @@ static irqreturn_t ab5500_irq(int irq, void *data) } #ifdef CONFIG_DEBUG_FS +static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { + [AB5500_BANK_LED] = { + .bankid = AB5500_BANK_LED, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0C, + .perm = AB5500_PERM_RW, + }, + }, + }, + [AB5500_BANK_ADC] = { + .bankid = AB5500_BANK_ADC, + .nranges = 6, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x1F, + .last = 0x22, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x23, + .last = 0x24, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x26, + .last = 0x2D, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x2F, + .last = 0x34, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x37, + .last = 0x57, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x58, + .last = 0x58, + .perm = AB5500_PERM_RO, + }, + }, + }, + [AB5500_BANK_RTC] = { + .bankid = AB5500_BANK_RTC, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x06, + .last = 0x0C, + .perm = AB5500_PERM_RW, + }, + }, + }, + [AB5500_BANK_STARTUP] = { + .bankid = AB5500_BANK_STARTUP, + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x1F, + .last = 0x1F, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x2E, + .last = 0x2E, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x2F, + .last = 0x30, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x50, + .last = 0x51, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x60, + .last = 0x61, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x66, + .last = 0x8A, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x8C, + .last = 0x96, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xAA, + .last = 0xB4, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xB7, + .last = 0xBF, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xC1, + .last = 0xCA, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xD3, + .last = 0xE0, + .perm = AB5500_PERM_RW, + }, + }, + }, + [AB5500_BANK_DBI_ECI] = { + .bankid = AB5500_BANK_DBI_ECI, + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x10, + .last = 0x10, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x13, + .last = 0x13, + .perm = AB5500_PERM_RW, + }, + }, + }, + [AB5500_BANK_CHG] = { + .bankid = AB5500_BANK_CHG, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x11, + .last = 0x11, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x12, + .last = 0x1B, + .perm = AB5500_PERM_RW, + }, + }, + }, + [AB5500_BANK_FG_BATTCOM_ACC] = { + .bankid = AB5500_BANK_FG_BATTCOM_ACC, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0B, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x0C, + .last = 0x10, + .perm = AB5500_PERM_RW, + }, + }, + }, + [AB5500_BANK_USB] = { + .bankid = AB5500_BANK_USB, + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x01, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x80, + .last = 0x83, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x87, + .last = 0x8A, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x8B, + .last = 0x8B, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x91, + .last = 0x92, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x93, + .last = 0x93, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x94, + .last = 0x94, + .perm = AB5500_PERM_RO, + }, + { + .first = 0xA8, + .last = 0xB0, + .perm = AB5500_PERM_RO, + }, + { + .first = 0xB2, + .last = 0xB2, + .perm = AB5500_PERM_RO, + }, + { + .first = 0xB4, + .last = 0xBC, + .perm = AB5500_PERM_RO, + }, + { + .first = 0xBF, + .last = 0xBF, + .perm = AB5500_PERM_RO, + }, + { + .first = 0xC1, + .last = 0xC5, + .perm = AB5500_PERM_RO, + }, + }, + }, + [AB5500_BANK_IT] = { + .bankid = AB5500_BANK_IT, + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x02, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x20, + .last = 0x36, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x40, + .last = 0x56, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x60, + .last = 0x76, + .perm = AB5500_PERM_RO, + }, + }, + }, + [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { + .bankid = AB5500_BANK_VDDDIG_IO_I2C_CLK_TST, + .nranges = 7, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x02, + .last = 0x02, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x12, + .last = 0x12, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x30, + .last = 0x34, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x40, + .last = 0x44, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x50, + .last = 0x54, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x60, + .last = 0x64, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x70, + .last = 0x74, + .perm = AB5500_PERM_RW, + }, + }, + }, + [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { + .bankid = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + .nranges = 13, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x01, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x02, + .last = 0x02, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x0D, + .last = 0x0F, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x1C, + .last = 0x1C, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x1E, + .last = 0x1E, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x20, + .last = 0x21, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x25, + .last = 0x25, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x28, + .last = 0x2A, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x30, + .last = 0x33, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x40, + .last = 0x43, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x50, + .last = 0x53, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x60, + .last = 0x63, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x70, + .last = 0x73, + .perm = AB5500_PERM_RW, + }, + }, + }, + [AB5500_BANK_VIBRA] = { + .bankid = AB5500_BANK_VIBRA, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x10, + .last = 0x13, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xFE, + .last = 0xFE, + .perm = AB5500_PERM_RW, + }, + }, + }, + [AB5500_BANK_AUDIO_HEADSETUSB] = { + .bankid = AB5500_BANK_AUDIO_HEADSETUSB, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x48, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xEB, + .last = 0xFB, + .perm = AB5500_PERM_RW, + }, + }, + }, + [AB5500_BANK_SIM_USBSIM] = { + .bankid = AB5500_BANK_SIM_USBSIM, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x13, + .last = 0x19, + .perm = AB5500_PERM_RW, + }, + }, + }, + [AB5500_BANK_VDENC] = { + .bankid = AB5500_BANK_VDENC, + .nranges = 12, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x08, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x09, + .last = 0x09, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x0A, + .last = 0x12, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x15, + .last = 0x19, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x1B, + .last = 0x21, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x27, + .last = 0x2C, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x41, + .last = 0x41, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x45, + .last = 0x5B, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x5D, + .last = 0x5D, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x69, + .last = 0x69, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x6C, + .last = 0x6D, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x80, + .last = 0x81, + .perm = AB5500_PERM_RW, + }, + }, + }, +}; static int ab5500_registers_print(struct seq_file *s, void *p) { struct ab5500 *ab = s->private; -- cgit v1.2.3 From 99dcc6932b727e69764141890de4c7132240abcd Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 12 Oct 2011 15:24:13 +0200 Subject: arm: ux500: change the ab8500-pwm clock from sysclk to ab_ulpclk This patch removes the explicit request for sysclk from the ab8500-pwm driver, and changes the clock it is given to the internal AB8500 ULP PLL. ST Ericsson ID: 335424 ST Ericsson FOSS-OUT ID: trivial ST Ericsson Linux next: - Change-Id: I9ddb05e6caa480976286024bb305e962b86da636 Signed-off-by: Mattias Nilsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/21198 Reviewed-by: Bengt JONSSON Reviewed-by: Jonas ABERG Conflicts: arch/arm/mach-ux500/clock-db8500.c --- drivers/misc/ab8500-pwm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/ab8500-pwm.c b/drivers/misc/ab8500-pwm.c index a1a3609c961..372fd4a37f2 100644 --- a/drivers/misc/ab8500-pwm.c +++ b/drivers/misc/ab8500-pwm.c @@ -72,7 +72,7 @@ int pwm_enable(struct pwm_device *pwm) if (!pwm->clk_enabled) { ret = clk_enable(pwm->clk); if (ret < 0) { - dev_err(pwm->dev, "failed to enable sysclk\n"); + dev_err(pwm->dev, "failed to enable clock\n"); return ret; } pwm->clk_enabled = true; @@ -160,7 +160,7 @@ static int __devinit ab8500_pwm_probe(struct platform_device *pdev) list_add_tail(&pwm->node, &pwm_list); platform_set_drvdata(pdev, pwm); - pwm->clk = clk_get(pwm->dev, "sysclk"); + pwm->clk = clk_get(pwm->dev, NULL); if (IS_ERR(pwm->clk)) { dev_err(pwm->dev, "clock request failed\n"); ret = PTR_ERR(pwm->clk); -- cgit v1.2.3 From 9098bbdbc639f207af0a3166190306b2195b0b01 Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Thu, 28 Apr 2011 13:33:36 +0530 Subject: abx500: provide simpler function names There are no non-interruptible versions of these functions, and all of them operate on registers, so provide shorter names to make subdrivers more readable while sticking to the 80-column line length limit. ST-Ericsson Linux next: - ST-Ericsson ID: WP257121 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I98dc8152e3e8064edb95ba5679277ed8c229b537 Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/21816 Reviewed-by: QATEST Reviewed-by: Mattias WALLIN --- include/linux/mfd/abx500.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index bf8cc970b4c..86a17b29cc1 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -232,6 +232,12 @@ int abx500_get_chip_id(struct device *dev); int abx500_event_registers_startup_state_get(struct device *dev, u8 *event); int abx500_startup_irq_enabled(struct device *dev, unsigned int irq); +#define abx500_get abx500_get_register_interruptible +#define abx500_set abx500_set_register_interruptible +#define abx500_get_page abx500_get_register_page_interruptible +#define abx500_set_page abx500_set_register_page_interruptible +#define abx500_mask_and_set abx500_mask_and_set_register_interruptible + struct abx500_ops { int (*get_chip_id) (struct device *); int (*get_register) (struct device *, u8, u8, u8 *); -- cgit v1.2.3 From ed1754893c0a53d160cd69cef8828713f6310cf5 Mon Sep 17 00:00:00 2001 From: Bibek Basu Date: Tue, 3 May 2011 10:20:53 +0530 Subject: U5500: Add seperate IRQ RESOURCE for USB USB wants seperate mentioning of contiguous IRQ resource. VBUS_R, VBUS_F, usb_idgnd_f, usb_idgnd_r ST-Ericsson Linux next: - ST-Ericsson ID: WP257121 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I47d340a71223423cc6a1ac1de309df3d21eb17f2 Signed-off-by: Bibek Basu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/22050 Reviewed-by: QATEST Reviewed-by: Praveena NADAHALLY Reviewed-by: Srinidhi KASAGAR --- drivers/mfd/ab5500-core.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index aef7387aaab..7b4099ddb0a 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -720,7 +720,7 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { [AB5500_DEVID_USB] = { .name = "ab5500-usb", .id = AB5500_DEVID_USB, - .num_resources = 35, + .num_resources = 36, .resources = (struct resource[]) { { .name = "Link_Update", @@ -735,9 +735,15 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { .end = AB5500_IRQ(8, 4), }, { - .name = "VBUS", + .name = "VBUS_R", .flags = IORESOURCE_IRQ, .start = AB5500_IRQ(8, 5), + .end = AB5500_IRQ(8, 5), + }, + { + .name = "VBUS_F", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 6), .end = AB5500_IRQ(8, 6), }, { @@ -819,9 +825,15 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { .end = AB5500_IRQ(16, 1), }, { - .name = "usb_idgnd", + .name = "usb_idgnd_r", .flags = IORESOURCE_IRQ, .start = AB5500_IRQ(16, 2), + .end = AB5500_IRQ(16, 2), + }, + { + .name = "usb_idgnd_f", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(16, 3), .end = AB5500_IRQ(16, 3), }, { -- cgit v1.2.3 From c0e42ed2e3bc98df2e0adee1cef7c5bf9e7bdbe2 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 11 May 2011 09:32:05 +0200 Subject: ab5500: placeholder to pass devices platform data Added placeholder in struct ab5500_platform_data to add platform data of ab5500 devices. Change-Id: I020cbcfd344d19c7f4268638ebf147e7080b127d Signed-off-by: Shreshtha Kumar Sahu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/21907 Reviewed-by: Mattias WALLIN Reviewed-by: Srinidhi KASAGAR --- drivers/mfd/ab5500-core.c | 5 +++++ include/linux/mfd/abx500/ab5500.h | 8 +++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 7b4099ddb0a..43f7cad48e7 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -2366,6 +2366,11 @@ static int __init ab5500_probe(struct platform_device *pdev) goto exit_no_irq; } + /* Set up and register the platform devices. */ + for (i = 0; i < AB5500_NUM_DEVICES; i++) { + ab5500_devs[i].mfd_data = ab5500_plf_data->dev_data[i]; + } + err = mfd_add_devices(&pdev->dev, 0, ab5500_devs, ARRAY_SIZE(ab5500_devs), NULL, ab5500_plf_data->irq.base); diff --git a/include/linux/mfd/abx500/ab5500.h b/include/linux/mfd/abx500/ab5500.h index 72c2baf1b83..f9230b6444f 100644 --- a/include/linux/mfd/abx500/ab5500.h +++ b/include/linux/mfd/abx500/ab5500.h @@ -133,9 +133,11 @@ struct ab5500 { struct ab5500_regulator_platform_data; struct ab5500_platform_data { - struct {unsigned int base; unsigned int count; } irq; - struct ab5500_regulator_platform_data *regulator; - }; + struct {unsigned int base; unsigned int count; } irq; + void *dev_data[AB5500_NUM_DEVICES]; + size_t dev_data_sz[AB5500_NUM_DEVICES]; + struct ab5500_regulator_platform_data *regulator; +}; #endif /* MFD_AB5500_H */ -- cgit v1.2.3 From 0b4e3f1ce63ce3f9782367b52fbdbfe0501af7a0 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 12 Oct 2011 15:37:42 +0200 Subject: ab5500: provide a driver for power off ST-Ericsson Linux next: - ST-Ericsson ID: WP257121 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I3fcc5c9f1d88c433edc3698da739ecdd11e51711 Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/22352 Reviewed-by: Srinidhi KASAGAR Conflicts: arch/arm/mach-ux500/board-u5500.c --- drivers/mfd/Makefile | 2 +- drivers/mfd/ab5500-core.c | 16 +++++++++ drivers/mfd/ab5500-power.c | 72 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/abx500/ab5500.h | 1 + 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 drivers/mfd/ab5500-power.c diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 0bf175a91e4..a4955178af8 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -2,7 +2,7 @@ # Makefile for multifunction miscellaneous devices # -obj-$(CONFIG_AB5500_CORE) += ab5500-core.o +obj-$(CONFIG_AB5500_CORE) += ab5500-core.o ab5500-power.o 88pm860x-objs := 88pm860x-core.o 88pm860x-i2c.o obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o obj-$(CONFIG_MFD_SM501) += sm501.o diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 43f7cad48e7..2fc1cf1b101 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -529,6 +529,22 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { }, }, }, + [AB5500_DEVID_POWER] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_STARTUP, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x30, + .last = 0x30, + .perm = AB5500_PERM_RW, + }, + }, + } + }, + }, }; #define AB5500_IRQ(bank, bit) ((bank) * 8 + (bit)) diff --git a/drivers/mfd/ab5500-power.c b/drivers/mfd/ab5500-power.c new file mode 100644 index 00000000000..5bf6a4c5e97 --- /dev/null +++ b/drivers/mfd/ab5500-power.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL) version 2 + */ + +#include +#include +#include +#include + +#include +#include + +static struct device *dev; + +#define AB5500_SYSPOR_CONTROL 0x30 + +static void ab5500_power_off(void) +{ + sigset_t old; + sigset_t all; + + sigfillset(&all); + + if (!sigprocmask(SIG_BLOCK, &all, &old)) { + /* Clear dbb_on */ + int ret = abx500_set(dev, AB5500_BANK_STARTUP, + AB5500_SYSPOR_CONTROL, 0); + WARN_ON(ret); + } +} + +static int __devinit ab5500_power_probe(struct platform_device *pdev) +{ + struct ab5500_platform_data *plat = dev_get_platdata(pdev->dev.parent); + + if (!plat->pm_power_off) + return -ENODEV; + + dev = &pdev->dev; + pm_power_off = ab5500_power_off; + + return 0; +} + +static int __devexit ab5500_power_remove(struct platform_device *pdev) +{ + pm_power_off = NULL; + dev = NULL; + + return 0; +} + +static struct platform_driver ab5500_power_driver = { + .driver = { + .name = "ab5500-power", + .owner = THIS_MODULE, + }, + .probe = ab5500_power_probe, + .remove = __devexit_p(ab5500_power_remove), +}; + +static int __init ab8500_sysctrl_init(void) +{ + return platform_driver_register(&ab5500_power_driver); +} + +subsys_initcall(ab8500_sysctrl_init); + +MODULE_DESCRIPTION("AB5500 power driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/abx500/ab5500.h b/include/linux/mfd/abx500/ab5500.h index f9230b6444f..1b2b150072a 100644 --- a/include/linux/mfd/abx500/ab5500.h +++ b/include/linux/mfd/abx500/ab5500.h @@ -134,6 +134,7 @@ struct ab5500 { struct ab5500_regulator_platform_data; struct ab5500_platform_data { struct {unsigned int base; unsigned int count; } irq; + bool pm_power_off; void *dev_data[AB5500_NUM_DEVICES]; size_t dev_data_sz[AB5500_NUM_DEVICES]; struct ab5500_regulator_platform_data *regulator; -- cgit v1.2.3 From 1eddeef79b9b7eb89be690868fba51def54975c4 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 11 May 2011 14:21:16 +0200 Subject: mfd:ab8500-core: Add interrupt disable hook Add the missing interrupt disable hook in the irq_chip callbacks for ab8500. ST-Ericsson ID: ER 338639 Change-Id: I8dc939c0f0039885e4948e5e08443ed3b45f1ca8 Signed-off-by: Virupax Sadashivpetimath Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/22597 Reviewed-by: Srinidhi KASAGAR Conflicts: drivers/mfd/ab8500-core.c --- drivers/mfd/ab8500-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index 3fac47f9bb5..4b1a40fe2fe 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -293,6 +293,7 @@ static struct irq_chip ab8500_irq_chip = { .irq_bus_lock = ab8500_irq_lock, .irq_bus_sync_unlock = ab8500_irq_sync_unlock, .irq_mask = ab8500_irq_mask, + .irq_disable = ab8500_irq_mask, .irq_unmask = ab8500_irq_unmask, }; -- cgit v1.2.3 From 6ee73e0a1cac8b699704c240f00d1d5f9685442a Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 12 Oct 2011 16:00:23 +0200 Subject: ab5500: leds: driver for ab5500 leds Simple HV LED controller driver for AB5500v1.0 MFD chips ST-Ericsson ID: WP 332221 ST-Ericsson Linux next: ER 336280 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ic787cd1a2277a4c5782dd18d3436cd95b763c81a Signed-off-by: Shreshtha Kumar Sahu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/21898 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/leds/Kconfig | 8 + drivers/leds/Makefile | 1 + drivers/leds/leds-ab5500.c | 432 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/leds-ab5500.h | 31 ++++ 4 files changed, 472 insertions(+) create mode 100644 drivers/leds/leds-ab5500.c create mode 100644 include/linux/leds-ab5500.h diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index b591e726a6f..12ce1177f12 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -50,6 +50,14 @@ config LEDS_LM3530 controlled manually or using PWM input or using ambient light automatically. +config LEDS_AB5500 + tristate "HVLED driver for AB5500" + depends on AB5500_CORE + help + This option enables support for the HVLED in AB5500 + multi function device. Currently Ab5500 v1.0 chip leds + are supported. + config LEDS_LOCOMO tristate "LED Support for Locomo device" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index bbfd2e367dc..d2acb6e35d8 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o +obj-$(CONFIG_LEDS_AB5500) += leds-ab5500.o obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o obj-$(CONFIG_LEDS_AMS_DELTA) += leds-ams-delta.o diff --git a/drivers/leds/leds-ab5500.c b/drivers/leds/leds-ab5500.c new file mode 100644 index 00000000000..94db3a6feea --- /dev/null +++ b/drivers/leds/leds-ab5500.c @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2011 ST-Ericsson SA. + * + * License Terms: GNU General Public License v2 + * + * Driver for LED in ST-Ericsson AB5500 v1.0 Analog baseband Controller + * + * Author: Shreshtha Kumar SAHU + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AB5500LED_NAME "ab5500-leds" + +/* Register offsets */ +#define AB5500_LED_REG_ENABLE 0x03 +#define AB5500_LED_FADE_CTL 0x0D + +/* LED-0 */ +#define AB5500_LED0_PWM_DUTY 0x01 +#define AB5500_LED0_PWMFREQ 0x02 +#define AB5500_LED0_SINKCTL 0x0A + +/* LED-1 */ +#define AB5500_LED1_PWM_DUTY 0x05 +#define AB5500_LED1_PWMFREQ 0x06 +#define AB5500_LED1_SINKCTL 0x0B + +/* LED-2 */ +#define AB5500_LED2_PWM_DUTY 0x08 +#define AB5500_LED2_PWMFREQ 0x09 +#define AB5500_LED2_SINKCTL 0x0C + +/* pwm duty cycle */ +#define AB5500_LED_PWMDUTY_OFF 0x0 +#define AB5500_LED_PWMDUTY_MAX 0x3FF +#define AB5500_LED_PWMDUTY_STEP (AB5500_LED_PWMDUTY_MAX/LED_FULL) + +/* pwm frequency */ +#define AB5500_LED_PWMFREQ_MAX 0x0F /* 373.39 @sysclk=26MHz */ +#define AB5500_LED_PWMFREQ_SHIFT 4 + +/* LED sink current control */ +#define AB5500_LED_SINKCURR_MAX 0x0F /* 40mA */ +#define AB5500_LED_SINKCURR_SHIFT 4 + +struct ab5500_led { + u8 id; + u8 max_current; + u16 brt_val; + enum ab5500_led_status status; + struct led_classdev led_cdev; + struct work_struct led_work; +}; + +struct ab5500_hvleds { + struct mutex lock; + struct device *dev; + struct ab5500_hvleds_platform_data *pdata; + struct ab5500_led leds[AB5500_HVLEDS_MAX]; +}; + +static u8 ab5500_led_pwmduty_reg[] = { + AB5500_LED0_PWM_DUTY, + AB5500_LED1_PWM_DUTY, + AB5500_LED2_PWM_DUTY, +}; + +static u8 ab5500_led_pwmfreq_reg[] = { + AB5500_LED0_PWMFREQ, + AB5500_LED1_PWMFREQ, + AB5500_LED2_PWMFREQ, +}; + +static u8 ab5500_led_sinkctl_reg[] = { + AB5500_LED0_SINKCTL, + AB5500_LED1_SINKCTL, + AB5500_LED2_SINKCTL +}; + +#define to_led(_x) container_of(_x, struct ab5500_led, _x) + +static inline struct ab5500_hvleds *led_to_hvleds(struct ab5500_led *led) +{ + return container_of(led, struct ab5500_hvleds, leds[led->id]); +} + +static int ab5500_led_pwmduty_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, u16 val) +{ + int ret; + int val_lsb = val & 0xFF; + int val_msb = (val & 0x300) >> 8; + + mutex_lock(&hvleds->lock); + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val = %d\n" + "reg[%d] w val = %d\n", + ab5500_led_pwmduty_reg[led_id] - 1, val_lsb, + ab5500_led_pwmduty_reg[led_id], val_msb); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id] - 1, val_lsb); + ret |= abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id], val_msb); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmduty_reg[led_id], ret); + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_pwmfreq_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, u8 val) +{ + int ret; + + val = (val & 0x0F) << AB5500_LED_PWMFREQ_SHIFT; + + mutex_lock(&hvleds->lock); + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val=%d\n", + ab5500_led_pwmfreq_reg[led_id], val); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmfreq_reg[led_id], val); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmfreq_reg[led_id], ret); + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_sinkctl_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, u8 val) +{ + int ret; + + val = (val & 0x0F) << AB5500_LED_SINKCURR_SHIFT; + + mutex_lock(&hvleds->lock); + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val=%d\n", + ab5500_led_sinkctl_reg[led_id], val); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_sinkctl_reg[led_id], val); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_sinkctl_reg[led_id], ret); + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_sinkctl_read(struct ab5500_hvleds *hvleds, + unsigned int led_id) +{ + int ret; + u8 val; + + mutex_lock(&hvleds->lock); + ret = abx500_get_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_sinkctl_reg[led_id], &val); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] r failed: %d\n", + ab5500_led_sinkctl_reg[led_id], ret); + mutex_unlock(&hvleds->lock); + return ret; + } + val = (val & 0xF0) >> AB5500_LED_SINKCURR_SHIFT; + mutex_unlock(&hvleds->lock); + + return val; +} + +static void ab5500_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brt_val) +{ + struct ab5500_led *led = to_led(led_cdev); + + /* adjust LED_FULL to 10bit range */ + brt_val &= LED_FULL; + led->brt_val = brt_val * AB5500_LED_PWMDUTY_STEP; + schedule_work(&led->led_work); +} + +static void ab5500_led_work(struct work_struct *led_work) +{ + struct ab5500_led *led = to_led(led_work); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + if (led->status == AB5500_LED_ON) + ab5500_led_pwmduty_write(hvleds, led->id, led->brt_val); +} + +static ssize_t ab5500_led_show_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int led_curr = 0; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + led_curr = ab5500_led_sinkctl_read(hvleds, led->id); + + if (led_curr < 0) + return led_curr; + + return sprintf(buf, "%d\n", led_curr); +} + +static ssize_t ab5500_led_store_current(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int ret; + unsigned long led_curr; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + if (strict_strtoul(buf, 0, &led_curr)) + return -EINVAL; + + if (led_curr > led->max_current) + led_curr = led->max_current; + + ret = ab5500_led_sinkctl_write(hvleds, led->id, led_curr); + if (ret < 0) + return ret; + + return len; +} + +/* led class device attributes */ +static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, + ab5500_led_show_current, ab5500_led_store_current); + +static int ab5500_led_init_registers(struct ab5500_hvleds *hvleds) +{ + int ret = 0; + unsigned int led_id; + + /* fade - manual : dur mid : pwm duty mid */ + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_REG_ENABLE, true); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + AB5500_LED_REG_ENABLE, ret); + return ret; + } + + for (led_id = 0; led_id < AB5500_HVLEDS_MAX; led_id++) { + /* Set pwm freq. and sink current to mid values */ + ret = ab5500_led_pwmfreq_write( + hvleds, led_id, AB5500_LED_PWMFREQ_MAX); + if (ret < 0) + return ret; + + ret = ab5500_led_sinkctl_write( + hvleds, led_id, AB5500_LED_SINKCURR_MAX); + if (ret < 0) + return ret; + + /* init led off */ + ret = ab5500_led_pwmduty_write( + hvleds, led_id, AB5500_LED_PWMDUTY_OFF); + if (ret < 0) + return ret; + } + + return ret; +} + +static int ab5500_led_register_leds(struct device *dev, + struct ab5500_hvleds_platform_data *pdata, + struct ab5500_hvleds *hvleds) +{ + int i_led; + int err; + struct ab5500_led_conf *pled; + struct ab5500_led *led; + + hvleds->dev = dev; + hvleds->pdata = pdata; + for (i_led = 0; i_led < AB5500_HVLEDS_MAX; i_led++) { + pled = &pdata->leds[i_led]; + led = &hvleds->leds[i_led]; + + INIT_WORK(&led->led_work, ab5500_led_work); + + led->id = pled->led_id; + led->max_current = pled->max_current; + led->status = pled->status; + led->led_cdev.name = pled->name; + led->led_cdev.brightness_set = ab5500_led_brightness_set; + + err = led_classdev_register(dev, &led->led_cdev); + if (err < 0) { + dev_err(dev, "Register led class failed: %d\n", err); + goto bailout1; + } + + err = device_create_file(led->led_cdev.dev, + &dev_attr_led_current); + if (err < 0) { + dev_err(dev, "sysfs device creation failed: %d\n", err); + goto bailout2; + } + } + + return err; + for (; i_led >= 0; i_led--) { + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); +bailout2: + led_classdev_unregister(&hvleds->leds[i_led].led_cdev); +bailout1: + cancel_work_sync(&hvleds->leds[i_led].led_work); + } + return err; +} + +static int __devinit ab5500_hvleds_probe(struct platform_device *pdev) +{ + struct ab5500_hvleds_platform_data *pdata = pdev->dev.platform_data; + struct ab5500_hvleds *hvleds = NULL; + int err = 0, i; + + if (pdata == NULL) { + dev_err(&pdev->dev, "platform data required\n"); + err = -ENODEV; + goto err_out; + } + + hvleds = kzalloc(sizeof(struct ab5500_hvleds), GFP_KERNEL); + if (hvleds == NULL) { + err = -ENOMEM; + goto err_out; + } + + mutex_init(&hvleds->lock); + + /* init leds data and register led_classdev */ + err = ab5500_led_register_leds(&pdev->dev, pdata, hvleds); + if (err < 0) { + dev_err(&pdev->dev, "leds registeration failed\n"); + goto err_out; + } + + /* init device registers and set initial led current */ + err = ab5500_led_init_registers(hvleds); + if (err < 0) { + dev_err(&pdev->dev, "reg init failed: %d\n", err); + goto err_reg_init; + } + + dev_info(&pdev->dev, "enabled\n"); + + return err; + +err_reg_init: + for (i = 0; i < AB5500_HVLEDS_MAX; i++) { + struct ab5500_led *led = &hvleds->leds[i]; + + led_classdev_unregister(&led->led_cdev); + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); + cancel_work_sync(&led->led_work); + } +err_out: + kfree(hvleds); + return err; +} + +static int __devexit ab5500_hvleds_remove(struct platform_device *pdev) +{ + struct ab5500_hvleds *hvleds = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < AB5500_HVLEDS_MAX; i++) { + struct ab5500_led *led = &hvleds->leds[i]; + + led_classdev_unregister(&led->led_cdev); + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); + cancel_work_sync(&led->led_work); + } + kfree(hvleds); + return 0; +} + +static struct platform_driver ab5500_hvleds_driver = { + .driver = { + .name = AB5500LED_NAME, + .owner = THIS_MODULE, + }, + .probe = ab5500_hvleds_probe, + .remove = __devexit_p(ab5500_hvleds_remove), +}; + +static int __init ab5500_hvleds_module_init(void) +{ + return platform_driver_register(&ab5500_hvleds_driver); +} + +static void __exit ab5500_hvleds_module_exit(void) +{ + platform_driver_unregister(&ab5500_hvleds_driver); +} + +module_init(ab5500_hvleds_module_init); +module_exit(ab5500_hvleds_module_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Shreshtha Kumar SAHU "); +MODULE_DESCRIPTION("Driver for AB5500 HVLED"); + diff --git a/include/linux/leds-ab5500.h b/include/linux/leds-ab5500.h new file mode 100644 index 00000000000..2db6ffd188e --- /dev/null +++ b/include/linux/leds-ab5500.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011 ST-Ericsson SA. + * + * License Terms: GNU General Public License v2 + * + * Simple driver for HVLED in ST-Ericsson AB5500 Analog baseband Controller + * + * Author: Shreshtha Kumar SAHU + */ + +#define AB5500_HVLED0 0 +#define AB5500_HVLED1 1 +#define AB5500_HVLED2 2 +#define AB5500_HVLEDS_MAX 3 + +enum ab5500_led_status { + AB5500_LED_OFF = 0x00, + AB5500_LED_ON, +}; + +struct ab5500_led_conf { + char *name; + u8 led_id; + enum ab5500_led_status status; + u8 max_current; +}; + +struct ab5500_hvleds_platform_data { + bool hw_blink; + struct ab5500_led_conf leds[AB5500_HVLEDS_MAX]; +}; -- cgit v1.2.3 From 3400e1badf6e58370222c9c7d27439e72495e4ff Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 11 May 2011 14:35:30 +0200 Subject: U5500 : Update of ab5500 plaftorm data structure Bring back AB5500 initial data setting structure in petra platform data.Required by CG2900 chip driver ST-Ericsson Linux next: NA ST-Ericsson ID: 257121 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I7626d10611a32ea18619e47f21ed7a53016592be Signed-off-by: Bibek Basu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/22763 Reviewed-by: Lukasz RYMANOWSKI Reviewed-by: QATOOLS Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR Conflicts: drivers/mfd/ab5500-core.c --- drivers/mfd/ab5500-core.c | 41 ++++++++++++++++++++++++++++++++++++++- include/linux/mfd/abx500/ab5500.h | 4 +++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 2fc1cf1b101..6f850853ce7 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -2171,6 +2171,40 @@ static inline void ab5500_remove_debugfs(void) } #endif +/* + * ab5500_setup : Basic set-up, datastructure creation/destruction + * and I2C interface.This sets up a default config + * in the AB5500 chip so that it will work as expected. + * @ab : Pointer to ab5500 structure + * @settings : Pointer to struct abx500_init_settings + * @size : Size of init data + */ +static int __init ab5500_setup(struct ab5500 *ab, + struct abx500_init_settings *settings, unsigned int size) +{ + int err = 0; + int i; + + for (i = 0; i < size; i++) { + err = mask_and_set_register_interruptible(ab, + settings[i].bank, + settings[i].reg, + 0xFF, settings[i].setting); + if (err) + goto exit_no_setup; + + /* If event mask register update the event mask in ab5500 */ + if ((settings[i].bank == AB5500_BANK_IT) && + (AB5500_MASK_BASE <= settings[i].reg) && + (settings[i].reg <= AB5500_MASK_END)) { + ab->mask[settings[i].reg - AB5500_MASK_BASE] = + settings[i].setting; + } + } +exit_no_setup: + return err; +} + static void ab5500_irq_mask(struct irq_data *data) { struct ab5500 *ab = irq_data_get_irq_chip_data(data); @@ -2394,7 +2428,12 @@ static int __init ab5500_probe(struct platform_device *pdev) dev_err(&pdev->dev, "ab5500_mfd_add_device error\n"); goto exit_no_irq; } - + err = ab5500_setup(ab, ab5500_plf_data->init_settings, + ab5500_plf_data->init_settings_sz); + if (err) { + dev_err(&pdev->dev, "ab5500_setup error\n"); + goto exit_no_irq; + } ab5500_setup_debugfs(ab); return 0; diff --git a/include/linux/mfd/abx500/ab5500.h b/include/linux/mfd/abx500/ab5500.h index 1b2b150072a..58db5206fa9 100644 --- a/include/linux/mfd/abx500/ab5500.h +++ b/include/linux/mfd/abx500/ab5500.h @@ -134,9 +134,11 @@ struct ab5500 { struct ab5500_regulator_platform_data; struct ab5500_platform_data { struct {unsigned int base; unsigned int count; } irq; - bool pm_power_off; void *dev_data[AB5500_NUM_DEVICES]; size_t dev_data_sz[AB5500_NUM_DEVICES]; + struct abx500_init_settings *init_settings; + unsigned int init_settings_sz; + bool pm_power_off; struct ab5500_regulator_platform_data *regulator; }; -- cgit v1.2.3 From 75d4b12b126a81ccbe2e02bd1373c65e403840e5 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 12 Oct 2011 16:18:34 +0200 Subject: Fix Introduced warnings Signed-off-by: Philippe Langlais --- drivers/mfd/ab8500-debugfs.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index 712b2d064b7..f8a5684205b 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -774,9 +774,9 @@ static ssize_t hwreg_common_write(char *b, struct hwreg_cfg *cfg, /* args are ok, update target cfg (mainly for read) */ *cfg = loc; -#if ABB_HWREG_DEBUG - pr_warn("HWREG request: %s, %s, addr=0x%08X, mask=0x%X, shift=%d - value=0x%X\n", (write) ? "write" : "read", +#ifdef ABB_HWREG_DEBUG + pr_warn("HWREG request: %s, %s, addr=0x%08X, mask=0x%X, shift=%d" + "value=0x%X\n", (write) ? "write" : "read", REG_FMT_DEC(cfg) ? "decimal" : "hexa", cfg->addr, cfg->mask, cfg->shift, val); #endif -- cgit v1.2.3 From f7c146a24ede70952806255074809d98c54a3a26 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 12 Oct 2011 16:19:40 +0200 Subject: mach-ux500: ux500 defconfig update Merge all u8500 & u5500 defconfigs in one. FIXME: SND_SOC_UX500_AB8500 (AB8500 audio codec) not set conflicts with U5500 Signed-off-by: Philippe Langlais Conflicts: arch/arm/configs/u8500_defconfig --- arch/arm/configs/u8500_defconfig | 54 +++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index f82ec2e33d0..7401e0ac6e6 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -1,5 +1,6 @@ CONFIG_EXPERIMENTAL=y # CONFIG_LOCALVERSION_AUTO is not set +# CONFIG_SWAP is not set CONFIG_SYSVIPC=y CONFIG_BSD_PROCESS_ACCT=y CONFIG_LOG_BUF_SHIFT=18 @@ -27,7 +28,7 @@ CONFIG_MACH_HREFV60=y CONFIG_MACH_U8500_SNOWBALL=y CONFIG_MACH_U5500=y CONFIG_DB8500_MLOADER=y -CONFIG_U8500_CPUIDLE_DEEPEST_STATE=3 +CONFIG_UX500_DEBUG_HWREG=y CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y @@ -90,12 +91,11 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y -CONFIG_CFG80211=m # CONFIG_CFG80211_WEXT is not set -CONFIG_MAC80211=m CONFIG_MAC80211_LEDS=y CONFIG_RFKILL=y CONFIG_RFKILL_INPUT=y +CONFIG_NET_9P=y CONFIG_CAIF=y CONFIG_UEVENT_HELPER_PATH="/sbin/hotplug" # CONFIG_STANDALONE is not set @@ -117,7 +117,7 @@ CONFIG_STE_TRACE_MODEM=y CONFIG_STM_TRACE=y CONFIG_U8500_SHRM=y CONFIG_U8500_SHRM_MODEM_SILENT_RESET=y -CONFIG_STM_I2S_TEST_PROTOCOL_DRIVER=y +# CONFIG_STE_AUDIO_IO_DEV is not set CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y CONFIG_NETDEVICES=y @@ -125,15 +125,10 @@ CONFIG_TUN=y CONFIG_SMSC_PHY=y CONFIG_NET_ETHERNET=y CONFIG_SMSC911X=y -#CONFIG_USB_USBNET is not set -# CONFIG_USB_NET_AX8817X is not set -# CONFIG_USB_NET_NET1080 is not set -# CONFIG_USB_BELKIN is not set -# CONFIG_USB_ARMLINUX is not set -# CONFIG_USB_NET_ZAURUS is not set # CONFIG_INPUT_MOUSEDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set +CONFIG_KEYBOARD_DB5500=y CONFIG_KEYBOARD_GPIO=y CONFIG_KEYBOARD_NOMADIK_SKE=y CONFIG_KEYBOARD_STMPE=y @@ -142,6 +137,7 @@ CONFIG_KEYBOARD_TC3589X=y CONFIG_INPUT_TOUCHSCREEN=y CONFIG_TOUCHSCREEN_BU21013=y CONFIG_INPUT_MISC=y +CONFIG_INPUT_AB8500_ACCDET=y CONFIG_INPUT_AB8500_PONKEY=y CONFIG_INPUT_UINPUT=y CONFIG_VT_HW_CONSOLE_BINDING=y @@ -171,6 +167,7 @@ CONFIG_WATCHDOG=y CONFIG_MPCORE_WATCHDOG=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y +CONFIG_AB5500_CORE=y CONFIG_AB8500_CORE=y CONFIG_REGULATOR_DEBUG=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y @@ -217,11 +214,14 @@ CONFIG_LEDS_LM3530=y CONFIG_LEDS_LP5521=y CONFIG_LEDS_PWM=y CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_AB=y CONFIG_RTC_DRV_AB8500=y CONFIG_RTC_DRV_PL031=y CONFIG_DMADEVICES=y CONFIG_STE_DMA40=y CONFIG_STAGING=y +# CONFIG_STAGING_EXCLUDE_BUILD is not set +CONFIG_AUTOFS_FS=m CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y CONFIG_CG2900=y CONFIG_CG2900_CHIP=y @@ -230,6 +230,9 @@ CONFIG_CG2900_UART=y CONFIG_CG2900_AUDIO=y CONFIG_CG2900_TEST=y CONFIG_BT_CG2900=y +CONFIG_CW1200=m +CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES=y +CONFIG_CW1200_USE_GPIO_IRQ=y CONFIG_EXT2_FS=y CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y @@ -240,16 +243,33 @@ CONFIG_EXT3_FS_POSIX_ACL=y CONFIG_EXT4_FS=y CONFIG_EXT4_FS_POSIX_ACL=y CONFIG_QUOTA=y +CONFIG_QFMT_V2=y +CONFIG_AUTOFS4_FS=m +CONFIG_ISO9660_FS=y +CONFIG_JOLIET=y +CONFIG_ZISOFS=y +CONFIG_UDF_FS=m +CONFIG_MSDOS_FS=y CONFIG_VFAT_FS=y CONFIG_TMPFS=y CONFIG_TMPFS_POSIX_ACL=y -CONFIG_CONFIGFS_FS=m -# CONFIG_MISC_FILESYSTEMS is not set -CONFIG_NFS_FS=y -CONFIG_ROOT_NFS=y -# CONFIG_NETWORK_FILESYSTEMS is not set +CONFIG_HFS_FS=m +CONFIG_BEFS_FS=m +CONFIG_CRAMFS=m +CONFIG_VXFS_FS=m +CONFIG_MINIX_FS=m +CONFIG_ROMFS_FS=m +CONFIG_SYSV_FS=m +CONFIG_UFS_FS=m +CONFIG_9P_FS=y CONFIG_PARTITION_ADVANCED=y CONFIG_BLKDEV_PARTITION=y +CONFIG_BSD_DISKLABEL=y +CONFIG_MINIX_SUBPARTITION=y +CONFIG_SOLARIS_X86_PARTITION=y +CONFIG_UNIXWARE_DISKLABEL=y +CONFIG_SGI_PARTITION=y +CONFIG_SUN_PARTITION=y CONFIG_NLS_CODEPAGE_437=y CONFIG_NLS_ISO8859_1=y CONFIG_PRINTK_TIME=y @@ -272,9 +292,13 @@ CONFIG_DEBUG_USER=y CONFIG_DEBUG_ERRORS=y CONFIG_KEYS=y CONFIG_CRYPTO_ECB=y +CONFIG_CRYPTO_HMAC=m +CONFIG_CRYPTO_MD5=m +CONFIG_CRYPTO_SHA1=m CONFIG_CRYPTO_AES=y CONFIG_CRYPTO_ARC4=y # CONFIG_CRYPTO_ANSI_CPRNG is not set CONFIG_CRYPTO_DEV_UX500=y CONFIG_CRYPTO_DEV_UX500_CRYP=y CONFIG_CRC7=y +CONFIG_LIBCRC32C=m -- cgit v1.2.3 From 0dae3c24d75ccceb95e9effe9c8eee744a06bdac Mon Sep 17 00:00:00 2001 From: Dmitry Tarnyagin Date: Wed, 25 May 2011 00:05:09 +0200 Subject: cw1200: Enable WEXT compatibility. User-space tools like wpa_supplicant still use legacy wireless extensions for wlan device management. CONFIG_CFG80211_WEXT should be set in the kernel config to enable wireless extension compatibility in the kernel wireless stack. Change-Id: I661eaa0cf3b854b2bb08ecac309d5d050c027a2b Signed-off-by: Dmitry Tarnyagin Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23802 Reviewed-by: Bartosz MARKOWSKI Reviewed-by: Philippe LANGLAIS --- arch/arm/configs/u8500_defconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 7401e0ac6e6..aa7d9f2b3bc 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -91,7 +91,6 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y -# CONFIG_CFG80211_WEXT is not set CONFIG_MAC80211_LEDS=y CONFIG_RFKILL=y CONFIG_RFKILL_INPUT=y -- cgit v1.2.3 From b0538663e66d7469c744698da6a9549052a88779 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 27 May 2011 12:03:53 +0200 Subject: serial: pl011: Fix enable of pins ST-Ericsson Linux next: 342766 ST-Ericsson ID: 340139 ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Robert Marklund Change-Id: I47692d7a7993cbeda53dfca737d6346f043336f3 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24059 Reviewed-by: Par-Gunnar HJALMDAHL Reviewed-by: Dmitry TARNYAGIN Reviewed-by: Philippe LANGLAIS --- drivers/tty/serial/amba-pl011.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index f5f6831b0a6..ff4761e482c 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -1493,6 +1493,14 @@ static void pl011_shutdown(struct uart_port *port) if (uap->lcrh_rx != uap->lcrh_tx) pl011_shutdown_channel(uap, uap->lcrh_tx); + if (uap->port.dev->platform_data) { + struct amba_pl011_data *plat; + + plat = uap->port.dev->platform_data; + if (plat->exit) + plat->exit(); + } + /* * Shut down the clock producer */ -- cgit v1.2.3 From 9a711dcc10cbf7b11b372fd5865f8725ea95da49 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Wed, 18 May 2011 18:08:48 +0200 Subject: config: Add fixed regulator driver ST-Ericsson Linux next: 340134 ST-Ericsson ID: 340139 ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Robert Marklund Change-Id: Ic187bf313f2f420c4cd4cbbe1a6bf69a0bb48a95 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24084 Reviewed-by: Philippe LANGLAIS Reviewed-by: Dmitry TARNYAGIN Reviewed-by: Par-Gunnar HJALMDAHL --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index aa7d9f2b3bc..ff78f99add0 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -171,6 +171,7 @@ CONFIG_AB8500_CORE=y CONFIG_REGULATOR_DEBUG=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y # CONFIG_MEDIA_TUNER_CUSTOMISE is not set -- cgit v1.2.3 From b35af96764b300e17408980e11d349a07c2702cb Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Thu, 19 May 2011 17:54:50 +0200 Subject: config: Add cg2900 deps BT and HCIUART ST-Ericsson Linux next: 340134 ST-Ericsson ID: 340139 ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Robert Marklund Change-Id: Ie01c6904beb23f0c72163ab262d4a34ed253115e Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24087 Reviewed-by: Philippe LANGLAIS --- arch/arm/configs/u8500_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index ff78f99add0..e6ca27d6ca7 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -83,6 +83,7 @@ CONFIG_IP_NF_TARGET_MASQUERADE=y CONFIG_PHONET=y CONFIG_PHONET_PIPECTRLR=y CONFIG_NET_SCHED=y +CONFIG_BT=y CONFIG_BT_L2CAP=y CONFIG_BT_SCO=y CONFIG_BT_RFCOMM=y @@ -91,6 +92,8 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y +CONFIG_CFG80211_WEXT=y +CONFIG_BT_HCIUART=y CONFIG_MAC80211_LEDS=y CONFIG_RFKILL=y CONFIG_RFKILL_INPUT=y -- cgit v1.2.3 From bf900d6396b0b7db7b868939d6be3cd3940a5bae Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 13 Oct 2011 16:43:12 +0200 Subject: mfd: abx500: Move ab5500 include Move the content of linux/mfd/abx500/ab5500.h to the mainline header linux/mfd/abx500.h Also change the clients of this file. ST-Ericsson Linux next: OK ST-Ericsson ID: 342253 ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Robert Marklund Change-Id: I30f7e2978d7bb00408597fdbdce8e7b6fdca397f Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24725 Reviewed-by: Philippe LANGLAIS Conflicts: drivers/regulator/ab5500.c --- drivers/leds/leds-ab5500.c | 1 - drivers/mfd/ab5500-core.c | 1 - drivers/mfd/ab5500-power.c | 1 - drivers/rtc/rtc-ab.c | 1 - include/linux/mfd/abx500.h | 139 ++++++++++++++++++++++++++++++++++++ include/linux/mfd/abx500/ab5500.h | 146 -------------------------------------- 6 files changed, 139 insertions(+), 150 deletions(-) delete mode 100644 include/linux/mfd/abx500/ab5500.h diff --git a/drivers/leds/leds-ab5500.c b/drivers/leds/leds-ab5500.c index 94db3a6feea..e4376d1b59b 100644 --- a/drivers/leds/leds-ab5500.c +++ b/drivers/leds/leds-ab5500.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 6f850853ce7..d471db6c406 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/mfd/ab5500-power.c b/drivers/mfd/ab5500-power.c index 5bf6a4c5e97..46c2ae8c321 100644 --- a/drivers/mfd/ab5500-power.c +++ b/drivers/mfd/ab5500-power.c @@ -10,7 +10,6 @@ #include #include -#include static struct device *dev; diff --git a/drivers/rtc/rtc-ab.c b/drivers/rtc/rtc-ab.c index db1992632fa..8e595e05d99 100644 --- a/drivers/rtc/rtc-ab.c +++ b/drivers/rtc/rtc-ab.c @@ -13,7 +13,6 @@ #include #include #include -#include #define AB5500_RTC_CLOCK_RATE 32768 #define AB5500_RTC 0x00 diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 86a17b29cc1..fb888e80da9 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -206,6 +206,145 @@ struct ab3550_platform_data { unsigned int init_settings_sz; }; +/** + * + * ab5500 + * + */ + +enum ab5500_devid { + AB5500_DEVID_ADC, + AB5500_DEVID_LEDS, + AB5500_DEVID_POWER, + AB5500_DEVID_REGULATORS, + AB5500_DEVID_SIM, + AB5500_DEVID_RTC, + AB5500_DEVID_CHARGER, + AB5500_DEVID_FUELGAUGE, + AB5500_DEVID_VIBRATOR, + AB5500_DEVID_CODEC, + AB5500_DEVID_USB, + AB5500_DEVID_OTP, + AB5500_DEVID_VIDEO, + AB5500_DEVID_DBIECI, + AB5500_DEVID_ONSWA, + AB5500_NUM_DEVICES, +}; + +enum ab5500_banks { + AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP = 0, + AB5500_BANK_VDDDIG_IO_I2C_CLK_TST = 1, + AB5500_BANK_VDENC = 2, + AB5500_BANK_SIM_USBSIM = 3, + AB5500_BANK_LED = 4, + AB5500_BANK_ADC = 5, + AB5500_BANK_RTC = 6, + AB5500_BANK_STARTUP = 7, + AB5500_BANK_DBI_ECI = 8, + AB5500_BANK_CHG = 9, + AB5500_BANK_FG_BATTCOM_ACC = 10, + AB5500_BANK_USB = 11, + AB5500_BANK_IT = 12, + AB5500_BANK_VIBRA = 13, + AB5500_BANK_AUDIO_HEADSETUSB = 14, + AB5500_NUM_BANKS = 15, +}; + +enum ab5500_banks_addr { + AB5500_ADDR_VIT_IO_I2C_CLK_TST_OTP = 0x4A, + AB5500_ADDR_VDDDIG_IO_I2C_CLK_TST = 0x4B, + AB5500_ADDR_VDENC = 0x06, + AB5500_ADDR_SIM_USBSIM = 0x04, + AB5500_ADDR_LED = 0x10, + AB5500_ADDR_ADC = 0x0A, + AB5500_ADDR_RTC = 0x0F, + AB5500_ADDR_STARTUP = 0x03, + AB5500_ADDR_DBI_ECI = 0x07, + AB5500_ADDR_CHG = 0x0B, + AB5500_ADDR_FG_BATTCOM_ACC = 0x0C, + AB5500_ADDR_USB = 0x05, + AB5500_ADDR_IT = 0x0E, + AB5500_ADDR_VIBRA = 0x02, + AB5500_ADDR_AUDIO_HEADSETUSB = 0x0D, +}; + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB5500_IT_SOURCE0_REG 0x20 +#define AB5500_IT_SOURCE1_REG 0x21 +#define AB5500_IT_SOURCE2_REG 0x22 +#define AB5500_IT_SOURCE3_REG 0x23 +#define AB5500_IT_SOURCE4_REG 0x24 +#define AB5500_IT_SOURCE5_REG 0x25 +#define AB5500_IT_SOURCE6_REG 0x26 +#define AB5500_IT_SOURCE7_REG 0x27 +#define AB5500_IT_SOURCE8_REG 0x28 +#define AB5500_IT_SOURCE9_REG 0x29 +#define AB5500_IT_SOURCE10_REG 0x2A +#define AB5500_IT_SOURCE11_REG 0x2B +#define AB5500_IT_SOURCE12_REG 0x2C +#define AB5500_IT_SOURCE13_REG 0x2D +#define AB5500_IT_SOURCE14_REG 0x2E +#define AB5500_IT_SOURCE15_REG 0x2F +#define AB5500_IT_SOURCE16_REG 0x30 +#define AB5500_IT_SOURCE17_REG 0x31 +#define AB5500_IT_SOURCE18_REG 0x32 +#define AB5500_IT_SOURCE19_REG 0x33 +#define AB5500_IT_SOURCE20_REG 0x34 +#define AB5500_IT_SOURCE21_REG 0x35 +#define AB5500_IT_SOURCE22_REG 0x36 +#define AB5500_IT_SOURCE23_REG 0x37 + +#define AB5500_NUM_IRQ_REGS 23 + +/** + * struct ab5500 + * @access_mutex: lock out concurrent accesses to the AB registers + * @dev: a pointer to the device struct for this chip driver + * @ab5500_irq: the analog baseband irq + * @irq_base: the platform configuration irq base for subdevices + * @chip_name: name of this chip variant + * @chip_id: 8 bit chip ID for this chip variant + * @irq_lock: a lock to protect the mask + * @abb_events: a local bit mask of the prcmu wakeup events + * @event_mask: a local copy of the mask event registers + * @last_event_mask: a copy of the last event_mask written to hardware + * @startup_events: a copy of the first reading of the event registers + * @startup_events_read: whether the first events have been read + */ +struct ab5500 { + struct mutex access_mutex; + struct device *dev; + unsigned int ab5500_irq; + unsigned int irq_base; + char chip_name[32]; + u8 chip_id; + struct mutex irq_lock; + u32 abb_events; + u8 mask[AB5500_NUM_IRQ_REGS]; + u8 oldmask[AB5500_NUM_IRQ_REGS]; + u8 startup_events[AB5500_NUM_IRQ_REGS]; + bool startup_events_read; +#ifdef CONFIG_DEBUG_FS + unsigned int debug_bank; + unsigned int debug_address; +#endif +}; + +struct ab5500_regulator_platform_data; +struct ab5500_platform_data { + struct {unsigned int base; unsigned int count; } irq; + void *dev_data[AB5500_NUM_DEVICES]; + size_t dev_data_sz[AB5500_NUM_DEVICES]; + struct abx500_init_settings *init_settings; + unsigned int init_settings_sz; + bool pm_power_off; + struct ab5500_regulator_platform_data *regulator; +}; + + int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, u8 value); int abx500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, diff --git a/include/linux/mfd/abx500/ab5500.h b/include/linux/mfd/abx500/ab5500.h deleted file mode 100644 index 58db5206fa9..00000000000 --- a/include/linux/mfd/abx500/ab5500.h +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) ST-Ericsson BLR 2011 - * - * License Terms: GNU General Public License v2 - * Author: Bibek Basu - */ -#ifndef MFD_AB5500_H -#define MFD_AB5500_H - -#include - - -enum ab5500_devid { - AB5500_DEVID_ADC, - AB5500_DEVID_LEDS, - AB5500_DEVID_POWER, - AB5500_DEVID_REGULATORS, - AB5500_DEVID_SIM, - AB5500_DEVID_RTC, - AB5500_DEVID_CHARGER, - AB5500_DEVID_FUELGAUGE, - AB5500_DEVID_VIBRATOR, - AB5500_DEVID_CODEC, - AB5500_DEVID_USB, - AB5500_DEVID_OTP, - AB5500_DEVID_VIDEO, - AB5500_DEVID_DBIECI, - AB5500_DEVID_ONSWA, - AB5500_NUM_DEVICES, -}; - -enum ab5500_banks { - AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP = 0, - AB5500_BANK_VDDDIG_IO_I2C_CLK_TST = 1, - AB5500_BANK_VDENC = 2, - AB5500_BANK_SIM_USBSIM = 3, - AB5500_BANK_LED = 4, - AB5500_BANK_ADC = 5, - AB5500_BANK_RTC = 6, - AB5500_BANK_STARTUP = 7, - AB5500_BANK_DBI_ECI = 8, - AB5500_BANK_CHG = 9, - AB5500_BANK_FG_BATTCOM_ACC = 10, - AB5500_BANK_USB = 11, - AB5500_BANK_IT = 12, - AB5500_BANK_VIBRA = 13, - AB5500_BANK_AUDIO_HEADSETUSB = 14, - AB5500_NUM_BANKS = 15, -}; - -enum ab5500_banks_addr { - AB5500_ADDR_VIT_IO_I2C_CLK_TST_OTP = 0x4A, - AB5500_ADDR_VDDDIG_IO_I2C_CLK_TST = 0x4B, - AB5500_ADDR_VDENC = 0x06, - AB5500_ADDR_SIM_USBSIM = 0x04, - AB5500_ADDR_LED = 0x10, - AB5500_ADDR_ADC = 0x0A, - AB5500_ADDR_RTC = 0x0F, - AB5500_ADDR_STARTUP = 0x03, - AB5500_ADDR_DBI_ECI = 0x07, - AB5500_ADDR_CHG = 0x0B, - AB5500_ADDR_FG_BATTCOM_ACC = 0x0C, - AB5500_ADDR_USB = 0x05, - AB5500_ADDR_IT = 0x0E, - AB5500_ADDR_VIBRA = 0x02, - AB5500_ADDR_AUDIO_HEADSETUSB = 0x0D, -}; - -/* - * Interrupt register offsets - * Bank : 0x0E - */ -#define AB5500_IT_SOURCE0_REG 0x20 -#define AB5500_IT_SOURCE1_REG 0x21 -#define AB5500_IT_SOURCE2_REG 0x22 -#define AB5500_IT_SOURCE3_REG 0x23 -#define AB5500_IT_SOURCE4_REG 0x24 -#define AB5500_IT_SOURCE5_REG 0x25 -#define AB5500_IT_SOURCE6_REG 0x26 -#define AB5500_IT_SOURCE7_REG 0x27 -#define AB5500_IT_SOURCE8_REG 0x28 -#define AB5500_IT_SOURCE9_REG 0x29 -#define AB5500_IT_SOURCE10_REG 0x2A -#define AB5500_IT_SOURCE11_REG 0x2B -#define AB5500_IT_SOURCE12_REG 0x2C -#define AB5500_IT_SOURCE13_REG 0x2D -#define AB5500_IT_SOURCE14_REG 0x2E -#define AB5500_IT_SOURCE15_REG 0x2F -#define AB5500_IT_SOURCE16_REG 0x30 -#define AB5500_IT_SOURCE17_REG 0x31 -#define AB5500_IT_SOURCE18_REG 0x32 -#define AB5500_IT_SOURCE19_REG 0x33 -#define AB5500_IT_SOURCE20_REG 0x34 -#define AB5500_IT_SOURCE21_REG 0x35 -#define AB5500_IT_SOURCE22_REG 0x36 -#define AB5500_IT_SOURCE23_REG 0x37 - -#define AB5500_NUM_IRQ_REGS 23 - -/** - * struct ab5500 - * @access_mutex: lock out concurrent accesses to the AB registers - * @dev: a pointer to the device struct for this chip driver - * @ab5500_irq: the analog baseband irq - * @irq_base: the platform configuration irq base for subdevices - * @chip_name: name of this chip variant - * @chip_id: 8 bit chip ID for this chip variant - * @irq_lock: a lock to protect the mask - * @abb_events: a local bit mask of the prcmu wakeup events - * @event_mask: a local copy of the mask event registers - * @last_event_mask: a copy of the last event_mask written to hardware - * @startup_events: a copy of the first reading of the event registers - * @startup_events_read: whether the first events have been read - */ -struct ab5500 { - struct mutex access_mutex; - struct device *dev; - unsigned int ab5500_irq; - unsigned int irq_base; - char chip_name[32]; - u8 chip_id; - struct mutex irq_lock; - u32 abb_events; - u8 mask[AB5500_NUM_IRQ_REGS]; - u8 oldmask[AB5500_NUM_IRQ_REGS]; - u8 startup_events[AB5500_NUM_IRQ_REGS]; - bool startup_events_read; -#ifdef CONFIG_DEBUG_FS - unsigned int debug_bank; - unsigned int debug_address; -#endif -}; - -struct ab5500_regulator_platform_data; -struct ab5500_platform_data { - struct {unsigned int base; unsigned int count; } irq; - void *dev_data[AB5500_NUM_DEVICES]; - size_t dev_data_sz[AB5500_NUM_DEVICES]; - struct abx500_init_settings *init_settings; - unsigned int init_settings_sz; - bool pm_power_off; - struct ab5500_regulator_platform_data *regulator; -}; - - -#endif /* MFD_AB5500_H */ -- cgit v1.2.3 From 5c9ff0d52092c96b9b10253f0f5d6c25aeede961 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Wed, 25 May 2011 17:44:23 +0200 Subject: config: u8500: Enable the new asoc drivers ST-Ericsson Linux next: 342252 ST-Ericsson ID: 342253 ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Robert Marklund Change-Id: I0f379ee7deeccabe9cbaad29f9ad3f67f269a24d Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24729 Reviewed-by: Ola LILJA2 Reviewed-by: Philippe LANGLAIS --- arch/arm/configs/u8500_defconfig | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index e6ca27d6ca7..29ce46b9b02 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -22,9 +22,12 @@ CONFIG_DEFAULT_DEADLINE=y CONFIG_ARCH_U8500=y CONFIG_UX500_SOC_DB5500=y CONFIG_UX500_SOC_DB8500=y -CONFIG_MACH_U8500=y -CONFIG_MACH_SNOWBALL=y +CONFIG_SND_SOC_UX500_AB5500=y +CONFIG_SND_SOC_UX500_AB8500=y +CONFIG_SND_SOC_UX500_AV8100=y +CONFIG_SND_SOC_UX500_CG29XX=y CONFIG_MACH_HREFV60=y +CONFIG_MACH_SNOWBALL=y CONFIG_MACH_U8500_SNOWBALL=y CONFIG_MACH_U5500=y CONFIG_DB8500_MLOADER=y @@ -183,9 +186,14 @@ CONFIG_FB=y CONFIG_FB_MCDE=y CONFIG_FB_B2R2=y CONFIG_B2R2_PLUG_CONF=y +# CONFIG_AV8100_HWTRIG_DSI_TE is not set +CONFIG_AV8100_HWTRIG_I2SDAT3=y CONFIG_SOUND=y CONFIG_SND=y CONFIG_SND_USB_AUDIO=y +CONFIG_SND_SOC=y +CONFIG_SND_SOC_UX500=y +CONFIG_SND_SOC_UX500_AB8500=y CONFIG_USB=y # CONFIG_USB_DEVICE_CLASS is not set CONFIG_USB_SUSPEND=y -- cgit v1.2.3 From e27a4e2c7a2052f3061c4ec5d08a9044f28a2773 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Tue, 3 May 2011 15:32:06 +0200 Subject: arm: add functions to control prefetch in outer_cache_fns This patch introduces the outer_cache_fns.prefetch_enable and outer_cache_fns.prefetch_disable function pointers. These can be used to control prefetching in the outer cache. ST-Ericsson ID: ER337734 Change-Id: I0885dbba0658f7d7b1081e30418dc0aab66a148a Signed-off-by: Mian Yousaf Kaukab Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/22901 Reviewed-by: QATEST Reviewed-by: Jonas ABERG --- arch/arm/include/asm/outercache.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/arch/arm/include/asm/outercache.h b/arch/arm/include/asm/outercache.h index d8387437ec5..7ed8f98b217 100644 --- a/arch/arm/include/asm/outercache.h +++ b/arch/arm/include/asm/outercache.h @@ -33,6 +33,8 @@ struct outer_cache_fns { #ifdef CONFIG_OUTER_CACHE_SYNC void (*sync)(void); #endif + void (*prefetch_enable)(void); + void (*prefetch_disable)(void); void (*set_debug)(unsigned long); }; @@ -74,6 +76,18 @@ static inline void outer_disable(void) outer_cache.disable(); } +static inline void outer_prefetch_enable(void) +{ + if (outer_cache.prefetch_enable) + outer_cache.prefetch_enable(); +} + +static inline void outer_prefetch_disable(void) +{ + if (outer_cache.prefetch_disable) + outer_cache.prefetch_disable(); +} + #else static inline void outer_inv_range(phys_addr_t start, phys_addr_t end) -- cgit v1.2.3 From cdafc050680de5c76013aa4cdedef02776ce570e Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Fri, 14 Oct 2011 09:29:10 +0200 Subject: u5500: support RTC_CLK1/2 ST-Ericsson Linux next: - ST-Ericsson ID: WP257121 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I5feb1dea92e5cc5d4ce57120cfbd320fc54629af Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/22993 Reviewed-by: Bibek BASU Reviewed-by: Srinidhi KASAGAR Fix to merge with 335a4a "u5500: support RTC_CLK1/2" --- drivers/mfd/ab5500-core.c | 15 +++++++++++++-- drivers/mfd/ab5500-power.c | 33 ++++++++++++++++++++++++++++----- include/linux/mfd/abx500.h | 9 +++++++++ 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index d471db6c406..b50da3c5a76 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -529,7 +529,7 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { }, }, [AB5500_DEVID_POWER] = { - .nbanks = 1, + .nbanks = 2, .bank = (struct ab5500_i2c_ranges []) { { .bankid = AB5500_BANK_STARTUP, @@ -541,7 +541,18 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { .perm = AB5500_PERM_RW, }, }, - } + }, + { + .bankid = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x01, + .perm = AB5500_PERM_RW, + }, + }, + }, }, }, }; diff --git a/drivers/mfd/ab5500-power.c b/drivers/mfd/ab5500-power.c index 46c2ae8c321..d23960bc976 100644 --- a/drivers/mfd/ab5500-power.c +++ b/drivers/mfd/ab5500-power.c @@ -13,8 +13,29 @@ static struct device *dev; +/* STARTUP */ #define AB5500_SYSPOR_CONTROL 0x30 +/* VINT IO I2C CLOCK */ +#define AB5500_RTC_VINT 0x01 + +int ab5500_clock_rtc_enable(int num, bool enable) +{ + /* RTC_CLK{0,1,2} are bits {4,3,2}, active low */ + u8 mask = BIT(4 - num); + u8 value = enable ? 0 : mask; + + /* Don't allow RTC_CLK0 to be controlled. */ + if (num < 1 || num > 2) + return -EINVAL; + + if (!dev) + return -EAGAIN; + + return abx500_mask_and_set(dev, AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + AB5500_RTC_VINT, mask, value); +} + static void ab5500_power_off(void) { sigset_t old; @@ -34,18 +55,20 @@ static int __devinit ab5500_power_probe(struct platform_device *pdev) { struct ab5500_platform_data *plat = dev_get_platdata(pdev->dev.parent); - if (!plat->pm_power_off) - return -ENODEV; - dev = &pdev->dev; - pm_power_off = ab5500_power_off; + + if (plat->pm_power_off) + pm_power_off = ab5500_power_off; return 0; } static int __devexit ab5500_power_remove(struct platform_device *pdev) { - pm_power_off = NULL; + struct ab5500_platform_data *plat = dev_get_platdata(pdev->dev.parent); + + if (plat->pm_power_off) + pm_power_off = NULL; dev = NULL; return 0; diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index fb888e80da9..d8929038d09 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -333,6 +333,15 @@ struct ab5500 { #endif }; +#ifndef CONFIG_AB5500_CORE +static inline int ab5500_clock_rtc_enable(int num, bool enable) +{ + return -ENOSYS; +} +#else +extern int ab5500_clock_rtc_enable(int num, bool enable); +#endif + struct ab5500_regulator_platform_data; struct ab5500_platform_data { struct {unsigned int base; unsigned int count; } irq; -- cgit v1.2.3 From b122e4ca81b1b9ceee218921e9c3b3e0328cbef9 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Fri, 14 Oct 2011 09:39:29 +0200 Subject: kernel-doc: fix kernel-doc warnings fix the potential kernel-doc warnings ST-Ericsson Linux next: - ST-Ericsson ID: 327425 ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: om prakash Change-Id: Ibaa302ddbaa8e223b81bd047fceaf7c3b97af491 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23345 Reviewed-by: Rabin VINCENT Reviewed-by: Jonas ABERG Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- Documentation/DocBook/stmpe.tmpl | 6 +----- drivers/gpio/gpio-nomadik.c | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Documentation/DocBook/stmpe.tmpl b/Documentation/DocBook/stmpe.tmpl index 6759149d6ce..9e64a00f6b3 100644 --- a/Documentation/DocBook/stmpe.tmpl +++ b/Documentation/DocBook/stmpe.tmpl @@ -101,11 +101,7 @@
stmpe-keypad.c !Idrivers/input/keyboard/stmpe-keypad.c -
-
- stmpe-gpio.c -!Idrivers/gpio/stmpe-gpio.c -
+ diff --git a/drivers/gpio/gpio-nomadik.c b/drivers/gpio/gpio-nomadik.c index 2c212c732d7..be27545f16f 100644 --- a/drivers/gpio/gpio-nomadik.c +++ b/drivers/gpio/gpio-nomadik.c @@ -353,7 +353,7 @@ static int __nmk_config_pins(pin_cfg_t *cfgs, int num, bool sleep) /** * nmk_config_pin - configure a pin's mux attributes * @cfg: pin confguration - * + * @sleep: Non-zero to apply the sleep mode configuration * Configures a pin's mode (alternate function or GPIO), its pull up status, * and its sleep mode based on the specified configuration. The @cfg is * usually one of the SoC specific macros defined in mach/-pins.h. These -- cgit v1.2.3 From 7a138c28a62dcabfba0e501745b84c3c3f3d06e4 Mon Sep 17 00:00:00 2001 From: Kalle Komierowski Date: Wed, 18 May 2011 16:31:50 +0200 Subject: power: ab8500_bm: Fix for inconsistent lock state Debug prove locking warns about inconsistent lock state, this patch fixes that warning ST-Ericsson Linux next: Not tested ST-Ericsson ID: ER329423 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I8ebc082ea590f512ad71b197c88a6e6bb80e92bf Signed-off-by: Kalle Komierowski Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23366 Reviewed-by: QATEST Tested-by: Karl KOMIEROWSKI Reviewed-by: Jonas ABERG --- drivers/power/ab8500_charger.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index 96859456ef7..2f9f14d10bb 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -160,6 +160,7 @@ struct ab8500_charger_usb_state { bool usb_changed; int usb_current; enum ab8500_usb_state state; + spinlock_t usb_lock; }; /** @@ -1492,13 +1493,17 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work) static void ab8500_charger_usb_state_changed_work(struct work_struct *work) { int ret; + unsigned long flags; + struct ab8500_charger *di = container_of(work, struct ab8500_charger, usb_state_changed_work); if (!di->vbus_detected) return; + spin_lock_irqsave(&di->usb_state.usb_lock, flags); di->usb_state.usb_changed = false; + spin_unlock_irqrestore(&di->usb_state.usb_lock, flags); /* * wait for some time until you get updates from the usb stack @@ -2198,7 +2203,10 @@ static int ab8500_charger_usb_notifier_call(struct notifier_block *nb, dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n", __func__, bm_usb_state, mA); + spin_lock(&di->usb_state.usb_lock); di->usb_state.usb_changed = true; + spin_unlock(&di->usb_state.usb_lock); + di->usb_state.state = bm_usb_state; di->usb_state.usb_current = mA; @@ -2318,6 +2326,9 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) di->parent = dev_get_drvdata(pdev->dev.parent); di->gpadc = ab8500_gpadc_get(); + /* initialize lock */ + spin_lock_init(&di->usb_state.usb_lock); + plat = dev_get_platdata(di->parent->dev); /* get charger specific platform data */ -- cgit v1.2.3 From 1d34a6e1eae85a781b4e98188f24a322165c7384 Mon Sep 17 00:00:00 2001 From: Vijaya Kumar Kilari Date: Wed, 27 Apr 2011 16:56:26 +0530 Subject: AB5500: AB5500 Analog to Digital conversion driver AB5500 GPADC driver for battery/usb voltage, current and temperature measurement ST-Ericsson ID: WP332221 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ia841694711cbba168e7e705a5a2bc78c6e3db8dc Signed-off-by: Vijaya Kumar Kilari Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/21775 Reviewed-by: Arun MURTHY Tested-by: Arun MURTHY Reviewed-by: Johan PALSSON Reviewed-by: QATEST Reviewed-by: Linus WALLEIJ Fix to merge with 4e75305 "AB5500: AB5500 Analog to Digital conversion driver" --- drivers/mfd/Kconfig | 7 + drivers/mfd/Makefile | 1 + drivers/mfd/ab5500-gpadc.c | 1167 +++++++++++++++++++++++++++++++ include/linux/mfd/abx500/ab5500-gpadc.h | 69 ++ 4 files changed, 1244 insertions(+) create mode 100644 drivers/mfd/ab5500-gpadc.c create mode 100644 include/linux/mfd/abx500/ab5500-gpadc.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index c65235b5312..9833f0cd2d5 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -346,6 +346,13 @@ config AB5500_CORE battery-backed RTC, charging control, Regulators, LEDs, vibrator, system power and temperature, power management and ALSA sound. +config AB5500_GPADC + bool "AB5500 GPADC driver" + depends on AB5500_CORE + default y + help + AB5500 GPADC driver used to convert battery/usb voltage. + config PMIC_DA903X bool "Dialog Semiconductor DA9030/DA9034 PMIC Support" depends on I2C=y diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index a4955178af8..88c8aa619e0 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -89,6 +89,7 @@ obj-$(CONFIG_MFD_DB8500_PRCMU) += db8500-prcmu.o # ab8500-i2c need to come after db8500-prcmu (which provides the channel) obj-$(CONFIG_AB8500_I2C_CORE) += ab8500-i2c.o obj-$(CONFIG_MFD_DB5500_PRCMU) += db5500-prcmu.o +obj-$(CONFIG_AB5500_GPADC) += ab5500-gpadc.o obj-$(CONFIG_MFD_TIMBERDALE) += timberdale.o obj-$(CONFIG_PMIC_ADP5520) += adp5520.o obj-$(CONFIG_LPC_SCH) += lpc_sch.o diff --git a/drivers/mfd/ab5500-gpadc.c b/drivers/mfd/ab5500-gpadc.c new file mode 100644 index 00000000000..e90bd9a3aaa --- /dev/null +++ b/drivers/mfd/ab5500-gpadc.c @@ -0,0 +1,1167 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * Author: Vijaya Kumar K + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Manual mode ADC registers + */ +#define AB5500_GPADC_MANUAL_STAT_REG 0x1F +#define AB5500_GPADC_MANDATAL_REG 0x21 +#define AB5500_GPADC_MANDATAH_REG 0x20 +#define AB5500_GPADC_MANUAL_MUX_CTRL 0x22 +#define AB5500_GPADC_MANUAL_MODE_CTRL 0x23 +#define AB5500_GPADC_MANUAL_MODE_CTRL2 0x24 +/* + * Auto/Polling mode ADC registers + */ +#define AB5500_GPADC_AUTO_VBAT_MAX 0x26 +#define AB5500_GPADC_AUTO_VBAT_MIN_TXON 0x27 +#define AB5500_GPADC_AUTO_VBAT_MIN_NOTX 0x28 +#define AB5500_GPADC_AUTO_VBAT_AVGH 0x29 +#define AB5500_GPADC_AUTO_VBAT_AVGL 0x2A +#define AB5500_GPADC_AUTO_ICHAR_MAX 0x2B +#define AB5500_GPADC_AUTO_ICHAR_MIN 0x2C +#define AB5500_GPADC_AUTO_ICHAR_AVG 0x2D +#define AB5500_GPADC_AUTO_CTRL2 0x2F +#define AB5500_GPADC_AUTO_CTRL1 0x30 +#define AB5500_GPADC_AUTO_PWR_CTRL 0x31 +#define AB5500_GPADC_AUTO_TRIG_VBAT_MIN_TXON 0x32 +#define AB5500_GPADC_AUTO_TRIG_VBAT_MIN_NOTX 0x33 +#define AB5500_GPADC_AUTO_TRIG_ADOUT0_CTRL 0x34 +#define AB5500_GPADC_AUTO_TRIG_ADOUT1_CTRL 0x35 +#define AB5500_GPADC_AUTO_TRIG0_MUX_CTRL 0x37 +#define AB5500_GPADC_AUTO_XALTEMP_CTRL 0x57 +#define AB5500_GPADC_KELVIN_CTRL 0xFE + +/* gpadc constants */ +#define AB5500_INT_ADC_TRIG0 0x0 +#define AB5500_INT_ADC_TRIG1 0x1 +#define AB5500_INT_ADC_TRIG2 0x2 +#define AB5500_INT_ADC_TRIG3 0x3 +#define AB5500_INT_ADC_TRIG4 0x4 +#define AB5500_INT_ADC_TRIG5 0x5 +#define AB5500_INT_ADC_TRIG6 0x6 +#define AB5500_INT_ADC_TRIG7 0x7 + +#define AB5500_GPADC_AUTO_TRIG_INDEX AB5500_GPADC_AUTO_TRIG0_MUX_CTRL +#define GPADC_MANUAL_READY 0x01 +#define GPADC_MANUAL_ADOUT0_MASK 0x30 +#define GPADC_MANUAL_ADOUT1_MASK 0xC0 +#define GPADC_MANUAL_ADOUT0_ON 0x10 +#define GPADC_MANUAL_ADOUT1_ON 0x40 +#define MUX_SCALE_VBAT_MASK 0x02 +#define MUX_SCALE_45 0x02 +#define MUX_SCALE_BDATA_MASK 0x01 +#define MUX_SCALE_BDATA27 0x00 +#define MUX_SCALE_BDATA18 0x01 +#define MUX_SCALE_ACCDET2_MASK 0x01 +#define MUX_SCALE_ACCDET3_MASK 0x02 +#define ACCDET2_SCALE_VOL27 0x00 +#define ACCDET3_SCALE_VOL27 0x00 +#define TRIGX_FREQ_MASK 0x07 +#define AUTO_VBAT_MASK 0x10 +#define AUTO_VBAT_ON 0x10 +#define TRIG_VBAT_TXON_ARM_MASK 0x08 +#define TRIG_VBAT_NOTX_ARM_MASK 0x04 +#define TRIGX_ARM_MASK 0x20 +#define TRIGX_ARM 0x20 +#define TRIGX_MUX_SELECT 0x1F +#define ADC_CAL_OFF_MASK 0x04 +#define ADC_ON_MODE_MASK 0x03 +#define ADC_CAL_ON 0x00 +#define ADC_FULLPWR 0x03 +#define ADC_XTAL_FORCE_MASK 0x80 +#define ADC_XTAL_FORCE_EN 0x80 +#define ADC_XTAL_FORCE_DI 0x00 +#define ADOUT0 0x01 +#define ADOUT1 0x02 +#define MIN_INDEX 0x02 +#define MAX_INDEX 0x03 +#define CTRL_INDEX 0x01 + +/* GPADC constants from AB5500 spec */ +#define BTEMP_MIN 0 +#define BTEMP_MAX 1800 +#define BDATA_MIN 0 +#define BDATA_MAX 2750 +#define PCBTEMP_MIN 0 +#define PCBTEMP_MAX 1800 +#define XTALTEMP_MIN 0 +#define XTALTEMP_MAX 1800 +#define DIETEMP_MIN 0 +#define DIETEMP_MAX 1800 +#define VBUS_I_MIN 0 +#define VBUS_I_MAX 1600 +#define VBUS_V_MIN 0 +#define VBUS_V_MAX 20000 +#define ACCDET2_MIN 0 +#define ACCDET2_MAX 2500 +#define ACCDET3_MIN 0 +#define ACCDET3_MAX 2500 +#define VBAT_MIN 2300 +#define VBAT_MAX 4500 +#define BKBAT_MIN 0 +#define BKBAT_MAX 2750 +#define USBID_MIN 0 +#define USBID_MAX 1800 +#define KELVIN_MIN 0 +#define KELVIN_MAX 4500 + +/* This is used for calibration */ +#define ADC_RESOLUTION 1023 +#define AUTO_ADC_RESOLUTION 255 + +enum adc_auto_channels { + ADC_INPUT_TRIG0 = 0, + ADC_INPUT_TRIG1, + ADC_INPUT_TRIG2, + ADC_INPUT_TRIG3, + ADC_INPUT_TRIG4, + ADC_INPUT_TRIG5, + ADC_INPUT_TRIG6, + ADC_INPUT_TRIG7, + ADC_INPUT_VBAT_TXOFF, + ADC_INPUT_VBAT_TXON, + N_AUTO_TRIGGER +}; + +/** + * struct adc_auto_trigger - AB5500 GPADC auto trigger + * @adc_mux Mux input + * @flag Status of trigger + * @freq Frequency of conversion + * @adout Adout to pull + * @trig_min trigger minimum value + * @trig_max trigger maximum value + * @auto_adc_callback notification callback + */ +struct adc_auto_trigger { + u8 auto_mux; + u8 flag; + u8 freq; + u8 adout; + u8 trig_min; + u8 trig_max; + int (*auto_callb)(int mux); +}; + +/** + * struct ab5500_btemp_interrupts - ab5500 interrupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab5500_adc_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +/** + * struct ab5500_gpadc - AB5500 GPADC device information + * @chip_id ABB chip id + * @dev: pointer to the struct device + * @node: a list of AB5500 GPADCs, hence prepared for + reentrance + * @ab5500_gpadc_complete: pointer to the struct completion, to indicate + * the completion of gpadc conversion + * @ab5500_gpadc_lock: structure of type mutex + * @regu: pointer to the struct regulator + * @irq: interrupt number that is used by gpadc + * @cal_data array of ADC calibration data structs + * @auto_trig auto trigger channel + * @gpadc_trigX_work work items for trigger channels + */ +struct ab5500_gpadc { + u8 chip_id; + struct device *dev; + struct list_head node; + struct mutex ab5500_gpadc_lock; + struct regulator *regu; + int irq; + spinlock_t gpadc_auto_lock; + struct adc_auto_trigger adc_trig[N_AUTO_TRIGGER]; + struct workqueue_struct *gpadc_wq; + struct work_struct gpadc_trig0_work; + struct work_struct gpadc_trig1_work; + struct work_struct gpadc_trig2_work; + struct work_struct gpadc_trig3_work; + struct work_struct gpadc_trig4_work; + struct work_struct gpadc_trig5_work; + struct work_struct gpadc_trig6_work; + struct work_struct gpadc_trig7_work; + struct work_struct gpadc_trig_vbat_txon_work; + struct work_struct gpadc_trig_vbat_txoff_work; +}; + +static LIST_HEAD(ab5500_gpadc_list); + +struct adc_data { + u8 mux; + int min; + int max; + int adout; +}; + +#define ADC_DATA(_id, _mux, _min, _max, _adout) \ + [_id] = { \ + .mux = _mux, \ + .min = _min, \ + .max = _max, \ + .adout = _adout \ + } + +struct adc_data adc_tab[] = { + ADC_DATA(BTEMP_BALL, 0x0D, BTEMP_MIN, BTEMP_MAX, ADOUT0), + ADC_DATA(BAT_CTRL, 0x0D, BDATA_MIN, BDATA_MAX, 0), + ADC_DATA(MAIN_BAT_V, 0x0C, VBAT_MIN, VBAT_MAX, 0), + ADC_DATA(MAIN_BAT_V_TXON, 0x0C, VBAT_MIN, VBAT_MAX, 0), + ADC_DATA(VBUS_V, 0x10, VBUS_V_MIN, VBUS_V_MAX, 0), + ADC_DATA(USB_CHARGER_C, 0x0A, VBUS_I_MIN, VBUS_I_MAX, 0), + ADC_DATA(BK_BAT_V, 0x07, BKBAT_MIN, BKBAT_MAX, 0), + ADC_DATA(DIE_TEMP, 0x0F, DIETEMP_MIN, DIETEMP_MAX, ADOUT0), + ADC_DATA(PCB_TEMP, 0x13, PCBTEMP_MIN, PCBTEMP_MAX, ADOUT0), + ADC_DATA(XTAL_TEMP, 0x06, XTALTEMP_MIN, XTALTEMP_MAX, ADOUT0), + ADC_DATA(USB_ID, 0x1A, USBID_MIN, USBID_MAX, 0), + ADC_DATA(ACC_DETECT2, 0x18, ACCDET2_MIN, ACCDET2_MAX, 0), + ADC_DATA(ACC_DETECT3, 0x19, ACCDET3_MIN, ACCDET3_MAX, 0), + ADC_DATA(MAIN_BAT_V_TRIG_MIN, 0x0C, VBAT_MIN, VBAT_MAX, 0), + ADC_DATA(MAIN_BAT_V_TXON_TRIG_MIN, 0x0C, VBAT_MIN, VBAT_MAX, 0), +}; +/** + * ab5500_gpadc_get() - returns a reference to the primary AB5500 GPADC + * (i.e. the first GPADC in the instance list) + */ +struct ab5500_gpadc *ab5500_gpadc_get(const char *name) +{ + struct ab5500_gpadc *gpadc; + list_for_each_entry(gpadc, &ab5500_gpadc_list, node) { + if (!strcmp(name, dev_name(gpadc->dev))) + return gpadc; + } + + return ERR_PTR(-ENOENT); +} +EXPORT_SYMBOL(ab5500_gpadc_get); + +#define CONV(min, max, x)\ + ((min) + ((((max)-(min))*(x))/ADC_RESOLUTION)) + +static int ab5500_gpadc_ad_to_voltage(struct ab5500_gpadc *gpadc, + u8 in, u16 ad_val) +{ + int res; + + switch (in) { + case PCB_TEMP: + case BTEMP_BALL: + case MAIN_BAT_V: + case MAIN_BAT_V_TXON: + case ACC_DETECT2: + case ACC_DETECT3: + case VBUS_V: + case USB_CHARGER_C: + case BK_BAT_V: + case XTAL_TEMP: + case USB_ID: + case BAT_CTRL: + res = CONV(adc_tab[in].min, adc_tab[in].max, ad_val); + break; + case DIE_TEMP: + /* + * From the AB5500 product specification + * T(deg cel) = 27 - ((ADCode - 709)/2.4213) + * 27 + 709/2.4213 - ADCode/2.4123 + * (320 - ADCode)/2.4213 + */ + res = 320 - (((unsigned long)ad_val * 1000000) / 242130) / 10; + break; + default: + dev_err(gpadc->dev, + "unknown channel, not possible to convert\n"); + res = -EINVAL; + break; + } + return res; +} + +/** + * ab5500_gpadc_convert() - gpadc conversion + * @input: analog input to be converted to digital data + * + * This function converts the selected analog i/p to digital + * data. + */ +int ab5500_gpadc_convert(struct ab5500_gpadc *gpadc, u8 input) +{ + int result, ret = -EINVAL; + u16 data = 0; + u8 looplimit = 0; + u8 status = 0; + u8 low_data, high_data, adout_mask, adout_val; + + if (!gpadc) + return -ENODEV; + + mutex_lock(&gpadc->ab5500_gpadc_lock); + + switch (input) { + case MAIN_BAT_V: + case MAIN_BAT_V_TXON: + /* + * The value of mux scale volatage depends + * on the type of battery + * for LI-ion use MUX_SCALE_35 => 2.3-3.5V + * for LiFePo4 use MUX_SCALE_45 => 2.3-4.5V + * Check type of battery from platform data TODO ??? + */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_VBAT_MASK, MUX_SCALE_45); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to read status\n"); + goto out; + } + break; + case BTEMP_BALL: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_BDATA_MASK, MUX_SCALE_BDATA27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set mux scale\n"); + goto out; + } + break; + case BAT_CTRL: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_BDATA_MASK, MUX_SCALE_BDATA27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set mux scale\n"); + goto out; + } + break; + case XTAL_TEMP: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_XALTEMP_CTRL, + ADC_XTAL_FORCE_MASK, ADC_XTAL_FORCE_EN); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set xtaltemp\n"); + goto out; + } + break; + case ACC_DETECT2: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL2, + MUX_SCALE_ACCDET2_MASK, ACCDET2_SCALE_VOL27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set accdet2\n"); + goto out; + } + break; + case ACC_DETECT3: + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL2, + MUX_SCALE_ACCDET3_MASK, ACCDET3_SCALE_VOL27); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set accdet3\n"); + goto out; + } + break; + case USB_CHARGER_C: + case VBUS_V: + case BK_BAT_V: + case USB_ID: + case PCB_TEMP: + case DIE_TEMP: + break; + default: + dev_err(gpadc->dev, "gpadc: Wrong adc\n"); + goto out; + break; + } + if (adc_tab[input].adout) { + adout_mask = adc_tab[input].adout == ADOUT0 ? + GPADC_MANUAL_ADOUT0_MASK : GPADC_MANUAL_ADOUT1_MASK; + adout_val = adc_tab[input].adout == ADOUT0 ? + GPADC_MANUAL_ADOUT0_ON : GPADC_MANUAL_ADOUT1_ON; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + adout_mask, adout_val); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to set ADOUT\n"); + goto out; + } + } + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_MANUAL_MUX_CTRL, adc_tab[input].mux); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: fail to trigger manual conv\n"); + goto out; + } + /* wait for completion of conversion */ + looplimit = 0; + do { + msleep(1); + ret = abx500_get_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_STAT_REG, + &status); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to read status\n"); + goto out; + } + if (status & GPADC_MANUAL_READY) + break; + } while (++looplimit < 2); + if (looplimit >= 2) { + dev_err(gpadc->dev, "timeout:failed to complete conversion\n"); + ret = -EINVAL; + goto out; + } + + /* + * Disable ADOUT for measurement + */ + if (adc_tab[input].adout) { + adout_mask = adc_tab[input].adout == ADOUT0 ? + GPADC_MANUAL_ADOUT0_MASK : GPADC_MANUAL_ADOUT1_MASK; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + adout_mask, 0x0); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to disable ADOUT\n"); + goto out; + } + } + /* + * Disable XTAL TEMP + */ + if (input == XTAL_TEMP) { + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_XALTEMP_CTRL, + ADC_XTAL_FORCE_MASK, ADC_XTAL_FORCE_DI); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: fail to disable xtaltemp\n"); + goto out; + } + } + /* Read the converted RAW data */ + ret = abx500_get_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_MANDATAL_REG, &low_data); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: read low data failed\n"); + goto out; + } + + ret = abx500_get_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_MANDATAH_REG, &high_data); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: read high data failed\n"); + goto out; + } + + data = (high_data << 2) | (low_data >> 6); + result = ab5500_gpadc_ad_to_voltage(gpadc, input, data); + + mutex_unlock(&gpadc->ab5500_gpadc_lock); + return result; + +out: + mutex_unlock(&gpadc->ab5500_gpadc_lock); + dev_err(gpadc->dev, + "gpadc: Failed to AD convert channel %d\n", input); + return ret; +} +EXPORT_SYMBOL(ab5500_gpadc_convert); + +/** + * ab5500_gpadc_program_auto() - gpadc conversion auto conversion + * @trig_index: Generic trigger channel for conversion + * + * This function program the auto trigger channel + */ +static int ab5500_gpadc_program_auto(struct ab5500_gpadc *gpadc, int trig) +{ + int ret; + u8 adout; +#define MIN_INDEX 0x02 +#define MAX_INDEX 0x03 +#define CTRL_INDEX 0x01 + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + MIN_INDEX, + gpadc->adc_trig[trig].trig_min); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program min\n"); + return ret; + } + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + MAX_INDEX, + gpadc->adc_trig[trig].trig_max); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program max\n"); + return ret; + } + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2), + TRIGX_MUX_SELECT, gpadc->adc_trig[trig].auto_mux); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to select mux\n"); + return ret; + } + if (gpadc->adc_trig[trig].adout) { + adout = gpadc->adc_trig[trig].adout == ADOUT0 ? + gpadc->adc_trig[trig].adout << 6 : + gpadc->adc_trig[trig].adout << 5; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + CTRL_INDEX, + adout, adout); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program adout\n"); + return ret; + } + } + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig << 2) + CTRL_INDEX, + TRIGX_FREQ_MASK, gpadc->adc_trig[trig].freq); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program freq\n"); + return ret; + } + return ret; + +} + +#define TRIG_V(trigval, min, max) \ + ((((trigval) - (min)) * AUTO_ADC_RESOLUTION) / ((max) - (min))) + +static int ab5500_gpadc_vbat_auto_conf(struct ab5500_gpadc *gpadc, + struct adc_auto_input *in) +{ + int trig_min, ret; + u8 trig_reg, trig_arm; + + /* Scale mux voltage */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_VBAT_MASK, MUX_SCALE_45); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to set vbat scale\n"); + return ret; + } + + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_CTRL1, + AUTO_VBAT_MASK, AUTO_VBAT_ON); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to set vbat on\n"); + return ret; + } + + trig_min = TRIG_V(in->min, adc_tab[in->mux].min, adc_tab[in->mux].max); + + if (in->mux == MAIN_BAT_V_TRIG_MIN) { + trig_reg = AB5500_GPADC_AUTO_TRIG_VBAT_MIN_NOTX; + trig_arm = TRIG_VBAT_NOTX_ARM_MASK; + } else { + trig_reg = AB5500_GPADC_AUTO_TRIG_VBAT_MIN_TXON; + trig_arm = TRIG_VBAT_TXON_ARM_MASK; + } + ret = abx500_set_register_interruptible(gpadc->dev, AB5500_BANK_ADC, + trig_reg, trig_min); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to program vbat min\n"); + return ret; + } + /* + * arm the trigger + */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_CTRL1, trig_arm, trig_arm); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: failed to trig vbat\n"); + return ret; + } + return ret; +} +/** + * ab5500_gpadc_convert_auto() - gpadc conversion + * @auto_input: input trigger for conversion + * + * This function converts the selected channel from + * analog to digital data in auto mode + */ + +int ab5500_gpadc_convert_auto(struct ab5500_gpadc *gpadc, + struct adc_auto_input *in) +{ + int ret, trig; + unsigned long flags; + + if (!gpadc) + return -ENODEV; + mutex_lock(&gpadc->ab5500_gpadc_lock); + + if (in->mux == MAIN_BAT_V_TXON_TRIG_MIN) { + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + if (gpadc->adc_trig[ADC_INPUT_VBAT_TXON].flag == true) { + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + ret = -EBUSY; + dev_err(gpadc->dev, "gpadc: Auto vbat txon busy"); + goto out; + } + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + + ret = ab5500_gpadc_vbat_auto_conf(gpadc, in); + if (ret < 0) + goto out; + + gpadc->adc_trig[ADC_INPUT_VBAT_TXON].auto_mux = in->mux; + gpadc->adc_trig[ADC_INPUT_VBAT_TXON].auto_callb = + in->auto_adc_callback; + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + gpadc->adc_trig[ADC_INPUT_VBAT_TXON].flag = true; + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + } else if (in->mux == MAIN_BAT_V_TRIG_MIN) { + + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + if (gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].flag == true) { + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + ret = -EBUSY; + dev_err(gpadc->dev, "gpadc: Auto vbat busy"); + goto out; + } + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + + ret = ab5500_gpadc_vbat_auto_conf(gpadc, in); + if (ret < 0) + goto out; + + gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].auto_mux = in->mux; + gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].auto_callb = + in->auto_adc_callback; + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + gpadc->adc_trig[ADC_INPUT_VBAT_TXOFF].flag = true; + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + } else { + /* + * check if free trigger is available + */ + trig = ADC_INPUT_TRIG0; + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + while (gpadc->adc_trig[trig].flag == true && + trig <= ADC_INPUT_TRIG7) + trig++; + + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + if (trig > ADC_INPUT_TRIG7) { + ret = -EBUSY; + dev_err(gpadc->dev, "gpadc: no free channel\n"); + goto out; + } + switch (in->mux) { + case BTEMP_BALL: + case MAIN_BAT_V: + /* + * The value of mux scale volatage depends + * on the type of battery + * for LI-ion use MUX_SCALE_35 => 2.3-3.5V + * for LiFePo4 use MUX_SCALE_45 => 2.3-4.5V + * Check type of battery from platform data TODO ??? + */ + ret = abx500_mask_and_set_register_interruptible( + gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_MANUAL_MODE_CTRL, + MUX_SCALE_VBAT_MASK, MUX_SCALE_45); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: failed to read status\n"); + goto out; + } + case ACC_DETECT2: + case ACC_DETECT3: + case VBUS_V: + case USB_CHARGER_C: + case BK_BAT_V: + case PCB_TEMP: + case USB_ID: + case BAT_CTRL: + gpadc->adc_trig[trig].trig_min = + (u8)TRIG_V(in->min, adc_tab[in->mux].min, + adc_tab[in->mux].max); + gpadc->adc_trig[trig].trig_max = + (u8)TRIG_V(in->max, adc_tab[in->mux].min, + adc_tab[in->mux].max); + gpadc->adc_trig[trig].adout = + adc_tab[in->mux].adout; + break; + case DIE_TEMP: + /* + * From the AB5500 product specification + * T(deg_cel) = 27 -(ADCode - 709)/2.4123) + * adc min and max values are based on the above formula. + */ + gpadc->adc_trig[trig].trig_min = + 709 - (22413 * (in->min - 27))/10000; + gpadc->adc_trig[trig].trig_max = + 709 - (22413 * (in->max - 27))/10000; + gpadc->adc_trig[trig].adout = + adc_tab[in->mux].adout; + break; + default: + dev_err(gpadc->dev, "Unknow GPADC request\n"); + break; + } + gpadc->adc_trig[trig].freq = in->freq; + gpadc->adc_trig[trig].auto_mux = + adc_tab[in->mux].mux; + gpadc->adc_trig[trig].auto_callb = in->auto_adc_callback; + + ret = ab5500_gpadc_program_auto(gpadc, trig); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc: fail to program auto ch\n"); + goto out; + } + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, + AB5500_GPADC_AUTO_TRIG_INDEX + (trig * 4), + TRIGX_ARM_MASK, TRIGX_ARM); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: fail to trigger\n"); + goto out; + } + spin_lock_irqsave(&gpadc->gpadc_auto_lock, flags); + gpadc->adc_trig[trig].flag = true; + spin_unlock_irqrestore(&gpadc->gpadc_auto_lock, flags); + } +out: + mutex_unlock(&gpadc->ab5500_gpadc_lock); + return ret; + +} +EXPORT_SYMBOL(ab5500_gpadc_convert_auto); + +static void ab5500_gpadc_trigx_work(struct ab5500_gpadc *gp, int trig) +{ + unsigned long flags; + if (gp->adc_trig[trig].auto_callb != NULL) { + gp->adc_trig[trig].auto_callb(gp->adc_trig[trig].auto_mux); + spin_lock_irqsave(&gp->gpadc_auto_lock, flags); + gp->adc_trig[trig].flag = false; + spin_unlock_irqrestore(&gp->gpadc_auto_lock, flags); + } else { + dev_err(gp->dev, "Unknown trig for %d\n", trig); + } +} +/** + * ab5500_gpadc_trig0_work() - work item for trig0 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 0 auto conversion. + */ +static void ab5500_gpadc_trig0_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig0_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG0); +} + +/** + * ab5500_gpadc_trig1_work() - work item for trig1 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig1 auto conversion. + */ +static void ab5500_gpadc_trig1_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig1_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG1); +} + +/** + * ab5500_gpadc_trig2_work() - work item for trig2 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 2 auto conversion. + */ +static void ab5500_gpadc_trig2_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig2_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG2); +} + +/** + * ab5500_gpadc_trig3_work() - work item for trig3 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 3 auto conversion. + */ +static void ab5500_gpadc_trig3_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig3_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG3); +} + +/** + * ab5500_gpadc_trig4_work() - work item for trig4 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 4 auto conversion. + */ +static void ab5500_gpadc_trig4_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig4_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG4); +} + +/** + * ab5500_gpadc_trig5_work() - work item for trig5 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 5 auto conversion. + */ +static void ab5500_gpadc_trig5_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig5_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG5); +} + +/** + * ab5500_gpadc_trig6_work() - work item for trig6 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 6 auto conversion. + */ +static void ab5500_gpadc_trig6_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig6_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG6); +} + +/** + * ab5500_gpadc_trig7_work() - work item for trig7 auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for trig 7 auto conversion. + */ +static void ab5500_gpadc_trig7_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig7_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_TRIG7); +} + +/** + * ab5500_gpadc_vbat_txon_work() - work item for vbat_txon trigger auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for vbat_txon trigger auto adc. + */ +static void ab5500_gpadc_vbat_txon_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig_vbat_txon_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_VBAT_TXON); +} + +/** + * ab5500_gpadc_vbat_txoff_work() - work item for vbat_txoff trigger auto adc + * @irq: irq number + * @work: work pointer + * + * This is a work handler for vbat_txoff trigger auto adc. + */ +static void ab5500_gpadc_vbat_txoff_work(struct work_struct *work) +{ + struct ab5500_gpadc *gpadc = container_of(work, + struct ab5500_gpadc, gpadc_trig_vbat_txoff_work); + ab5500_gpadc_trigx_work(gpadc, ADC_INPUT_VBAT_TXOFF); +} + +/** + * ab5500_adc_trigx_handler() - isr for auto gpadc conversion trigger + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for auto gpadc conversion. + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_adc_trigx_handler(int irq, void *_gpadc) +{ + struct ab5500_platform_data *plat; + struct ab5500_gpadc *gpadc = _gpadc; + int dev_irq; + + plat = dev_get_platdata(gpadc->dev->parent); + dev_irq = irq - plat->irq.base; + + switch (dev_irq) { + case AB5500_INT_ADC_TRIG0: + dev_dbg(gpadc->dev, "Trigger 0 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig0_work); + break; + case AB5500_INT_ADC_TRIG1: + dev_dbg(gpadc->dev, "Trigger 1 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig1_work); + break; + case AB5500_INT_ADC_TRIG2: + dev_dbg(gpadc->dev, "Trigger 2 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig2_work); + break; + case AB5500_INT_ADC_TRIG3: + dev_dbg(gpadc->dev, "Trigger 3 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig3_work); + break; + case AB5500_INT_ADC_TRIG4: + dev_dbg(gpadc->dev, "Trigger 4 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig4_work); + break; + case AB5500_INT_ADC_TRIG5: + dev_dbg(gpadc->dev, "Trigger 5 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig5_work); + break; + case AB5500_INT_ADC_TRIG6: + dev_dbg(gpadc->dev, "Trigger 6 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig6_work); + break; + case AB5500_INT_ADC_TRIG7: + dev_dbg(gpadc->dev, "Trigger 7 received\n"); + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig7_work); + break; + default: + dev_dbg(gpadc->dev, "unknown trigx handler input\n"); + break; + } + return IRQ_HANDLED; +} + +/** + * ab5500_adc_vbat_txon_handler() - isr for auto vbat_txon conversion trigger + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for auto vbat_txon conversion + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_adc_vbat_txon_handler(int irq, void *_gpadc) +{ + struct ab5500_gpadc *gpadc = _gpadc; + + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig_vbat_txon_work); + return IRQ_HANDLED; +} + +/** + * ab5500_adc_vbat_txoff_handler() - isr for auto vbat_txoff conversion trigger + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This is a interrupt service routine for auto vbat_txoff conversion + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_adc_vbat_txoff_handler(int irq, void *_gpadc) +{ + struct ab5500_gpadc *gpadc = _gpadc; + + queue_work(gpadc->gpadc_wq, &gpadc->gpadc_trig_vbat_txoff_work); + return IRQ_HANDLED; +} + +/** + * ab5500_gpadc_configuration() - function for gpadc conversion + * @irq: irq number + * @data: pointer to the data passed during request irq + * + * This function configures the gpadc + */ +static int ab5500_gpadc_configuration(struct ab5500_gpadc *gpadc) +{ + int ret; + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_CTRL2, + ADC_CAL_OFF_MASK | ADC_ON_MODE_MASK, + ADC_CAL_ON | ADC_FULLPWR); + return ret; +} + +/* ab5500 btemp driver interrupts and their respective isr */ +static struct ab5500_adc_interrupts ab5500_adc_irq[] = { + {"TRIGGER-0", ab5500_adc_trigx_handler}, + {"TRIGGER-1", ab5500_adc_trigx_handler}, + {"TRIGGER-2", ab5500_adc_trigx_handler}, + {"TRIGGER-3", ab5500_adc_trigx_handler}, + {"TRIGGER-4", ab5500_adc_trigx_handler}, + {"TRIGGER-5", ab5500_adc_trigx_handler}, + {"TRIGGER-6", ab5500_adc_trigx_handler}, + {"TRIGGER-7", ab5500_adc_trigx_handler}, + {"TRIGGER-VBAT-TXON", ab5500_adc_vbat_txon_handler}, + {"TRIGGER-VBAT", ab5500_adc_vbat_txoff_handler}, +}; + +static int __devinit ab5500_gpadc_probe(struct platform_device *pdev) +{ + int ret, irq, i, j; + struct ab5500_gpadc *gpadc; + + gpadc = kzalloc(sizeof(struct ab5500_gpadc), GFP_KERNEL); + if (!gpadc) { + dev_err(&pdev->dev, "Error: No memory\n"); + return -ENOMEM; + } + gpadc->dev = &pdev->dev; + mutex_init(&gpadc->ab5500_gpadc_lock); + spin_lock_init(&gpadc->gpadc_auto_lock); + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_adc_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_adc_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab5500_adc_irq[i].isr, + IRQF_NO_SUSPEND, + ab5500_adc_irq[i].name, gpadc); + + if (ret) { + dev_err(gpadc->dev, "failed to request %s IRQ %d: %d\n" + , ab5500_adc_irq[i].name, irq, ret); + goto fail_irq; + } + dev_dbg(gpadc->dev, "Requested %s IRQ %d: %d\n", + ab5500_adc_irq[i].name, irq, ret); + } + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(gpadc->dev); + if (ret < 0) { + dev_err(gpadc->dev, "failed to get chip ID\n"); + goto fail_irq; + } + gpadc->chip_id = (u8) ret; + + /* Create a work queue for gpadc auto */ + gpadc->gpadc_wq = + create_singlethread_workqueue("ab5500_gpadc_wq"); + if (gpadc->gpadc_wq == NULL) { + dev_err(gpadc->dev, "failed to create work queue\n"); + goto fail_irq; + } + + INIT_WORK(&gpadc->gpadc_trig0_work, ab5500_gpadc_trig0_work); + INIT_WORK(&gpadc->gpadc_trig1_work, ab5500_gpadc_trig1_work); + INIT_WORK(&gpadc->gpadc_trig2_work, ab5500_gpadc_trig2_work); + INIT_WORK(&gpadc->gpadc_trig3_work, ab5500_gpadc_trig3_work); + INIT_WORK(&gpadc->gpadc_trig4_work, ab5500_gpadc_trig4_work); + INIT_WORK(&gpadc->gpadc_trig5_work, ab5500_gpadc_trig5_work); + INIT_WORK(&gpadc->gpadc_trig6_work, ab5500_gpadc_trig6_work); + INIT_WORK(&gpadc->gpadc_trig7_work, ab5500_gpadc_trig7_work); + INIT_WORK(&gpadc->gpadc_trig_vbat_txon_work, + ab5500_gpadc_vbat_txon_work); + INIT_WORK(&gpadc->gpadc_trig_vbat_txoff_work, + ab5500_gpadc_vbat_txoff_work); + + for (j = 0; j < N_AUTO_TRIGGER; j++) + gpadc->adc_trig[j].flag = false; + + ret = ab5500_gpadc_configuration(gpadc); + if (ret < 0) { + dev_err(gpadc->dev, "gpadc: configuration failed\n"); + goto free_wq; + } + list_add_tail(&gpadc->node, &ab5500_gpadc_list); + + return 0; +free_wq: + destroy_workqueue(gpadc->gpadc_wq); +fail_irq: + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab5500_adc_irq[i].name); + free_irq(irq, gpadc); + } + kfree(gpadc); + gpadc = NULL; + return ret; +} + +static int __devexit ab5500_gpadc_remove(struct platform_device *pdev) +{ + int i, irq; + struct ab5500_gpadc *gpadc = platform_get_drvdata(pdev); + + /* remove this gpadc entry from the list */ + list_del(&gpadc->node); + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_adc_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_adc_irq[i].name); + free_irq(irq, gpadc); + } + /* Flush work */ + flush_workqueue(gpadc->gpadc_wq); + + /* Delete the work queue */ + destroy_workqueue(gpadc->gpadc_wq); + + kfree(gpadc); + gpadc = NULL; + return 0; +} + +static struct platform_driver ab5500_gpadc_driver = { + .probe = ab5500_gpadc_probe, + .remove = __devexit_p(ab5500_gpadc_remove), + .driver = { + .name = "ab5500-adc", + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_gpadc_init(void) +{ + return platform_driver_register(&ab5500_gpadc_driver); +} + +static void __exit ab5500_gpadc_exit(void) +{ + platform_driver_unregister(&ab5500_gpadc_driver); +} + +subsys_initcall_sync(ab5500_gpadc_init); +module_exit(ab5500_gpadc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Vijaya Kumar K"); +MODULE_ALIAS("platform:ab5500_adc"); +MODULE_DESCRIPTION("AB5500 GPADC driver"); diff --git a/include/linux/mfd/abx500/ab5500-gpadc.h b/include/linux/mfd/abx500/ab5500-gpadc.h new file mode 100644 index 00000000000..3282b44114e --- /dev/null +++ b/include/linux/mfd/abx500/ab5500-gpadc.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 ST-Ericsson SA + * Licensed under GPLv2. + * + * Author: Vijaya Kumar K + */ + +#ifndef _AB5500_GPADC_H +#define _AB5500_GPADC_H + +/* + * GPADC source: + * The BTEMP_BALL and PCB_TEMP are same. They differ if the + * battery supports internal NTC resistor connected to BDATA + * line. In this case, the BTEMP_BALL correspondss to BDATA + * of GPADC as per AB5500 product spec. + */ + +#define BTEMP_BALL 0 +#define ACC_DETECT2 1 +#define ACC_DETECT3 2 +#define MAIN_BAT_V 3 +#define MAIN_BAT_V_TXON 4 +#define VBUS_V 5 +#define USB_CHARGER_C 6 +#define BK_BAT_V 7 +#define DIE_TEMP 8 +#define PCB_TEMP 9 +#define XTAL_TEMP 10 +#define USB_ID 11 +#define BAT_CTRL 12 +/* VBAT with TXON only min trigger */ +#define MAIN_BAT_V_TXON_TRIG_MIN 13 +/* VBAT with TX off only min trigger */ +#define MAIN_BAT_V_TRIG_MIN 14 + +/* + * Frequency of auto adc conversion + */ +#define MS1000 0x0 +#define MS500 0x1 +#define MS200 0x2 +#define MS100 0x3 +#define MS10 0x4 + +struct ab5500_gpadc; + +/* + * struct adc_auto_input - AB5500 GPADC auto trigger + * @adc_mux Mux input + * @freq freq of conversion + * @min min value for trigger + * @max max value for trigger + * @auto_adc_callback notification callback + */ +struct adc_auto_input { + u8 mux; + u8 freq; + int min; + int max; + int (*auto_adc_callback)(int mux); +}; + +struct ab5500_gpadc *ab5500_gpadc_get(const char *name); +int ab5500_gpadc_convert(struct ab5500_gpadc *gpadc, u8 input); +int ab5500_gpadc_convert_auto(struct ab5500_gpadc *gpadc, + struct adc_auto_input *auto_input); + +#endif /* _AB5500_GPADC_H */ -- cgit v1.2.3 From e454717649d30011dd9946f7d1e3c96d54391063 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Fri, 14 Oct 2011 11:49:26 +0200 Subject: arm: ux500: common dbx500 prcmu driver api This patch updates the PRCMU driver API to be the same (as far as possible) in U8500 and U4500. - has been renamed . - The platform specific APIs have moved to and (but these should not be directly included). - The PRCMU QoS API has been put in . ST Ericsson ID: 334772 ST Ericsson FOSS-OUT ID: trivial ST Ericsson Linux next: 318371 Change-Id: I6ce117ec35ebf2e987178ccacce09afb554d2736 Signed-off-by: Mattias Nilsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23863 Reviewed-by: Jonas ABERG --- drivers/hwmon/db8500.c | 2 +- drivers/mfd/ab8500-i2c.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/db8500.c b/drivers/hwmon/db8500.c index b9b4312b8c1..c08ab033ada 100755 --- a/drivers/hwmon/db8500.c +++ b/drivers/hwmon/db8500.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/drivers/mfd/ab8500-i2c.c b/drivers/mfd/ab8500-i2c.c index 9be541c6b00..9c64a9ebbbc 100644 --- a/drivers/mfd/ab8500-i2c.c +++ b/drivers/mfd/ab8500-i2c.c @@ -13,6 +13,7 @@ #include #include + static int ab8500_i2c_write(struct ab8500 *ab8500, u16 addr, u8 data) { int ret; -- cgit v1.2.3 From 016abeaac1647c2f3cf901a7f21c9f6438891e48 Mon Sep 17 00:00:00 2001 From: Huan DUAN Date: Thu, 26 May 2011 17:08:21 +0200 Subject: hwmon: ab8500: Add BatCtrl thermal sensor Support for monitoring BatCtrl in AB8500 has been added ST-Ericsson Linux next: ER282986 ST-Ericsson ID: CR339643 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I71cfa8b8ecaeae9291d910bc1928ad0e36834709 Signed-off-by: Huan DUAN Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23986 Reviewed-by: QATEST Reviewed-by: Johan PALSSON Reviewed-by: Jonas ABERG Reviewed-by: Martin PERSSON --- drivers/hwmon/ab8500.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c index 640a6638db4..c8c05c6fa15 100644 --- a/drivers/hwmon/ab8500.c +++ b/drivers/hwmon/ab8500.c @@ -47,7 +47,7 @@ */ #define DEFAULT_POWER_OFF_DELAY 10000 -#define NUM_SENSORS 4 +#define NUM_SENSORS 5 /* The driver monitors GPADC - ADC_AUX1 and ADC_AUX2 */ #define NUM_MONITORED_SENSORS 2 @@ -316,11 +316,14 @@ static ssize_t show_label(struct device *dev, name = "ext_db8500"; break; case 3: - name = "battery"; + name = "bat_temp"; break; case 4: name = "ab8500"; break; + case 5: + name = "bat_ctrl"; + break; default: return -EINVAL; } @@ -534,6 +537,10 @@ static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 4); static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, show_crit_alarm, NULL, 4); +/* GPADC - BAT_CTRL */ +static SENSOR_DEVICE_ATTR(temp5_label, S_IRUGO, show_label, NULL, 5); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_input, NULL, 5); + static struct attribute *ab8500_temp_attributes[] = { &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, &sensor_dev_attr_temp_monitor_delay.dev_attr.attr, @@ -562,6 +569,9 @@ static struct attribute *ab8500_temp_attributes[] = { /* AB8500 */ &sensor_dev_attr_temp4_label.dev_attr.attr, &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + /* GPADC - BAT_CTRL */ + &sensor_dev_attr_temp5_label.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, NULL }; @@ -645,7 +655,8 @@ static int __devinit ab8500_temp_probe(struct platform_device *pdev) * AB8500 IRQ will be launched if die crit temp limit is reached. * * Also: - * Battery temperature thresholds will not be exposed via hwmon. + * Battery temperature (BatTemp and BatCtrl) thresholds will + * not be exposed via hwmon. * * Make sure indexes correspond to the attribute indexes * used when calling SENSOR_DEVICE_ATRR @@ -653,7 +664,7 @@ static int __devinit ab8500_temp_probe(struct platform_device *pdev) data->gpadc_addr[0] = ADC_AUX1; data->gpadc_addr[1] = ADC_AUX2; data->gpadc_addr[2] = BTEMP_BALL; - + data->gpadc_addr[4] = BAT_CTRL; mutex_init(&data->lock); data->pdev = pdev; data->power_off_delay = DEFAULT_POWER_OFF_DELAY; -- cgit v1.2.3 From b3590998dbc1b0e289e37f51cf9643a359181cc4 Mon Sep 17 00:00:00 2001 From: John Beckett Date: Tue, 31 May 2011 13:54:27 +0100 Subject: AB8500: Export all AB8500 ADCs as debugfs nodes ST-Ericsson ID: 326039 ST-Ericsson FOSS-OUT ID: Trivial ST-Ericsson Linux next: NA Change-Id: I30ca4ef74046397d4b498b7cf5673a153a2f62e1 Signed-off-by: John Beckett Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23456 Reviewed-by: Mattias WALLIN --- drivers/mfd/ab8500-debugfs.c | 485 +++++++++++++++++++++++++++++++++++++-- drivers/mfd/ab8500-gpadc.c | 23 +- include/linux/mfd/ab8500/gpadc.h | 3 + 3 files changed, 489 insertions(+), 22 deletions(-) diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index f8a5684205b..30854257586 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -82,6 +82,7 @@ #include #include +#include #ifdef CONFIG_DEBUG_FS #include @@ -142,6 +143,7 @@ static struct hwreg_cfg hwreg_cfg = { }; #define AB8500_NAME_STRING "ab8500" +#define AB8500_ADC_NAME_STRING "gpadc" #define AB8500_NUM_BANKS 22 #define AB8500_REV_REG 0x80 @@ -670,6 +672,382 @@ static int ab8500_hwreg_open(struct inode *inode, struct file *file) return single_open(file, ab8500_hwreg_print, inode->i_private); } +static int ab8500_gpadc_bat_ctrl_print(struct seq_file *s, void *p) +{ + int bat_ctrl_raw; + int bat_ctrl_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + bat_ctrl_raw = ab8500_gpadc_read_raw(gpadc, BAT_CTRL); + bat_ctrl_convert = ab8500_gpadc_ad_to_voltage(gpadc, + BAT_CTRL, bat_ctrl_raw); + + return seq_printf(s, "%d,0x%X\n", + bat_ctrl_convert, bat_ctrl_raw); +} + +static int ab8500_gpadc_bat_ctrl_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_bat_ctrl_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_bat_ctrl_fops = { + .open = ab8500_gpadc_bat_ctrl_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_btemp_ball_print(struct seq_file *s, void *p) +{ + int btemp_ball_raw; + int btemp_ball_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + btemp_ball_raw = ab8500_gpadc_read_raw(gpadc, BTEMP_BALL); + btemp_ball_convert = ab8500_gpadc_ad_to_voltage(gpadc, BTEMP_BALL, + btemp_ball_raw); + + return seq_printf(s, + "%d,0x%X\n", btemp_ball_convert, btemp_ball_raw); +} + +static int ab8500_gpadc_btemp_ball_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_btemp_ball_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_btemp_ball_fops = { + .open = ab8500_gpadc_btemp_ball_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_charger_v_print(struct seq_file *s, void *p) +{ + int main_charger_v_raw; + int main_charger_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + main_charger_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_V); + main_charger_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, + MAIN_CHARGER_V, main_charger_v_raw); + + return seq_printf(s, "%d,0x%X\n", + main_charger_v_convert, main_charger_v_raw); +} + +static int ab8500_gpadc_main_charger_v_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_charger_v_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_charger_v_fops = { + .open = ab8500_gpadc_main_charger_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_acc_detect1_print(struct seq_file *s, void *p) +{ + int acc_detect1_raw; + int acc_detect1_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + acc_detect1_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT1); + acc_detect1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ACC_DETECT1, + acc_detect1_raw); + + return seq_printf(s, "%d,0x%X\n", + acc_detect1_convert, acc_detect1_raw); +} + +static int ab8500_gpadc_acc_detect1_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_acc_detect1_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_acc_detect1_fops = { + .open = ab8500_gpadc_acc_detect1_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_acc_detect2_print(struct seq_file *s, void *p) +{ + int acc_detect2_raw; + int acc_detect2_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + acc_detect2_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT2); + acc_detect2_convert = ab8500_gpadc_ad_to_voltage(gpadc, + ACC_DETECT2, acc_detect2_raw); + + return seq_printf(s, "%d,0x%X\n", + acc_detect2_convert, acc_detect2_raw); +} + +static int ab8500_gpadc_acc_detect2_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_acc_detect2_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_acc_detect2_fops = { + .open = ab8500_gpadc_acc_detect2_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_aux1_print(struct seq_file *s, void *p) +{ + int aux1_raw; + int aux1_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + aux1_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX1); + aux1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX1, + aux1_raw); + + return seq_printf(s, "%d,0x%X\n", + aux1_convert, aux1_raw); +} + +static int ab8500_gpadc_aux1_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_aux1_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_aux1_fops = { + .open = ab8500_gpadc_aux1_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_aux2_print(struct seq_file *s, void *p) +{ + int aux2_raw; + int aux2_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + aux2_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX2); + aux2_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX2, + aux2_raw); + + return seq_printf(s, "%d,0x%X\n", + aux2_convert, aux2_raw); +} + +static int ab8500_gpadc_aux2_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_aux2_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_aux2_fops = { + .open = ab8500_gpadc_aux2_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_bat_v_print(struct seq_file *s, void *p) +{ + int main_bat_v_raw; + int main_bat_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + main_bat_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_BAT_V); + main_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, MAIN_BAT_V, + main_bat_v_raw); + + return seq_printf(s, "%d,0x%X\n", + main_bat_v_convert, main_bat_v_raw); +} + +static int ab8500_gpadc_main_bat_v_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_bat_v_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_bat_v_fops = { + .open = ab8500_gpadc_main_bat_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_vbus_v_print(struct seq_file *s, void *p) +{ + int vbus_v_raw; + int vbus_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + vbus_v_raw = ab8500_gpadc_read_raw(gpadc, VBUS_V); + vbus_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, VBUS_V, + vbus_v_raw); + + return seq_printf(s, "%d,0x%X\n", + vbus_v_convert, vbus_v_raw); +} + +static int ab8500_gpadc_vbus_v_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_vbus_v_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_vbus_v_fops = { + .open = ab8500_gpadc_vbus_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_main_charger_c_print(struct seq_file *s, void *p) +{ + int main_charger_c_raw; + int main_charger_c_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + main_charger_c_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_C); + main_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, + MAIN_CHARGER_C, main_charger_c_raw); + + return seq_printf(s, "%d,0x%X\n", + main_charger_c_convert, main_charger_c_raw); +} + +static int ab8500_gpadc_main_charger_c_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_main_charger_c_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_main_charger_c_fops = { + .open = ab8500_gpadc_main_charger_c_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_usb_charger_c_print(struct seq_file *s, void *p) +{ + int usb_charger_c_raw; + int usb_charger_c_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + usb_charger_c_raw = ab8500_gpadc_read_raw(gpadc, USB_CHARGER_C); + usb_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, + USB_CHARGER_C, usb_charger_c_raw); + + return seq_printf(s, "%d,0x%X\n", + usb_charger_c_convert, usb_charger_c_raw); +} + +static int ab8500_gpadc_usb_charger_c_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8500_gpadc_usb_charger_c_print, + inode->i_private); +} + +static const struct file_operations ab8500_gpadc_usb_charger_c_fops = { + .open = ab8500_gpadc_usb_charger_c_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_bk_bat_v_print(struct seq_file *s, void *p) +{ + int bk_bat_v_raw; + int bk_bat_v_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + bk_bat_v_raw = ab8500_gpadc_read_raw(gpadc, BK_BAT_V); + bk_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, + BK_BAT_V, bk_bat_v_raw); + + return seq_printf(s, "%d,0x%X\n", + bk_bat_v_convert, bk_bat_v_raw); +} + +static int ab8500_gpadc_bk_bat_v_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_bk_bat_v_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_bk_bat_v_fops = { + .open = ab8500_gpadc_bk_bat_v_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_die_temp_print(struct seq_file *s, void *p) +{ + int die_temp_raw; + int die_temp_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get(); + die_temp_raw = ab8500_gpadc_read_raw(gpadc, DIE_TEMP); + die_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, DIE_TEMP, + die_temp_raw); + + return seq_printf(s, "%d,0x%X\n", + die_temp_convert, die_temp_raw); +} + +static int ab8500_gpadc_die_temp_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_die_temp_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_die_temp_fops = { + .open = ab8500_gpadc_die_temp_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + /* * return length of an ASCII numerical value, 0 is string is not a * numerical value. @@ -1037,6 +1415,7 @@ static const struct file_operations ab8500_hwreg_fops = { }; static struct dentry *ab8500_dir; +static struct dentry *ab8500_gpadc_dir; static int __devinit ab8500_debug_probe(struct platform_device *plf) { @@ -1047,14 +1426,14 @@ static int __devinit ab8500_debug_probe(struct platform_device *plf) irq_first = platform_get_irq_byname(plf, "IRQ_FIRST"); if (irq_first < 0) { dev_err(&plf->dev, "First irq not found, err %d\n", - irq_first); + irq_first); return irq_first; } irq_last = platform_get_irq_byname(plf, "IRQ_LAST"); if (irq_last < 0) { dev_err(&plf->dev, "Last irq not found, err %d\n", - irq_last); + irq_last); return irq_last; } @@ -1062,42 +1441,108 @@ static int __devinit ab8500_debug_probe(struct platform_device *plf) if (!ab8500_dir) goto err; - file = debugfs_create_file("all-bank-registers", - S_IRUGO, ab8500_dir, &plf->dev, &ab8500_registers_fops); + ab8500_gpadc_dir = debugfs_create_dir(AB8500_ADC_NAME_STRING, + ab8500_dir); + if (!ab8500_gpadc_dir) + goto err; + + file = debugfs_create_file("all-bank-registers", S_IRUGO, + ab8500_dir, &plf->dev, &ab8500_registers_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-bank", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_bank_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-address", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_address_fops); + if (!file) + goto err; + + file = debugfs_create_file("register-value", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_val_fops); + if (!file) + goto err; + + file = debugfs_create_file("irq-subscribe", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_subscribe_fops); + if (!file) + goto err; + + file = debugfs_create_file("irq-unsubscribe", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_unsubscribe_fops); + if (!file) + goto err; + + file = debugfs_create_file("hwreg", (S_IRUGO | S_IWUGO), + ab8500_dir, &plf->dev, &ab8500_hwreg_fops); + if (!file) + goto err; + + file = debugfs_create_file("bat_ctrl", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bat_ctrl_fops); + if (!file) + goto err; + + file = debugfs_create_file("btemp_ball", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_btemp_ball_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_charger_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("acc_detect1", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect1_fops); + if (!file) + goto err; + + file = debugfs_create_file("acc_detect2", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect2_fops); + if (!file) + goto err; + + file = debugfs_create_file("adc_aux1", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux1_fops); + if (!file) + goto err; + + file = debugfs_create_file("adc_aux2", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux2_fops); if (!file) goto err; - file = debugfs_create_file("register-bank", - (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_bank_fops); + file = debugfs_create_file("main_bat_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_bat_v_fops); if (!file) goto err; - file = debugfs_create_file("register-address", - (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, - &ab8500_address_fops); + file = debugfs_create_file("vbus_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_vbus_v_fops); if (!file) goto err; - file = debugfs_create_file("register-value", - (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, &ab8500_val_fops); + file = debugfs_create_file("main_charger_c", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_c_fops); if (!file) goto err; - file = debugfs_create_file("irq-subscribe", - (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, - &ab8500_subscribe_fops); + file = debugfs_create_file("usb_charger_c", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_usb_charger_c_fops); if (!file) goto err; - file = debugfs_create_file("irq-unsubscribe", - (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, - &ab8500_unsubscribe_fops); + file = debugfs_create_file("bk_bat_v", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bk_bat_v_fops); if (!file) goto err; - file = debugfs_create_file("hwreg", - (S_IRUGO | S_IWUGO), ab8500_dir, &plf->dev, - &ab8500_hwreg_fops); + file = debugfs_create_file("die_temp", (S_IRUGO | S_IWUGO), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_die_temp_fops); if (!file) goto err; diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index 3da7d2e0808..ec9cc83d1eb 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -139,7 +139,10 @@ struct ab8500_gpadc *ab8500_gpadc_get(void) } EXPORT_SYMBOL(ab8500_gpadc_get); -static int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 input, +/** + * ab8500_gpadc_ad_to_voltage() - Convert a raw ADC value to a voltage + */ +int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 input, int ad_value) { int res; @@ -237,6 +240,23 @@ static int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 input, * data. */ int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) +{ + int data; + int ret; + + data = ab8500_gpadc_read_raw(gpadc, input); + ret = ab8500_gpadc_ad_to_voltage(gpadc, input, data); + return ret; +} + +/** + * ab8500_gpadc_read_raw() - gpadc read + * @input: analog input to be read + * + * This function obtains the raw ADC value, this then needs + * to be converted by calling ab8500_gpadc_ad_to_voltage() + */ +int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 input) { int ret; u16 data = 0; @@ -384,7 +404,6 @@ out: "gpadc_conversion: Failed to AD convert channel %d\n", input); return ret; } -EXPORT_SYMBOL(ab8500_gpadc_convert); /** * ab8500_bm_gpswadcconvend_handler() - isr for s/w gpadc conversion completion diff --git a/include/linux/mfd/ab8500/gpadc.h b/include/linux/mfd/ab8500/gpadc.h index 57c6b59fcaa..6688cbc8208 100644 --- a/include/linux/mfd/ab8500/gpadc.h +++ b/include/linux/mfd/ab8500/gpadc.h @@ -28,5 +28,8 @@ struct ab8500_gpadc; struct ab8500_gpadc *ab8500_gpadc_get(void); int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input); +int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 input); +int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, + u8 input, int ad_value); #endif /* _AB8500_GPADC_H */ -- cgit v1.2.3 From 5b08a7b5e413b9051f9fd30a20b408bb71ed691b Mon Sep 17 00:00:00 2001 From: Kalle Komierowski Date: Tue, 7 Jun 2011 15:43:55 +0200 Subject: mfd: ab8500_gpadc: Raw ADC value converted twice A bug made the raw ADC value converted to voltage entity twice. Renamed some gpadc function arguments to clarify use. ST-Ericsson ID: 344073 ST-Ericsson Linux next: Tested by ELINWAL ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I53465c2e65c001c23d13929b4bdb59700eb7cf18 Signed-off-by: Kalle Komierowski Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24552 Tested-by: Karl KOMIEROWSKI Reviewed-by: Mattias WALLIN Reviewed-by: John BECKETT Reviewed-by: QATEST --- drivers/mfd/ab8500-gpadc.c | 46 +++++++++++++++++++++++----------------- include/linux/mfd/ab8500/gpadc.h | 6 +++--- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index ec9cc83d1eb..4eeac415d84 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -142,12 +142,12 @@ EXPORT_SYMBOL(ab8500_gpadc_get); /** * ab8500_gpadc_ad_to_voltage() - Convert a raw ADC value to a voltage */ -int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 input, +int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel, int ad_value) { int res; - switch (input) { + switch (channel) { case MAIN_CHARGER_V: /* For some reason we don't have calibrated data */ if (!gpadc->cal_data[ADC_INPUT_VMAIN].gain) { @@ -234,32 +234,41 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 input, /** * ab8500_gpadc_convert() - gpadc conversion - * @input: analog input to be converted to digital data + * @channel: analog channel to be converted to digital data * * This function converts the selected analog i/p to digital * data. */ -int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input) +int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 channel) { - int data; - int ret; + int ad_value; + int voltage; - data = ab8500_gpadc_read_raw(gpadc, input); - ret = ab8500_gpadc_ad_to_voltage(gpadc, input, data); - return ret; + ad_value = ab8500_gpadc_read_raw(gpadc, channel); + if (ad_value < 0) { + dev_err(gpadc->dev, "GPADC raw value failed ch: %d\n", channel); + return ad_value; + } + + voltage = ab8500_gpadc_ad_to_voltage(gpadc, channel, ad_value); + + if (voltage < 0) + dev_err(gpadc->dev, "GPADC to voltage conversion failed ch:" + " %d AD: 0x%x\n", channel, ad_value); + + return voltage; } /** * ab8500_gpadc_read_raw() - gpadc read - * @input: analog input to be read + * @channel: analog channel to be read * * This function obtains the raw ADC value, this then needs * to be converted by calling ab8500_gpadc_ad_to_voltage() */ -int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 input) +int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) { int ret; - u16 data = 0; int looplimit = 0; u8 val, low_data, high_data; @@ -294,9 +303,9 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 input) goto out; } - /* Select the input source and set average samples to 16 */ + /* Select the channel source and set average samples to 16 */ ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, - AB8500_GPADC_CTRL2_REG, (input | SW_AVG_16)); + AB8500_GPADC_CTRL2_REG, (channel | SW_AVG_16)); if (ret < 0) { dev_err(gpadc->dev, "gpadc_conversion: set avg samples failed\n"); @@ -308,7 +317,7 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 input) * charging current sense if it needed, ABB 3.0 needs some special * treatment too. */ - switch (input) { + switch (channel) { case MAIN_CHARGER_C: case USB_CHARGER_C: ret = abx500_mask_and_set_register_interruptible(gpadc->dev, @@ -375,7 +384,6 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 input) goto out; } - data = (high_data << 8) | low_data; /* Disable GPADC */ ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL1_REG, DIS_GPADC); @@ -386,8 +394,8 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 input) /* Disable VTVout LDO this is required for GPADC */ regulator_disable(gpadc->regu); mutex_unlock(&gpadc->ab8500_gpadc_lock); - ret = ab8500_gpadc_ad_to_voltage(gpadc, input, data); - return ret; + + return (high_data << 8) | low_data; out: /* @@ -401,7 +409,7 @@ out: regulator_disable(gpadc->regu); mutex_unlock(&gpadc->ab8500_gpadc_lock); dev_err(gpadc->dev, - "gpadc_conversion: Failed to AD convert channel %d\n", input); + "gpadc_conversion: Failed to AD convert channel %d\n", channel); return ret; } diff --git a/include/linux/mfd/ab8500/gpadc.h b/include/linux/mfd/ab8500/gpadc.h index 6688cbc8208..fa706c5a04a 100644 --- a/include/linux/mfd/ab8500/gpadc.h +++ b/include/linux/mfd/ab8500/gpadc.h @@ -27,9 +27,9 @@ struct ab8500_gpadc; struct ab8500_gpadc *ab8500_gpadc_get(void); -int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 input); -int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 input); +int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 channel); +int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel); int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, - u8 input, int ad_value); + u8 channel, int ad_value); #endif /* _AB8500_GPADC_H */ -- cgit v1.2.3 From 19759beebd265d9f0b065d1d4dd3171cc3747eb9 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Wed, 22 Jun 2011 20:41:55 +0200 Subject: config: Add mmio driver to the u8500 config Signed-off-by: Robert Marklund --- arch/arm/configs/u8500_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 29ce46b9b02..e6e3c77c047 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -231,7 +231,7 @@ CONFIG_RTC_DRV_PL031=y CONFIG_DMADEVICES=y CONFIG_STE_DMA40=y CONFIG_STAGING=y -# CONFIG_STAGING_EXCLUDE_BUILD is not set +CONFIG_U8500_MMIO=y CONFIG_AUTOFS_FS=m CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y CONFIG_CG2900=y -- cgit v1.2.3 From ced784cf18fe5f860450d54e0565298d4696814b Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Wed, 22 Jun 2011 21:10:11 +0200 Subject: config: Add CM driver to u8500 Signed-off-by: Robert Marklund --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index e6e3c77c047..5b9101e57b8 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -232,6 +232,7 @@ CONFIG_DMADEVICES=y CONFIG_STE_DMA40=y CONFIG_STAGING=y CONFIG_U8500_MMIO=y +CONFIG_U8500_CM=y CONFIG_AUTOFS_FS=m CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y CONFIG_CG2900=y -- cgit v1.2.3 From 4464b83da5e349619a06cc784337e6d1a655a810 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 15 Jun 2011 15:57:28 +0200 Subject: config: Align u8500_defconfig and withdraw u5500 compilation FIXME: U5500 doesn't compile due to function redefinitions in PRCMU Signed-off-by: Philippe Langlais --- arch/arm/configs/u8500_defconfig | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 5b9101e57b8..80357f2a54e 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -20,21 +20,18 @@ CONFIG_MODULE_SRCVERSION_ALL=y # CONFIG_BLK_DEV_BSG is not set CONFIG_DEFAULT_DEADLINE=y CONFIG_ARCH_U8500=y -CONFIG_UX500_SOC_DB5500=y CONFIG_UX500_SOC_DB8500=y -CONFIG_SND_SOC_UX500_AB5500=y -CONFIG_SND_SOC_UX500_AB8500=y -CONFIG_SND_SOC_UX500_AV8100=y -CONFIG_SND_SOC_UX500_CG29XX=y CONFIG_MACH_HREFV60=y CONFIG_MACH_SNOWBALL=y CONFIG_MACH_U8500_SNOWBALL=y CONFIG_MACH_U5500=y +CONFIG_UX500_PRCMU_TIMER=y CONFIG_DB8500_MLOADER=y CONFIG_UX500_DEBUG_HWREG=y CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y +CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_STANDBY=y CONFIG_UX500_SUSPEND_MEM=y @@ -86,7 +83,6 @@ CONFIG_IP_NF_TARGET_MASQUERADE=y CONFIG_PHONET=y CONFIG_PHONET_PIPECTRLR=y CONFIG_NET_SCHED=y -CONFIG_BT=y CONFIG_BT_L2CAP=y CONFIG_BT_SCO=y CONFIG_BT_RFCOMM=y @@ -95,8 +91,6 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y -CONFIG_CFG80211_WEXT=y -CONFIG_BT_HCIUART=y CONFIG_MAC80211_LEDS=y CONFIG_RFKILL=y CONFIG_RFKILL_INPUT=y @@ -125,6 +119,10 @@ CONFIG_U8500_SHRM_MODEM_SILENT_RESET=y # CONFIG_STE_AUDIO_IO_DEV is not set CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y +CONFIG_MD=y +CONFIG_BLK_DEV_DM=y +CONFIG_DM_CRYPT=y +CONFIG_DM_UEVENT=y CONFIG_NETDEVICES=y CONFIG_TUN=y CONFIG_SMSC_PHY=y @@ -133,7 +131,6 @@ CONFIG_SMSC911X=y # CONFIG_INPUT_MOUSEDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set -CONFIG_KEYBOARD_DB5500=y CONFIG_KEYBOARD_GPIO=y CONFIG_KEYBOARD_NOMADIK_SKE=y CONFIG_KEYBOARD_STMPE=y @@ -177,7 +174,6 @@ CONFIG_AB8500_CORE=y CONFIG_REGULATOR_DEBUG=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y -CONFIG_REGULATOR_FIXED_VOLTAGE=y CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y # CONFIG_MEDIA_TUNER_CUSTOMISE is not set @@ -186,14 +182,16 @@ CONFIG_FB=y CONFIG_FB_MCDE=y CONFIG_FB_B2R2=y CONFIG_B2R2_PLUG_CONF=y -# CONFIG_AV8100_HWTRIG_DSI_TE is not set CONFIG_AV8100_HWTRIG_I2SDAT3=y CONFIG_SOUND=y CONFIG_SND=y CONFIG_SND_USB_AUDIO=y CONFIG_SND_SOC=y CONFIG_SND_SOC_UX500=y +CONFIG_SND_SOC_UX500_AB5500=y CONFIG_SND_SOC_UX500_AB8500=y +CONFIG_SND_SOC_UX500_CG29XX=y +CONFIG_SND_SOC_UX500_AV8100=y CONFIG_USB=y # CONFIG_USB_DEVICE_CLASS is not set CONFIG_USB_SUSPEND=y @@ -218,6 +216,7 @@ CONFIG_AB8500_USB=y CONFIG_MMC=y CONFIG_MMC_CLKGATE=y CONFIG_MMC_UNSAFE_RESUME=y +# CONFIG_MMC_BLOCK_BOUNCE is not set CONFIG_MMC_ARMMMCI=y CONFIG_NEW_LEDS=y CONFIG_LEDS_CLASS=y @@ -304,11 +303,11 @@ CONFIG_DEBUG_USER=y CONFIG_DEBUG_ERRORS=y CONFIG_KEYS=y CONFIG_CRYPTO_ECB=y -CONFIG_CRYPTO_HMAC=m CONFIG_CRYPTO_MD5=m CONFIG_CRYPTO_SHA1=m CONFIG_CRYPTO_AES=y CONFIG_CRYPTO_ARC4=y +CONFIG_CRYPTO_TWOFISH=y # CONFIG_CRYPTO_ANSI_CPRNG is not set CONFIG_CRYPTO_DEV_UX500=y CONFIG_CRYPTO_DEV_UX500_CRYP=y -- cgit v1.2.3 From 78e9d255afb8b16a895084fdb47929f2d6dcf263 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Wed, 22 Jun 2011 21:18:10 +0200 Subject: config: Add flash driver to u8500 Signed-off-by: Robert Marklund --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 80357f2a54e..19a8a251cd2 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -232,6 +232,7 @@ CONFIG_STE_DMA40=y CONFIG_STAGING=y CONFIG_U8500_MMIO=y CONFIG_U8500_CM=y +CONFIG_U8500_FLASH=y CONFIG_AUTOFS_FS=m CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y CONFIG_CG2900=y -- cgit v1.2.3 From b9514e8685e93102281171fafb65f4f29a44c732 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Thu, 23 Jun 2011 13:30:59 +0200 Subject: mfd: prcmu: Update for api changes in 3.0 Change-Id: Ia29d92edad15763a6cf9576cb934b7b77c500382 Signed-off-by: Robert Marklund --- drivers/mfd/ab5500-core.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index b50da3c5a76..75c773b232f 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -28,7 +28,7 @@ #include #include #include -#include +#include #define AB5500_NAME_STRING "ab5500" #define AB5500_ID_FORMAT_STRING "AB5500 %s" @@ -1332,7 +1332,7 @@ static int ab5500_startup_irq_enabled(struct device *dev, unsigned int irq) struct ab5500 *ab; bool val; - ab = get_irq_chip_data(irq); + ab = irq_get_chip_data(irq); irq -= ab->irq_base; val = ((ab->startup_events[irq / 8] & BIT(irq % 8)) != 0); @@ -2303,10 +2303,10 @@ static int ab5500_irq_init(struct ab5500 *ab) for (i = 0; i < ab5500_plf_data->irq.count; i++) { irq = ab5500_plf_data->irq.base + i; - set_irq_chip_data(irq, ab); - set_irq_chip_and_handler(irq, &ab5500_irq_chip, + irq_set_chip_data(irq, ab); + irq_set_chip_and_handler(irq, &ab5500_irq_chip, handle_simple_irq); - set_irq_nested_thread(irq, 1); + irq_set_nested_thread(irq, 1); #ifdef CONFIG_ARM set_irq_flags(irq, IRQF_VALID); #else @@ -2328,8 +2328,8 @@ static void ab5500_irq_remove(struct ab5500 *ab) #ifdef CONFIG_ARM set_irq_flags(irq, 0); #endif - set_irq_chip_and_handler(irq, NULL, NULL); - set_irq_chip_data(irq, NULL); + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); } } @@ -2428,7 +2428,7 @@ static int __init ab5500_probe(struct platform_device *pdev) /* Set up and register the platform devices. */ for (i = 0; i < AB5500_NUM_DEVICES; i++) { - ab5500_devs[i].mfd_data = ab5500_plf_data->dev_data[i]; + ab5500_devs[i].platform_data = ab5500_plf_data->dev_data[i]; } err = mfd_add_devices(&pdev->dev, 0, ab5500_devs, -- cgit v1.2.3 From 2af1504f75c95cb8ac21f1417104c7ab90f39056 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 5 Jul 2011 10:41:40 +0100 Subject: configs: update to a working u8500 defconfig U5500 & CW1200 withdrawn (To be fixed) Signed-off-by: Philippe Langlais Conflicts: arch/arm/configs/u8500_defconfig --- arch/arm/configs/u8500_defconfig | 44 ++++++++++++---------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 19a8a251cd2..a24b08868c7 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -8,8 +8,8 @@ CONFIG_CGROUPS=y CONFIG_CGROUP_CPUACCT=y CONFIG_RELAY=y CONFIG_BLK_DEV_INITRD=y -CONFIG_EMBEDDED=y CONFIG_KALLSYMS_ALL=y +CONFIG_EMBEDDED=y CONFIG_PERF_EVENTS=y CONFIG_SLAB=y CONFIG_BOOTTIME=y @@ -23,11 +23,8 @@ CONFIG_ARCH_U8500=y CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_HREFV60=y CONFIG_MACH_SNOWBALL=y -CONFIG_MACH_U8500_SNOWBALL=y -CONFIG_MACH_U5500=y CONFIG_UX500_PRCMU_TIMER=y CONFIG_DB8500_MLOADER=y -CONFIG_UX500_DEBUG_HWREG=y CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y @@ -35,8 +32,6 @@ CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_STANDBY=y CONFIG_UX500_SUSPEND_MEM=y -CONFIG_UX500_SUSPEND_DBG=y -CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y @@ -57,7 +52,6 @@ CONFIG_FPE_NWFPE=y CONFIG_VFP=y CONFIG_NEON=y # CONFIG_CORE_DUMP_DEFAULT_ELF_HEADERS is not set -CONFIG_PM=y CONFIG_PM_RUNTIME=y CONFIG_NET=y CONFIG_PACKET=y @@ -81,7 +75,6 @@ CONFIG_IP_NF_FILTER=y CONFIG_NF_NAT=y CONFIG_IP_NF_TARGET_MASQUERADE=y CONFIG_PHONET=y -CONFIG_PHONET_PIPECTRLR=y CONFIG_NET_SCHED=y CONFIG_BT_L2CAP=y CONFIG_BT_SCO=y @@ -91,6 +84,8 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y +CONFIG_CFG80211=m +CONFIG_MAC80211=m CONFIG_MAC80211_LEDS=y CONFIG_RFKILL=y CONFIG_RFKILL_INPUT=y @@ -104,14 +99,6 @@ CONFIG_BLK_DEV_RAM_SIZE=73728 CONFIG_MISC_DEVICES=y CONFIG_AB8500_PWM=y CONFIG_SENSORS_BH1780=y -CONFIG_NETDEVICES=y -CONFIG_SMSC_PHY=y -CONFIG_NET_ETHERNET=y -CONFIG_SMSC911X=y -# CONFIG_NETDEV_1000 is not set -# CONFIG_NETDEV_10000 is not set -# CONFIG_WLAN is not set -# CONFIG_INPUT_MOUSEDEV_PSAUX is not set CONFIG_STE_TRACE_MODEM=y CONFIG_STM_TRACE=y CONFIG_U8500_SHRM=y @@ -128,6 +115,9 @@ CONFIG_TUN=y CONFIG_SMSC_PHY=y CONFIG_NET_ETHERNET=y CONFIG_SMSC911X=y +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +# CONFIG_WLAN is not set # CONFIG_INPUT_MOUSEDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set @@ -180,9 +170,9 @@ CONFIG_VIDEO_DEV=y # CONFIG_VIDEO_CAPTURE_DRIVERS is not set CONFIG_FB=y CONFIG_FB_MCDE=y +CONFIG_AV8100_HWTRIG_I2SDAT3=y CONFIG_FB_B2R2=y CONFIG_B2R2_PLUG_CONF=y -CONFIG_AV8100_HWTRIG_I2SDAT3=y CONFIG_SOUND=y CONFIG_SND=y CONFIG_SND_USB_AUDIO=y @@ -198,6 +188,7 @@ CONFIG_USB_SUSPEND=y # CONFIG_USB_OTG_WHITELIST is not set CONFIG_USB_MON=y CONFIG_USB_MUSB_HDRC=y +CONFIG_USB_MUSB_UX500=y CONFIG_USB_MUSB_OTG=y CONFIG_USB_GADGET_MUSB_HDRC=y CONFIG_USB_ACM=y @@ -214,8 +205,8 @@ CONFIG_USB_G_MULTI=m # CONFIG_USB_G_MULTI_RNDIS is not set CONFIG_AB8500_USB=y CONFIG_MMC=y -CONFIG_MMC_CLKGATE=y CONFIG_MMC_UNSAFE_RESUME=y +CONFIG_MMC_CLKGATE=y # CONFIG_MMC_BLOCK_BOUNCE is not set CONFIG_MMC_ARMMMCI=y CONFIG_NEW_LEDS=y @@ -230,10 +221,6 @@ CONFIG_RTC_DRV_PL031=y CONFIG_DMADEVICES=y CONFIG_STE_DMA40=y CONFIG_STAGING=y -CONFIG_U8500_MMIO=y -CONFIG_U8500_CM=y -CONFIG_U8500_FLASH=y -CONFIG_AUTOFS_FS=m CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y CONFIG_CG2900=y CONFIG_CG2900_CHIP=y @@ -242,9 +229,9 @@ CONFIG_CG2900_UART=y CONFIG_CG2900_AUDIO=y CONFIG_CG2900_TEST=y CONFIG_BT_CG2900=y -CONFIG_CW1200=m -CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES=y -CONFIG_CW1200_USE_GPIO_IRQ=y +CONFIG_U8500_MMIO=y +CONFIG_U8500_CM=y +CONFIG_U8500_FLASH=y CONFIG_EXT2_FS=y CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y @@ -294,14 +281,9 @@ CONFIG_TIMER_STATS=y # CONFIG_DEBUG_PREEMPT is not set CONFIG_DEBUG_SPINLOCK_SLEEP=y CONFIG_DEBUG_INFO=y -# CONFIG_FTRACE is not set -CONFIG_DEBUG_USER=y -# CONFIG_RCU_CPU_STALL_DETECTOR is not set CONFIG_SYSCTL_SYSCALL_CHECK=y -CONFIG_SCHED_TRACER=y -CONFIG_DYNAMIC_DEBUG=y +# CONFIG_FTRACE is not set CONFIG_DEBUG_USER=y -CONFIG_DEBUG_ERRORS=y CONFIG_KEYS=y CONFIG_CRYPTO_ECB=y CONFIG_CRYPTO_MD5=m -- cgit v1.2.3 From 80ff9d135bac2a7904e99db6f62667114f44d958 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 6 Jul 2011 14:09:38 +0200 Subject: configs: u8500 defconfig: Activate U5500 platforms support Signed-off-by: Philippe Langlais --- arch/arm/configs/u8500_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index a24b08868c7..25df0002cdc 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -20,9 +20,11 @@ CONFIG_MODULE_SRCVERSION_ALL=y # CONFIG_BLK_DEV_BSG is not set CONFIG_DEFAULT_DEADLINE=y CONFIG_ARCH_U8500=y +CONFIG_UX500_SOC_DB5500=y CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_HREFV60=y CONFIG_MACH_SNOWBALL=y +CONFIG_MACH_U5500=y CONFIG_UX500_PRCMU_TIMER=y CONFIG_DB8500_MLOADER=y CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y -- cgit v1.2.3 From ec52519fc5ddf6772e6339da2f93302be362a4d4 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Fri, 14 Oct 2011 16:19:38 +0200 Subject: ux500: align u5500 PRCMU & CPUFREQ management with u8500 (multiple commits in one) Signed-off-by: Philippe Langlais Merge of following commits too: u5500: add support for sysclk basic sysclk support added in PRCMU driver and clock framework driver updated. Signed-off-by: Shreshtha Kumar Sahu U5500: Support for ESRAM12 EPOD in PRCMU driver Signed-off-by: Vijaya Kumar Kilari ux500: regulator: handle different base offset of ePOD ID 5500 ePOD ids are offseted for some reason in the PRCMU driver. Adjust the ids to index the local arrays to avoid memory corruption. Signed-off-by: Rabin Vincent ux500: pm: support PRCMU status check on 5500 This also removes unused 8500v1 code. Signed-off-by: Rabin Vincent arm: ux500: prcmu_ac_wake_req workaround This patch adds a check in prcmu_ac_wake_req that the modem is awake (in terms of the value in the PRCM_MOD_AWAKE_STATUS register) after the AC_WAKE_ACK has been received from the PRCMU FW. If the check fails, a retry is made. This seems to be necessary, since the modem can generate an AC_WAKE_ACK, and then still go to sleep. Signed-off-by: Mattias Nilsson U5500: Add support for PRCMU Mailbox0 Add PRCMU mailbox 0 support for irq wakeup enable and disable Signed-off-by: Vijaya Kumar K U5500: Add support for power state transition PRCMU driver is updated to provide API for system power state transition Signed-off-by: Vijaya Kumar K ARM: ux500: prcmu: Add A9 watchdog interface Signed-off-by: Jonas Aaberg U5500 : ab5500 core interrupt hander update AB5500 interrupts will be now handled by PRCMU and then routed to AB5500 core driver.AB5500 irq handler will no more read the latch registers to find the interrupt reason.Instead PRCMU will read the latch registers and provide the values to core driver. Signed-off-by: Bibek Basu ARM: ux500: prcmu-dbg: Tiny code clean-up Signed-off-by: Jonas Aaberg u5500: add mailbox1 and related function support Add cpufreq-db5500.c file for db5500 CPUfreq support. PRCMU mailbox1 and related functions' support is added. List of functions implemented: - prcmu_get/set_arm_opp - read_mailbox_1 Signed-off-by: Shreshtha Kumar Sahu Fix for PRCMU u5500: PRCMU IRQ should be NO_SUSPEND As on 8500. Signed-off-by: Rabin Vincent ARM: u5500: PRCMU reset API Added API for rebooting the board and for getting the last reboot code. Signed-off-by: Pawel Szyszuk --- drivers/mfd/ab5500-core.c | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 75c773b232f..df0ce72e585 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -28,7 +28,8 @@ #include #include #include -#include +#include +#include #define AB5500_NAME_STRING "ab5500" #define AB5500_ID_FORMAT_STRING "AB5500 %s" @@ -48,7 +49,7 @@ #define AB5500_MASK_BASE (0x60) #define AB5500_MASK_END (0x79) #define AB5500_CHIP_ID (0x20) - +#define AB5500_INTERRUPTS 0x007FFFFF /** * struct ab5500_bank * @slave_addr: I2C slave_addr found in AB5500 specification @@ -1071,17 +1072,6 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, }; -/* - * This stubbed prcmu functionality should be removed when the prcmu driver - * implements it. - */ -static u8 prcmu_event_buf[AB5500_NUM_EVENT_REG]; - -void prcmu_get_abb_event_buf(u8 **buf) -{ - *buf = prcmu_event_buf; -} - /* * Functionality for getting/setting register values. */ @@ -1355,19 +1345,20 @@ static irqreturn_t ab5500_irq(int irq, void *data) { struct ab5500 *ab = data; u8 i; + u8 *pvalue; + u8 value; - /* - * TODO: use the ITMASTER registers to reduce the number of i2c reads. - */ - + prcmu_get_abb_event_buffer((void **)&pvalue); + if (unlikely(pvalue == NULL)) { + dev_err(ab->dev, "PRCMU not enabled!!!\n"); + goto error_irq; + } for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { - int status; - u8 value; - - status = get_register_interruptible(ab, AB5500_BANK_IT, - AB5500_IT_LATCH0_REG + i, &value); - if (status < 0 || value == 0) + value = readb(pvalue); + if (value == 0) { + pvalue++; continue; + } do { int bit = __ffs(value); @@ -1376,9 +1367,12 @@ static irqreturn_t ab5500_irq(int irq, void *data) handle_nested_irq(ab->irq_base + line); value &= ~(1 << bit); } while (value); + pvalue++; } return IRQ_HANDLED; +error_irq: + return IRQ_NONE; } #ifdef CONFIG_DEBUG_FS @@ -2416,7 +2410,7 @@ static int __init ab5500_probe(struct platform_device *pdev) goto exit_remove_irq; } - + prcmu_config_abb_event_readout(AB5500_INTERRUPTS); /* This real unpredictable IRQ is of course sampled for entropy */ rand_initialize_irq(res->start); -- cgit v1.2.3 From a506470a51d1050f9a4004ed4242237b91fb8a1a Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 20 Jun 2011 10:56:04 +0200 Subject: ux500: config: Align u8500_defconfig with u5500+u8500+u9500 default configs Signed-off-by: Philippe Langlais --- arch/arm/configs/u8500_defconfig | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 25df0002cdc..41ef773cf93 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -117,9 +117,7 @@ CONFIG_TUN=y CONFIG_SMSC_PHY=y CONFIG_NET_ETHERNET=y CONFIG_SMSC911X=y -# CONFIG_NETDEV_1000 is not set -# CONFIG_NETDEV_10000 is not set -# CONFIG_WLAN is not set +CONFIG_CAIF_SHM=y # CONFIG_INPUT_MOUSEDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set @@ -148,7 +146,6 @@ CONFIG_SPI=y # CONFIG_STM_MSP_SPI is not set CONFIG_SPI_PL022=y CONFIG_GPIO_SYSFS=y -CONFIG_GPIO_STMPE=y CONFIG_GPIO_TC3589X=y CONFIG_AB8500_GPIO=y CONFIG_POWER_SUPPLY=y @@ -216,6 +213,7 @@ CONFIG_LEDS_CLASS=y CONFIG_LEDS_LM3530=y CONFIG_LEDS_LP5521=y CONFIG_LEDS_PWM=y +CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_RTC_CLASS=y CONFIG_RTC_DRV_AB=y CONFIG_RTC_DRV_AB8500=y -- cgit v1.2.3 From 4ba603c4d03763e4cd9c90cf668b46203241e02f Mon Sep 17 00:00:00 2001 From: Mattias Wallin Date: Wed, 8 Jun 2011 16:27:21 +0200 Subject: power: remove charger from ab8500 struct The pointer to the charger from the ab8500 struct is no longer needed. ST-Ericsson ID: 256401 ST-Ericsson FOSS-OUT ID: Trivial ST-Ericsson Linux next: NA Signed-off-by: Mattias Wallin Change-Id: Iec4d4fb69c54366f96283ab1e676a874637550f0 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24710 Reviewed-by: Johan PALSSON Reviewed-by: QATOOLS Reviewed-by: QATEST Reviewed-by: Jonas ABERG --- drivers/power/ab8500_charger.c | 1 - include/linux/mfd/ab8500.h | 6 ------ 2 files changed, 7 deletions(-) diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index 2f9f14d10bb..e9db45494fa 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -2515,7 +2515,6 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, di); - di->parent->charger = di; return ret; diff --git a/include/linux/mfd/ab8500.h b/include/linux/mfd/ab8500.h index 6219e1139e8..8f66bd52679 100644 --- a/include/linux/mfd/ab8500.h +++ b/include/linux/mfd/ab8500.h @@ -139,9 +139,6 @@ #define AB8500_NR_IRQS 112 #define AB8500_NUM_IRQ_REGS 14 -/* Forward declaration */ -struct ab8500_charger; - /** * struct ab8500 - ab8500 internal structure * @dev: parent device @@ -155,7 +152,6 @@ struct ab8500_charger; * @tx_buf: tx buf for SPI * @mask: cache of IRQ regs for bus lock * @oldmask: cache of previous IRQ regs for bus lock - * @charger: pointer to the charger driver device information. */ struct ab8500 { struct device *dev; @@ -173,8 +169,6 @@ struct ab8500 { u8 mask[AB8500_NUM_IRQ_REGS]; u8 oldmask[AB8500_NUM_IRQ_REGS]; - - struct ab8500_charger *charger; }; struct regulator_reg_init; -- cgit v1.2.3 From 5fa1332d68bcaade26122d566fc38a3d3149f584 Mon Sep 17 00:00:00 2001 From: Chris Kimber Date: Wed, 8 Jun 2011 15:49:59 +0100 Subject: hwmon: ab8500: Automatically monitor sensors when an alarm is set When setting a min/max/max_hyst alarm value for a sensor, it is no longer neccessary to manually kick of a monitoring job. ST-Ericsson Linux next: - ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I24f8f81481f3ff96b54c197ffcbe60f2e50a0d7c Signed-off-by: Chris Kimber Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24714 Reviewed-by: QATEST Reviewed-by: Martin PERSSON Reviewed-by: Linus WALLEIJ --- drivers/hwmon/ab8500.c | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c index c8c05c6fa15..1cfab4948e4 100644 --- a/drivers/hwmon/ab8500.c +++ b/drivers/hwmon/ab8500.c @@ -46,6 +46,7 @@ * frame, this driver will. Time unit is ms. */ #define DEFAULT_POWER_OFF_DELAY 10000 +#define DEFAULT_MONITOR_DELAY 1000 #define NUM_SENSORS 5 @@ -91,13 +92,17 @@ static bool find_active_thresholds(struct ab8500_temp *data) dev_dbg(&data->pdev->dev, "No active thresholds," "cancel deferred job (if it exists)" "and reset temp monitor delay\n"); - mutex_lock(&data->lock); - data->gpadc_monitor_delay = 0; cancel_delayed_work_sync(&data->work); - mutex_unlock(&data->lock); return false; } + +static inline void schedule_monitor(struct ab8500_temp *data) { + unsigned long delay_in_jiffies; + delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); + schedule_delayed_work(&data->work, delay_in_jiffies); +} + static void thermal_power_off(struct work_struct *work) { struct ab8500_temp *data = container_of(work, struct ab8500_temp, @@ -226,22 +231,21 @@ static ssize_t set_temp_monitor_delay(struct device *dev, const char *buf, size_t count) { int res; - unsigned long delay_in_jiffies, delay_in_s; + unsigned long delay_in_s; struct ab8500_temp *data = dev_get_drvdata(dev); - if (!find_active_thresholds(data)) { - dev_dbg(dev, "No thresholds to monitor, disregarding delay\n"); - return count; - } res = strict_strtoul(buf, 10, &delay_in_s); if (res < 0) return res; mutex_lock(&data->lock); data->gpadc_monitor_delay = delay_in_s * 1000; + + if (find_active_thresholds(data)) { + schedule_monitor(data); + } + mutex_unlock(&data->lock); - delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); - schedule_delayed_work(&data->work, delay_in_jiffies); return count; } @@ -251,7 +255,7 @@ static ssize_t set_temp_power_off_delay(struct device *dev, const char *buf, size_t count) { int res; - unsigned long delay_in_jiffies, delay_in_s; + unsigned long delay_in_s; struct ab8500_temp *data = dev_get_drvdata(dev); res = strict_strtoul(buf, 10, &delay_in_s); @@ -261,7 +265,6 @@ static ssize_t set_temp_power_off_delay(struct device *dev, mutex_lock(&data->lock); data->power_off_delay = delay_in_s * 1000; mutex_unlock(&data->lock); - delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); return count; } @@ -366,9 +369,13 @@ static ssize_t set_min(struct device *dev, struct device_attribute *devattr, data->min_alarm[attr->index - 1] = 0; data->min[attr->index - 1] = val; - mutex_unlock(&data->lock); + if (val == 0) (void) find_active_thresholds(data); + else + schedule_monitor(data); + + mutex_unlock(&data->lock); return count; } @@ -392,9 +399,13 @@ static ssize_t set_max(struct device *dev, struct device_attribute *devattr, data->max_alarm[attr->index - 1] = 0; data->max[attr->index - 1] = val; - mutex_unlock(&data->lock); + if (val == 0) (void) find_active_thresholds(data); + else + schedule_monitor(data); + + mutex_unlock(&data->lock); return count; } @@ -419,9 +430,13 @@ static ssize_t set_max_hyst(struct device *dev, data->max_hyst_alarm[attr->index - 1] = 0; data->max_hyst[attr->index - 1] = val; - mutex_unlock(&data->lock); + if (val == 0) (void) find_active_thresholds(data); + else + schedule_monitor(data); + + mutex_unlock(&data->lock); return count; } @@ -668,6 +683,7 @@ static int __devinit ab8500_temp_probe(struct platform_device *pdev) mutex_init(&data->lock); data->pdev = pdev; data->power_off_delay = DEFAULT_POWER_OFF_DELAY; + data->gpadc_monitor_delay = DEFAULT_MONITOR_DELAY; platform_set_drvdata(pdev, data); -- cgit v1.2.3 From cfa4200f12911fd7b1479828c91610970d1ba474 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 20 Jun 2011 14:27:33 +0200 Subject: u8500: defconfig: Enable CONFIG_MODEM & CONFIG_MODEM_U8500 Enables the Modem Access Framework Enables the STE U8500 Modem Access driver ST-Ericsson ID: CR329459 Change-Id: I1ce4c4f4e3fcd42f50f5f82d1f04f2fe52abe5aa Signed-off-by: Kumar Sanghvi Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23556 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR Conflicts: arch/arm/configs/u8500_defconfig --- arch/arm/configs/u8500_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 41ef773cf93..d39c3b58956 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -34,6 +34,8 @@ CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_STANDBY=y CONFIG_UX500_SUSPEND_MEM=y +CONFIG_MODEM=y +CONFIG_MODEM_U8500=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y -- cgit v1.2.3 From 9559fe31252be122700b8281413a10c5fd954261 Mon Sep 17 00:00:00 2001 From: Johan Palsson Date: Wed, 1 Jun 2011 18:46:04 +0200 Subject: power: ab8500_bm: Add sysfs node for charge_full In order for a userspace application to save the maximum capacity in persistant storage, this driver needs to expose this parameter in sysfs. This patch enables that functionality ST-Ericsson Linux Next: - ST-Ericsson ID: ER334609 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I9dd5e9c1faeb6d41da051417999ebdf53a2eaa72 Signed-off-by: Johan Palsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/24951 Reviewed-by: QATEST Tested-by: Karl KOMIEROWSKI Reviewed-by: Jonas ABERG --- drivers/power/ab8500_fg.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index 8af5216b90b..29237aa2746 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -154,6 +155,7 @@ struct ab8500_fg_flags { * @fg_work: Work to run the FG algorithm instantly * @fg_acc_cur_work: Work to read the FG accumulator * @cc_lock: Mutex for locking the CC + * @fg_kobject: Structure of type kobject */ struct ab8500_fg { struct device *dev; @@ -186,6 +188,7 @@ struct ab8500_fg { struct work_struct fg_work; struct work_struct fg_acc_cur_work; struct mutex cc_lock; + struct kobject fg_kobject; }; /* Main battery properties */ @@ -1617,6 +1620,8 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) di->flags.fully_charged = true; /* Save current capacity as maximum */ di->bat_cap.max_mah = di->bat_cap.mah; + sysfs_notify(&di->fg_kobject, + NULL, "charge_full"); queue_work(di->fg_wq, &di->fg_work); break; case POWER_SUPPLY_STATUS_CHARGING: @@ -1703,6 +1708,116 @@ static void ab8500_fg_external_power_changed(struct power_supply *psy) &di->fg_psy, ab8500_fg_get_ext_psy_data); } +/* Exposure to the sysfs interface */ + +struct ab8500_fg_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct ab8500_fg *, char *); + ssize_t (*store)(struct ab8500_fg *, const char *, size_t); +}; + +static ssize_t charge_full_show(struct ab8500_fg *di, char *buf) +{ + return sprintf(buf, "%d\n", di->bat_cap.max_mah); +} + +static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf, + size_t count) +{ + unsigned long charge_full; + ssize_t ret = -EINVAL; + + ret = strict_strtoul(buf, 10, &charge_full); + + dev_dbg(di->dev, "Ret %d charge_full %d", ret, charge_full); + if (!ret) { + di->bat_cap.max_mah = (int) charge_full; + ret = count; + } + + return ret; +} +static struct ab8500_fg_sysfs_entry charge_full_attr = + __ATTR(charge_full, 0644, charge_full_show, charge_full_store); + +static ssize_t +ab8500_fg_show(struct kobject *kobj, struct attribute *attr, char *buf) +{ + struct ab8500_fg_sysfs_entry *entry; + struct ab8500_fg *di; + + entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr); + di = container_of(kobj, struct ab8500_fg, fg_kobject); + + if (!entry->show) + return -EIO; + + return entry->show(di, buf); +} + +static ssize_t +ab8500_fg_store(struct kobject *kobj, struct attribute *attr, const char *buf, + size_t count) +{ + struct ab8500_fg_sysfs_entry *entry; + struct ab8500_fg *di; + + entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr); + di = container_of(kobj, struct ab8500_fg, fg_kobject); + + if (!entry->store) + return -EIO; + + return entry->store(di, buf, count); +} + +const struct sysfs_ops ab8500_fg_sysfs_ops = { + .show = ab8500_fg_show, + .store = ab8500_fg_store, +}; + +static struct attribute *ab8500_fg_attrs[] = { + &charge_full_attr.attr, + NULL, +}; + +static struct kobj_type ab8500_fg_ktype = { + .sysfs_ops = &ab8500_fg_sysfs_ops, + .default_attrs = ab8500_fg_attrs, +}; + +/** + * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function removes the entry in sysfs. + */ +static void ab8500_fg_sysfs_exit(struct ab8500_fg *di) +{ + kobject_del(&di->fg_kobject); +} + +/** + * ab8500_chargalg_sysfs_init() - init of sysfs entry + * @di: pointer to the struct ab8500_chargalg + * + * This function adds an entry in sysfs. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_fg_sysfs_init(struct ab8500_fg *di) +{ + int ret = 0; + + ret = kobject_init_and_add(&di->fg_kobject, + &ab8500_fg_ktype, + NULL, "ab8500_fg"); + if (ret < 0) + dev_err(di->dev, "failed to create sysfs entry\n"); + + return ret; +} +/* Exposure to the sysfs interface <> */ + #if defined(CONFIG_PM) static int ab8500_fg_resume(struct platform_device *pdev) { @@ -1752,6 +1867,7 @@ static int __devexit ab8500_fg_remove(struct platform_device *pdev) dev_err(di->dev, "failed to disable coulomb counter\n"); destroy_workqueue(di->fg_wq); + ab8500_fg_sysfs_exit(di); flush_scheduled_work(); power_supply_unregister(&di->fg_psy); @@ -1884,6 +2000,12 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev) platform_set_drvdata(pdev, di); + ret = ab8500_fg_sysfs_init(di); + if (ret) { + dev_err(di->dev, "failed to create sysfs entry\n"); + goto free_irq; + } + /* Calibrate the fg first time */ di->flags.calibrate = true; di->calib_state = AB8500_FG_CALIB_INIT; -- cgit v1.2.3 From c53ce058e1ec6d0850318729634d1a60dfa11291 Mon Sep 17 00:00:00 2001 From: Kalle Komierowski Date: Fri, 17 Jun 2011 17:02:44 +0200 Subject: power: ab8500_fg: Fixed correct conversion type for a print ST-Ericsson Linux Next: - ST-Ericsson ID: ER334609 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ia914fa6c65d9c4a5ecbc55237882590772c8b44d Signed-off-by: Kalle Komierowski Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/25439 Reviewed-by: QATEST Reviewed-by: Jonas ABERG --- drivers/power/ab8500_fg.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index 29237aa2746..b7462d493e0 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -1729,7 +1729,8 @@ static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf, ret = strict_strtoul(buf, 10, &charge_full); - dev_dbg(di->dev, "Ret %d charge_full %d", ret, charge_full); + dev_dbg(di->dev, "Ret %d charge_full %lu", ret, charge_full); + if (!ret) { di->bat_cap.max_mah = (int) charge_full; ret = count; -- cgit v1.2.3 From 83dd3f0e75cc16b6a5e69fe82bb1a7f51a0a3857 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 26 May 2011 16:35:34 +0530 Subject: mfd: ab5500 - update with battery driver related stuff Add charger, fuel gauge, battery temperature as clients of ab5500. Update the registers access privilege to each of the above mentioned and also the interrupts being used. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: WP256401 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ied7a7bc876459c3b96a62cda1ce29354f70c8573 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23148 Reviewed-by: QATEST Reviewed-by: Karl KOMIEROWSKI Reviewed-by: Johan PALSSON Reviewed-by: Srinidhi KASAGAR --- drivers/mfd/ab5500-core.c | 341 +++++++++++++++++++++++++++++++++++++++++++-- include/linux/mfd/abx500.h | 250 ++++++++++++++++++++++++++++++++- 2 files changed, 579 insertions(+), 12 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index df0ce72e585..189f4b66bfa 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -208,7 +208,7 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { { .first = 0x1F, .last = 0x22, - .perm = AB5500_PERM_RO, + .perm = AB5500_PERM_RW, }, { .first = 0x23, @@ -446,15 +446,15 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { }, }, [AB5500_DEVID_CHARGER] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges []) { + .nbanks = 4, + .bank = (struct ab5500_i2c_ranges[]) { { .bankid = AB5500_BANK_CHG, .nranges = 2, .range = (struct ab5500_reg_range[]) { { .first = 0x11, - .last = 0x11, + .last = 0x12, .perm = AB5500_PERM_RO, }, { @@ -464,9 +464,128 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { }, }, }, + { + .bankid = AB5500_BANK_USB, + .nranges = 13, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x01, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x80, + .last = 0x83, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x87, + .last = 0x8B, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x91, + .last = 0x94, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xA8, + .last = 0xB0, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xB2, + .last = 0xB2, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xB4, + .last = 0xBC, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xBF, + .last = 0xBF, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xC1, + .last = 0xC6, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xCD, + .last = 0xCD, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xD6, + .last = 0xDA, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xDC, + .last = 0xDC, + .perm = AB5500_PERM_RW, + }, + { + .first = 0xE0, + .last = 0xE4, + .perm = AB5500_PERM_RW, + }, + }, + }, + { + .bankid = AB5500_BANK_IT, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x02, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x20, + .last = 0x36, + .perm = AB5500_PERM_RO, + }, + }, + }, + { + .bankid = AB5500_BANK_STARTUP, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x2F, + .last = 0x30, + .perm = AB5500_PERM_RW, + }, + }, + }, + }, + }, + [AB5500_DEVID_BTEMP] = { + .nbanks = 1, + .bank = (struct ab5500_i2c_ranges []) { + { + .bankid = AB5500_BANK_FG_BATTCOM_ACC, + .nranges = 2, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x0B, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x0C, + .last = 0x24, + .perm = AB5500_PERM_RW, + }, + }, + }, }, }, - [AB5500_DEVID_FUELGAUGE] = { + [AB5500_DEVID_FG] = { .nbanks = 1, .bank = (struct ab5500_i2c_ranges []) { { @@ -480,7 +599,7 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { }, { .first = 0x0C, - .last = 0x10, + .last = 0x24, .perm = AB5500_PERM_RW, }, }, @@ -604,6 +723,206 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { [AB5500_DEVID_CHARGER] = { .name = "ab5500-charger", .id = AB5500_DEVID_CHARGER, + .num_resources = 29, + .resources = (struct resource[]) { + { + .name = "VBAT_INSERT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 4), + .end = AB5500_IRQ(2, 4), + }, + { + .name = "TEMP_ASIC_ALARM", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 2), + .end = AB5500_IRQ(2, 2), + }, + { + .name = "BATT_REMOVAL", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 6), + .end = AB5500_IRQ(7, 6), + }, + { + .name = "BATT_ATTACH", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 5), + .end = AB5500_IRQ(7, 5), + }, + { + .name = "CGSTATE_10_PCVBUS_CHG", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 7), + .end = AB5500_IRQ(8, 7), + }, + { + .name = "VBUS_FALLING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 6), + .end = AB5500_IRQ(8, 6), + }, + { + .name = "VBUS_RISING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 5), + .end = AB5500_IRQ(8, 5), + }, + { + .name = "UART_RDY_TX", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 2), + .end = AB5500_IRQ(8, 2), + }, + { + .name = "UART_RDY_RX", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 1), + .end = AB5500_IRQ(8, 1), + }, + { + .name = "UART_OVERRUN", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(8, 0), + .end = AB5500_IRQ(8, 0), + }, + { + .name = "VBUS_IMEAS_MAX_CHANGE_RISING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 7), + .end = AB5500_IRQ(9, 7), + }, + { + .name = "USB_SUSPEND", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 3), + .end = AB5500_IRQ(9, 3), + }, + { + .name = "USB_CHAR_DET_DONE", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(9, 1), + .end = AB5500_IRQ(9, 1), + }, + { + .name = "VBUS_IMEAS_MAX_CHANGE_FALLING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(10, 0), + .end = AB5500_IRQ(10, 0), + }, + { + .name = "OVV", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 5), + .end = AB5500_IRQ(14, 5), + }, + { + .name = "BSI_INDICATOR", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(14, 4), + .end = AB5500_IRQ(14, 4), + }, + { + .name = "USB_CH_TH_PROTECTION", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 4), + .end = AB5500_IRQ(15, 4), + }, + { + .name = "USB_CH_NOT_OK", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(15, 3), + .end = AB5500_IRQ(15, 3), + }, + { + .name = "CHAR_TEMP_WINDOW_OK_RISING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(17, 7), + .end = AB5500_IRQ(17, 7), + }, + { + .name = "CHARGING_STOPPED_BY_TEMP", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 6), + .end = AB5500_IRQ(18, 6), + }, + { + .name = "VBUS_DROP_FALLING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 4), + .end = AB5500_IRQ(18, 4), + }, + { + .name = "VBUS_DROP_RISING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 3), + .end = AB5500_IRQ(18, 3), + }, + { + .name = "CHAR_TEMP_WINDOW_OK_FALLING", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(18, 0), + .end = AB5500_IRQ(18, 0), + }, + { + .name = "CHG_STATE_13_COMP_VBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 3), + .end = AB5500_IRQ(21, 3), + }, + { + .name = "CHG_STATE_12_COMP_VBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 2), + .end = AB5500_IRQ(21, 2), + }, + { + .name = "CHG_STATE_11_SAFE_MODE_VBUS", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(21, 1), + .end = AB5500_IRQ(21, 1), + }, + { + .name = "USB_LINK_UPDATE", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(22, 1), + .end = AB5500_IRQ(22, 1), + }, + { + .name = "CHG_SW_TIMER_OUT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(23, 7), + .end = AB5500_IRQ(23, 7), + }, + { + .name = "CHG_HW_TIMER_OUT", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(23, 6), + .end = AB5500_IRQ(23, 6), + }, + }, + }, + [AB5500_DEVID_CHARGALG] = { + .name = "abx500-chargalg", + .id = AB5500_DEVID_CHARGALG, + }, + [AB5500_DEVID_BTEMP] = { + .name = "ab5500-btemp", + .id = AB5500_DEVID_BTEMP, + .num_resources = 2, + .resources = (struct resource[]) { + { + .name = "BATT_ATTACH", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 5), + .end = AB5500_IRQ(7, 5), + }, + { + .name = "BATT_REMOVAL", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(7, 6), + .end = AB5500_IRQ(7, 6), + }, + }, }, [AB5500_DEVID_ADC] = { .name = "ab5500-adc", @@ -672,9 +991,9 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, }, }, - [AB5500_DEVID_FUELGAUGE] = { - .name = "ab5500-fuelgauge", - .id = AB5500_DEVID_FUELGAUGE, + [AB5500_DEVID_FG] = { + .name = "ab5500-fg", + .id = AB5500_DEVID_FG, .num_resources = 6, .resources = (struct resource[]) { { @@ -1395,7 +1714,7 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x1F, .last = 0x22, - .perm = AB5500_PERM_RO, + .perm = AB5500_PERM_RW, }, { .first = 0x23, @@ -1554,7 +1873,7 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { }, { .first = 0x0C, - .last = 0x10, + .last = 0x24, .perm = AB5500_PERM_RW, }, }, diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index d8929038d09..37a4bfecc30 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -220,7 +220,9 @@ enum ab5500_devid { AB5500_DEVID_SIM, AB5500_DEVID_RTC, AB5500_DEVID_CHARGER, - AB5500_DEVID_FUELGAUGE, + AB5500_DEVID_CHARGALG, + AB5500_DEVID_FG, + AB5500_DEVID_BTEMP, AB5500_DEVID_VIBRATOR, AB5500_DEVID_CODEC, AB5500_DEVID_USB, @@ -342,7 +344,9 @@ static inline int ab5500_clock_rtc_enable(int num, bool enable) extern int ab5500_clock_rtc_enable(int num, bool enable); #endif +/* Forward Declaration */ struct ab5500_regulator_platform_data; + struct ab5500_platform_data { struct {unsigned int base; unsigned int count; } irq; void *dev_data[AB5500_NUM_DEVICES]; @@ -397,6 +401,250 @@ struct abx500_ops { int (*startup_irq_enabled) (struct device *, unsigned int); }; +/* Battery driver related data */ +/* + * ADC for the battery thermistor. + * When using the ABx500_ADC_THERM_BATCTRL the battery ID resistor is combined + * with a NTC resistor to both identify the battery and to measure its + * temperature. Different phone manufactures uses different techniques to both + * identify the battery and to read its temperature. + */ +enum abx500_adc_therm { + ABx500_ADC_THERM_BATCTRL, + ABx500_ADC_THERM_BATTEMP, +}; + +/** + * struct abx500_res_to_temp - defines one point in a temp to res curve. To + * be used in battery packs that combines the identification resistor with a + * NTC resistor. + * @temp: battery pack temperature in Celcius + * @resist: NTC resistor net total resistance + */ +struct abx500_res_to_temp { + int temp; + int resist; +}; + +/** + * struct abx500_v_to_cap - Table for translating voltage to capacity + * @voltage: Voltage in mV + * @capacity: Capacity in percent + */ +struct abx500_v_to_cap { + int voltage; + int capacity; +}; + +/* Forward declaration */ +struct abx500_fg; + +/** + * struct abx500_fg_parameters - Fuel gauge algorithm parameters, in seconds + * if not specified + * @recovery_sleep_timer: Time between measurements while recovering + * @recovery_total_time: Total recovery time + * @init_timer: Measurement interval during startup + * @init_discard_time: Time we discard voltage measurement at startup + * @init_total_time: Total init time during startup + * @high_curr_time: Time current has to be high to go to recovery + * @accu_charging: FG accumulation time while charging + * @accu_high_curr: FG accumulation time in high current mode + * @high_curr_threshold: High current threshold, in mA + * @lowbat_threshold: Low battery threshold, in mV + */ +struct abx500_fg_parameters { + int recovery_sleep_timer; + int recovery_total_time; + int init_timer; + int init_discard_time; + int init_total_time; + int high_curr_time; + int accu_charging; + int accu_high_curr; + int high_curr_threshold; + int lowbat_threshold; +}; + +/** + * struct abx500_charger_maximization - struct used by the board config. + * @use_maxi: Enable maximization for this battery type + * @maxi_chg_curr: Maximum charger current allowed + * @maxi_wait_cycles: cycles to wait before setting charger current + * @charger_curr_step delta between two charger current settings (mA) + */ +struct abx500_maxim_parameters { + bool ena_maxi; + int chg_curr; + int wait_cycles; + int charger_curr_step; +}; + +/** + * struct abx500_battery_type - different batteries supported + * @name: battery technology + * @resis_high: battery upper resistance limit + * @resis_low: battery lower resistance limit + * @charge_full_design: Maximum battery capacity in mAh + * @nominal_voltage: Nominal voltage of the battery in mV + * @termination_vol: max voltage upto which battery can be charged + * @termination_curr battery charging termination current in mA + * @recharge_vol battery voltage limit that will trigger a new + * full charging cycle in the case where maintenan- + * -ce charging has been disabled + * @normal_cur_lvl: charger current in normal state in mA + * @normal_vol_lvl: charger voltage in normal state in mV + * @maint_a_cur_lvl: charger current in maintenance A state in mA + * @maint_a_vol_lvl: charger voltage in maintenance A state in mV + * @maint_a_chg_timer_h: charge time in maintenance A state + * @maint_b_cur_lvl: charger current in maintenance B state in mA + * @maint_b_vol_lvl: charger voltage in maintenance B state in mV + * @maint_b_chg_timer_h: charge time in maintenance B state + * @low_high_cur_lvl: charger current in temp low/high state in mA + * @low_high_vol_lvl: charger voltage in temp low/high state in mV' + * @battery_resistance: battery inner resistance in mOhm. + * @n_r_t_tbl_elements: number of elements in r_to_t_tbl + * @r_to_t_tbl: table containing resistance to temp points + * @n_v_cap_tbl_elements: number of elements in v_to_cap_tbl + * @v_to_cap_tbl: Voltage to capacity (in %) table + */ +struct abx500_battery_type { + int name; + int resis_high; + int resis_low; + int charge_full_design; + int nominal_voltage; + int termination_vol; + int termination_curr; + int recharge_vol; + int normal_cur_lvl; + int normal_vol_lvl; + int maint_a_cur_lvl; + int maint_a_vol_lvl; + int maint_a_chg_timer_h; + int maint_b_cur_lvl; + int maint_b_vol_lvl; + int maint_b_chg_timer_h; + int low_high_cur_lvl; + int low_high_vol_lvl; + int battery_resistance; + int n_temp_tbl_elements; + struct abx500_res_to_temp *r_to_t_tbl; + int n_v_cap_tbl_elements; + struct abx500_v_to_cap *v_to_cap_tbl; +}; + +/** + * struct abx500_bm_capacity_levels - abx500 capacity level data + * @critical: critical capacity level in percent + * @low: low capacity level in percent + * @normal: normal capacity level in percent + * @high: high capacity level in percent + * @full: full capacity level in percent + */ +struct abx500_bm_capacity_levels { + int critical; + int low; + int normal; + int high; + int full; +}; + +/** + * struct abx500_bm_charger_parameters - Charger specific parameters + * @usb_volt_max: maximum allowed USB charger voltage in mV + * @usb_curr_max: maximum allowed USB charger current in mA + * @ac_volt_max: maximum allowed AC charger voltage in mV + * @ac_curr_max: maximum allowed AC charger current in mA + */ +struct abx500_bm_charger_parameters { + int usb_volt_max; + int usb_curr_max; + int ac_volt_max; + int ac_curr_max; +}; + +/** + * struct abx500_bm_data - abx500 battery management data + * @temp_under under this temp, charging is stopped + * @temp_low between this temp and temp_under charging is reduced + * @temp_high between this temp and temp_over charging is reduced + * @temp_over over this temp, charging is stopped + * @main_safety_tmr_h safety timer for main charger + * @usb_safety_tmr_h safety timer for usb charger + * @bkup_bat_v voltage which we charge the backup battery with + * @bkup_bat_i current which we charge the backup battery with + * @no_maintenance indicates that maintenance charging is disabled + * @abx500_adc_therm placement of thermistor, batctrl or battemp adc + * @chg_unknown_bat flag to enable charging of unknown batteries + * @enable_overshoot flag to enable VBAT overshoot control + * @fg_res resistance of FG resistor in mOhm + * @n_btypes number of elements in array bat_type + * @batt_id index of the identified battery in array bat_type + * @interval_charging charge alg cycle period time when charging (sec) + * @interval_not_charging charge alg cycle period time when not charging (sec) + * @temp_hysteresis temperature hysteresis + * @maxi: maximization parameters + * @cap_levels capacity in percent for the different capacity levels + * @bat_type table of supported battery types + * @chg_params charger parameters + * @fg_params fuel gauge parameters + */ +struct abx500_bm_data { + int temp_under; + int temp_low; + int temp_high; + int temp_over; + int temp_now; + int main_safety_tmr_h; + int usb_safety_tmr_h; + int bkup_bat_v; + int bkup_bat_i; + bool no_maintenance; + bool chg_unknown_bat; + bool enable_overshoot; + enum abx500_adc_therm adc_therm; + int fg_res; + int n_btypes; + int batt_id; + int interval_charging; + int interval_not_charging; + int temp_hysteresis; + const struct abx500_maxim_parameters *maxi; + const struct abx500_bm_capacity_levels *cap_levels; + const struct abx500_battery_type *bat_type; + const struct abx500_bm_charger_parameters *chg_params; + const struct abx500_fg_parameters *fg_params; +}; + +struct abx500_chargalg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct abx500_charger_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct abx500_btemp_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct abx500_fg_platform_data { + char **supplied_to; + size_t num_supplicants; +}; + +struct abx500_bm_plat_data { + struct abx500_bm_data *battery; + struct abx500_charger_platform_data *charger; + struct abx500_btemp_platform_data *btemp; + struct abx500_fg_platform_data *fg; + struct abx500_chargalg_platform_data *chargalg; +}; + int abx500_register_ops(struct device *dev, struct abx500_ops *ops); void abx500_remove_ops(struct device *dev); #endif -- cgit v1.2.3 From 519442baf2aa9099c6ae40d2a948aa4fc701b90b Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 26 May 2011 16:47:51 +0530 Subject: power: ab5500-charger: ab5500 usb charger driver Adds a power supply class for usb, used to charge the battery. It includes enabling/disabling of usb charging and monitoring the usb. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: WP256401 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I4fa42b3dbe82103ad49293b784243dc0edb61558 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23149 Reviewed-by: QATEST Reviewed-by: Johan PALSSON Reviewed-by: Srinidhi KASAGAR --- drivers/power/ab5500_charger.c | 1837 ++++++++++++++++++++++++++++++++++ include/linux/mfd/abx500/ab5500-bm.h | 108 ++ 2 files changed, 1945 insertions(+) create mode 100644 drivers/power/ab5500_charger.c create mode 100644 include/linux/mfd/abx500/ab5500-bm.h diff --git a/drivers/power/ab5500_charger.c b/drivers/power/ab5500_charger.c new file mode 100644 index 00000000000..1b6bfa21d00 --- /dev/null +++ b/drivers/power/ab5500_charger.c @@ -0,0 +1,1837 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Charger driver for AB5500 + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson + * Karl Komierowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Charger constants */ +#define NO_PW_CONN 0 +#define USB_PW_CONN 2 + +/* HW failure constants */ +#define VBUS_CH_NOK 0x0A +#define VBUS_OVV_TH 0x06 + +/* AB5500 Charger constants */ +#define AB5500_USB_LINK_STATUS 0x78 +#define USB_CHARG_DET_ENA_MASK 0x01 +#define USB_CHARG_DET_ENA 0x01 +#define CHARGER_REV_SUP 0x10 +#define SW_EOC 0x40 +#define USB_CHAR_DET 0x02 +#define USB_CHAR_DET_DONE 0x02 +#define VBUS_RISING 0x20 +#define USB_LINK_UPDATE 0x02 +#define USB_CH_TH_PROT_LOW 0x02 +#define USB_CH_TH_PROT_HIGH 0x01 +#define USB_ID_HOST_DET_ENA_MASK 0x02 +#define USB_ID_HOST_DET_ENA 0x02 +#define USB_ID_DEVICE_DET_ENA_MASK 0x01 +#define USB_ID_DEVICE_DET_ENA 0x01 +#define CHARGER_ISET_IN_1_1A 0x0C +#define LED_ENABLE 0x01 +#define RESET 0x00 +#define SSW_ENABLE_REBOOT 0x80 +#define SSW_REBOOT_EN 0x40 +#define SSW_CONTROL_AUTOC 0x04 +#define SSW_PSEL_480S 0x00 + +/* UsbLineStatus register - usb types */ +enum ab5500_charger_link_status { + USB_STAT_NOT_CONFIGURED, + USB_STAT_STD_HOST_NC, + USB_STAT_STD_HOST_C_NS, + USB_STAT_STD_HOST_C_S, + USB_STAT_HOST_CHG_NM, + USB_STAT_HOST_CHG_HS, + USB_STAT_HOST_CHG_HS_CHIRP, + USB_STAT_DEDICATED_CHG, + USB_STAT_ACA_RID_A, + USB_STAT_ACA_RID_B, + USB_STAT_ACA_RID_C_NM, + USB_STAT_ACA_RID_C_HS, + USB_STAT_ACA_RID_C_HS_CHIRP, + USB_STAT_HM_IDGND, + USB_STAT_RESERVED, + USB_STAT_NOT_VALID_LINK, +}; + +enum ab5500_usb_state { + AB5500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */ + AB5500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */ + AB5500_BM_USB_STATE_CONFIGURED, + AB5500_BM_USB_STATE_SUSPEND, + AB5500_BM_USB_STATE_RESUME, + AB5500_BM_USB_STATE_MAX, +}; + +/* VBUS input current limits supported in AB5500 in mA */ +#define USB_CH_IP_CUR_LVL_0P05 50 +#define USB_CH_IP_CUR_LVL_0P09 98 +#define USB_CH_IP_CUR_LVL_0P19 193 +#define USB_CH_IP_CUR_LVL_0P29 290 +#define USB_CH_IP_CUR_LVL_0P38 380 +#define USB_CH_IP_CUR_LVL_0P45 450 +#define USB_CH_IP_CUR_LVL_0P5 500 +#define USB_CH_IP_CUR_LVL_0P6 600 +#define USB_CH_IP_CUR_LVL_0P7 700 +#define USB_CH_IP_CUR_LVL_0P8 800 +#define USB_CH_IP_CUR_LVL_0P9 900 +#define USB_CH_IP_CUR_LVL_1P0 1000 +#define USB_CH_IP_CUR_LVL_1P1 1100 +#define USB_CH_IP_CUR_LVL_1P3 1300 +#define USB_CH_IP_CUR_LVL_1P4 1400 +#define USB_CH_IP_CUR_LVL_1P5 1500 + +#define to_ab5500_charger_usb_device_info(x) container_of((x), \ + struct ab5500_charger, usb_chg) + +/** + * struct ab5500_charger_interrupts - ab5500 interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab5500_charger_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab5500_charger_info { + int charger_connected; + int charger_online; + int charger_voltage; + int cv_active; + bool wd_expired; +}; + +struct ab5500_charger_event_flags { + bool usb_thermal_prot; + bool vbus_ovv; + bool usbchargernotok; + bool vbus_collapse; +}; + +struct ab5500_charger_usb_state { + bool usb_changed; + int usb_current; + enum ab5500_usb_state state; + spinlock_t usb_lock; +}; + +/** + * struct ab5500_charger - ab5500 Charger device information + * @dev: Pointer to the structure device + * @chip_id: Chip-Id of the ab5500 + * @max_usb_in_curr: Max USB charger input current + * @vbus_detected: VBUS detected + * @vbus_detected_start: + * VBUS detected during startup + * @parent: Pointer to the struct ab5500 + * @gpadc: Pointer to the struct gpadc + * @pdata: Pointer to the ab5500_charger platform data + * @bat: Pointer to the ab5500_bm platform data + * @flags: Structure for information about events triggered + * @usb_state: Structure for usb stack information + * @usb_chg: USB charger power supply + * @ac: Structure that holds the AC charger properties + * @usb: Structure that holds the USB charger properties + * @charger_wq: Work queue for the IRQs and checking HW state + * @check_hw_failure_work: Work for checking HW state + * @check_usbchgnotok_work: Work for checking USB charger not ok status + * @ac_work: Work for checking AC charger connection + * @detect_usb_type_work: Work for detecting the USB type connected + * @usb_link_status_work: Work for checking the new USB link status + * @usb_state_changed_work: Work for checking USB state + * @check_main_thermal_prot_work: + * Work for checking Main thermal status + * @check_usb_thermal_prot_work: + * Work for checking USB thermal status + */ +struct ab5500_charger { + struct device *dev; + u8 chip_id; + int max_usb_in_curr; + bool vbus_detected; + bool vbus_detected_start; + struct ab5500 *parent; + struct ab5500_gpadc *gpadc; + struct abx500_charger_platform_data *pdata; + struct abx500_bm_data *bat; + struct ab5500_charger_event_flags flags; + struct ab5500_charger_usb_state usb_state; + struct ux500_charger usb_chg; + struct ab5500_charger_info usb; + struct workqueue_struct *charger_wq; + struct delayed_work check_hw_failure_work; + struct delayed_work check_usbchgnotok_work; + struct work_struct detect_usb_type_work; + struct work_struct usb_link_status_work; + struct work_struct usb_state_changed_work; + struct work_struct check_usb_thermal_prot_work; +}; + +/* + * TODO: This variable is static in order to get information + * about maximum current and USB state from the USB driver + * This should be solved in a better way + */ +static struct ab5500_charger *static_di; + +/* USB properties */ +static enum power_supply_property ab5500_charger_usb_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/** + * ab5500_charger_get_vbus_voltage() - get vbus voltage + * @di: pointer to the ab5500_charger structure + * + * This function returns the vbus voltage. + * Returns vbus voltage (on success) + */ +static int ab5500_charger_get_vbus_voltage(struct ab5500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->usb.charger_connected) { + vch = ab5500_gpadc_convert(di->gpadc, VBUS_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab5500_charger_get_usb_current() - get usb charger current + * @di: pointer to the ab5500_charger structure + * + * This function returns the usb charger current. + * Returns usb current (on success) and error code on failure + */ +static int ab5500_charger_get_usb_current(struct ab5500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->usb.charger_online) { + ich = ab5500_gpadc_convert(di->gpadc, USB_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab5500_charger_detect_chargers() - Detect the connected chargers + * @di: pointer to the ab5500_charger structure + * + * Returns the type of charger connected. + * For USB it will not mean we can actually charge from it + * but that there is a USB cable connected that we have to + * identify. This is used during startup when we don't get + * interrupts of the charger detection + * + * Returns an integer value, that means, + * NO_PW_CONN no power supply is connected + * USB_PW_CONN if the USB power supply is connected + */ +static int ab5500_charger_detect_chargers(struct ab5500_charger *di) +{ + int result = NO_PW_CONN; + int ret; + u8 val; + /* Check for USB charger */ + /* + * TODO: Since there are no status register validating by + * reading the IT souce registers + */ + ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_IT, + AB5500_IT_SOURCE8, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return ret; + } + + if (val & VBUS_RISING) + result |= USB_PW_CONN; + + ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_IT, + AB5500_IT_SOURCE9, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return ret; + } + + if (val & USB_CHAR_DET_DONE) + result |= USB_PW_CONN; + + return result; +} + +/** + * ab5500_charger_max_usb_curr() - get the max curr for the USB type + * @di: pointer to the ab5500_charger structure + * @link_status: the identified USB type + * + * Get the maximum current that is allowed to be drawn from the host + * based on the USB type. + * Returns error code in case of failure else 0 on success + */ +static int ab5500_charger_max_usb_curr(struct ab5500_charger *di, + enum ab5500_charger_link_status link_status) +{ + int ret = 0; + + switch (link_status) { + case USB_STAT_STD_HOST_NC: + case USB_STAT_STD_HOST_C_NS: + case USB_STAT_STD_HOST_C_S: + dev_dbg(di->dev, "USB Type - Standard host is " + "detected through USB driver\n"); + ret = -1; + break; + case USB_STAT_HOST_CHG_HS_CHIRP: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + case USB_STAT_HOST_CHG_HS: + case USB_STAT_ACA_RID_C_HS: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9; + break; + case USB_STAT_ACA_RID_A: + /* + * Dedicated charger level minus maximum current accessory + * can consume (300mA). Closest level is 1100mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P1; + break; + case USB_STAT_ACA_RID_B: + /* + * Dedicated charger level minus 120mA (20mA for ACA and + * 100mA for potential accessory). Closest level is 1300mA + */ + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3; + break; + case USB_STAT_DEDICATED_CHG: + case USB_STAT_HOST_CHG_NM: + case USB_STAT_ACA_RID_C_HS_CHIRP: + case USB_STAT_ACA_RID_C_NM: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5; + break; + case USB_STAT_RESERVED: + /* + * This state is used to indicate that VBUS has dropped below + * the detection level 4 times in a row. This is due to the + * charger output current is set to high making the charger + * voltage collapse. This have to be propagated through to + * chargalg. This is done using the property + * POWER_SUPPLY_PROP_CURRENT_AVG = 1 + */ + di->flags.vbus_collapse = true; + dev_dbg(di->dev, "USB Type - USB_STAT_RESERVED " + "VBUS has collapsed\n"); + ret = -1; + break; + case USB_STAT_HM_IDGND: + case USB_STAT_NOT_CONFIGURED: + case USB_STAT_NOT_VALID_LINK: + dev_err(di->dev, "USB Type - Charging not allowed\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + default: + dev_err(di->dev, "USB Type - Unknown\n"); + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + }; + + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", + link_status, di->max_usb_in_curr); + + return ret; +} + +/** + * ab5500_charger_read_usb_type() - read the type of usb connected + * @di: pointer to the ab5500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab5500_charger_read_usb_type(struct ab5500_charger *di) +{ + int ret; + u8 val; + + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_IT, AB5500_IT_SOURCE22_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return ret; + } + ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_USB, + AB5500_USB_LINE_STATUS, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return ret; + } + + /* get the USB type */ + val = (val & AB5500_USB_LINK_STATUS) >> 3; + ret = ab5500_charger_max_usb_curr(di, + (enum ab5500_charger_link_status) val); + + return ret; +} + +/** + * ab5500_charger_detect_usb_type() - get the type of usb connected + * @di: pointer to the ab5500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab5500_charger_detect_usb_type(struct ab5500_charger *di) +{ + int i, ret; + u8 val; + + /* + * On getting the VBUS rising edge detect interrupt there + * is a 250ms delay after which the register UsbLineStatus + * is filled with valid data. + */ + for (i = 0; i < 10; i++) { + msleep(250); + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_IT, AB5500_IT_SOURCE22_REG, + &val); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return ret; + } + if (!(val & USB_LINK_UPDATE)) + continue; + + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_USB_LINE_STATUS, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return ret; + } + /* + * Until the IT source register is read the UsbLineStatus + * register is not updated, hence doing the same + * Revisit this: + */ + + /* get the USB type */ + val = (val & AB5500_USB_LINK_STATUS) >> 3; + if (val) { + dev_dbg(di->dev, "values = %d\n", val); + break; + } + } + ret = ab5500_charger_max_usb_curr(di, + (enum ab5500_charger_link_status) val); + + return ret; +} + +static int ab5500_charger_voltage_map[] = { + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3875 , + 3900 , + 3925 , + 3950 , + 3975 , + 4000 , + 4025 , + 4050 , + 4060 , + 4070 , + 4080 , + 4090 , + 4100 , + 4110 , + 4120 , + 4130 , + 4140 , + 4150 , + 4160 , + 4170 , + 4180 , + 4190 , + 4200 , + 4210 , + 4220 , + 4230 , + 4240 , + 4250 , + 4260 , + 4270 , + 4280 , + 4290 , + 4300 , + 4310 , + 4320 , + 4330 , + 4340 , + 4350 , + 4360 , + 4370 , + 4380 , + 4390 , + 4400 , + 4410 , + 4420 , + 4430 , + 4440 , + 4450 , + 4460 , + 4470 , + 4480 , + 4490 , + 4500 , + 4510 , + 4520 , + 4530 , + 4540 , + 4550 , + 4560 , + 4570 , + 4580 , + 4590 , + 4600 , +}; + +/* + * This array maps the raw hex value to charger current used by the ab5500 + * Values taken from the AB5500 product specification manual + */ +static int ab5500_charger_current_map[] = { + 100 , + 200 , + 300 , + 400 , + 500 , + 600 , + 700 , + 800 , + 900 , + 1000, + 1100, + 1200, + 1300, + 1400, + 1500, + 1500, +}; + +static int ab5500_icsr_current_map[] = { + 50, + 93, + 193, + 290, + 380, + 450, + 500 , + 600 , + 700 , + 800 , + 900 , + 1000, + 1100, + 1300, + 1400, + 1500, +}; + +static int ab5500_cvrec_voltage_map[] = { + 3300, + 3325, + 3350, + 3375, + 3400, + 3425, + 3450, + 3475, + 3500, + 3525, + 3550, + 3575, + 3600, + 3625, + 3650, + 3675, + 3700, + 3725, + 3750, + 3775, + 3800, + 3825, + 3850, + 3875, + 3900, + 3925, + 4000, + 4025, + 4050, + 4075, + 4100, + 4125, + 4150, + 4175, + 4200, + 4225, + 4250, + 4275, + 4300, + 4325, + 4350, + 4375, + 4400, + 4425, + 4450, + 4475, + 4500, + 4525, + 4550, + 4575, + 4600, +}; + +static int ab5500_cvrec_voltage_to_regval(int voltage) +{ + int i; + + /* Special case for voltage below 3.3V */ + if (voltage < ab5500_cvrec_voltage_map[0]) + return 0; + + for (i = 1; i < ARRAY_SIZE(ab5500_cvrec_voltage_map); i++) { + if (voltage < ab5500_cvrec_voltage_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab5500_cvrec_voltage_map) - 1; + if (voltage == ab5500_cvrec_voltage_map[i]) + return i; + else + return -1; +} + +static int ab5500_voltage_to_regval(int voltage) +{ + int i; + + /* Special case for voltage below 3.3V */ + if (voltage < ab5500_charger_voltage_map[0]) + return 0; + + for (i = 1; i < ARRAY_SIZE(ab5500_charger_voltage_map); i++) { + if (voltage < ab5500_charger_voltage_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab5500_charger_voltage_map) - 1; + if (voltage == ab5500_charger_voltage_map[i]) + return i; + else + return -1; +} + +static int ab5500_icsr_curr_to_regval(int curr) +{ + int i; + + if (curr < ab5500_icsr_current_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab5500_icsr_current_map); i++) { + if (curr < ab5500_icsr_current_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab5500_icsr_current_map) - 1; + if (curr == ab5500_icsr_current_map[i]) + return i; + else + return -1; +} + +static int ab5500_current_to_regval(int curr) +{ + int i; + + if (curr < ab5500_charger_current_map[0]) + return 0; + + for (i = 0; i < ARRAY_SIZE(ab5500_charger_current_map); i++) { + if (curr < ab5500_charger_current_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab5500_charger_current_map) - 1; + if (curr == ab5500_charger_current_map[i]) + return i; + else + return -1; +} + +/** + * ab5500_charger_get_usb_cur() - get usb current + * @di: pointer to the ab5500_charger structre + * + * The usb stack provides the maximum current that can be drawn from + * the standard usb host. This will be in mA. + * This function converts current in mA to a value that can be written + * to the register. Returns -1 if charging is not allowed + */ +static int ab5500_charger_get_usb_cur(struct ab5500_charger *di) +{ + switch (di->usb_state.usb_current) { + case 50: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + break; + case 100: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09; + break; + case 200: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P19; + break; + case 300: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P29; + break; + case 400: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P38; + break; + case 500: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + break; + default: + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + return -1; + break; + }; + return 0; +} + +/** + * ab5500_charger_set_vbus_in_curr() - set VBUS input current limit + * @di: pointer to the ab5500_charger structure + * @ich_in: charger input current limit + * + * Sets the current that can be drawn from the USB host + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_charger_set_vbus_in_curr(struct ab5500_charger *di, + int ich_in) +{ + int ret; + int input_curr_index; + int min_value; + + /* We should always use to lowest current limit */ + min_value = min(di->bat->chg_params->usb_curr_max, ich_in); + + input_curr_index = ab5500_icsr_curr_to_regval(min_value); + if (input_curr_index < 0) { + dev_err(di->dev, "VBUS input current limit too high\n"); + return -ENXIO; + } + + ret = abx500_set_register_interruptible(di->dev, AB5500_BANK_CHG, + AB5500_ICSR, input_curr_index); + if (ret) + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + + return ret; +} + +/** + * ab5500_charger_usb_en() - enable usb charging + * @di: pointer to the ab5500_charger structure + * @enable: enable/disable flag + * @vset: charging voltage + * @ich_out: charger output current + * + * Enable/Disable USB charging and turns on/off the charging led respectively. + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_charger_usb_en(struct ux500_charger *charger, + int enable, int vset, int ich_out) +{ + int ret; + int volt_index; + int curr_index; + + struct ab5500_charger *di = to_ab5500_charger_usb_device_info(charger); + + if (enable) { + /* Check if USB is connected */ + if (!di->usb.charger_connected) { + dev_err(di->dev, "USB charger not connected\n"); + return -ENXIO; + } + + /* Enable USB charging */ + dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out); + + volt_index = ab5500_voltage_to_regval(vset); + curr_index = ab5500_current_to_regval(ich_out) ; + + /* ChVoltLevel: max voltage upto which battery can be charged */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_VSRC, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + + /* current that can be drawn from the usb */ + ret = ab5500_charger_set_vbus_in_curr(di, ich_out); + if (ret) { + dev_err(di->dev, "%s setting icsr failed %d\n", + __func__, __LINE__); + return ret; + } + + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_OCSRV, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + + /* + * Battery voltage when charging should be resumed after + * completion of charging + */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_CVREC, + ab5500_cvrec_voltage_to_regval( + di->bat->bat_type[di->bat->batt_id].recharge_vol)); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + /* + * Battery temperature: + * Input to the TBDATA register corresponds to the battery + * temperature(temp being multiples of 2) + * In order to obatain the value to be written to this reg + * divide the temperature obtained from gpadc by 2 + */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_TBDATA, + di->bat->temp_now / 2); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + + /* If success power on charging LED indication */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_LEDT, LED_ENABLE); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + di->usb.charger_online = 1; + } else { + /* ChVoltLevel: max voltage upto which battery can be charged */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_VSRC, RESET); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + /* USBChInputCurr: current that can be drawn from the usb */ + ret = ab5500_charger_set_vbus_in_curr(di, RESET); + if (ret) { + dev_err(di->dev, "%s resetting icsr failed %d\n", + __func__, __LINE__); + return ret; + } + /* If success power off charging LED indication */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_LEDT, RESET); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + di->usb.charger_online = 0; + di->usb.wd_expired = false; + dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); + } + power_supply_changed(&di->usb_chg.psy); + + return ret; +} + +/** + * ab5500_charger_watchdog_kick() - kick charger watchdog + * @di: pointer to the ab5500_charger structure + * + * Kick charger watchdog + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_charger_watchdog_kick(struct ux500_charger *charger) +{ + int ret; + struct ab5500_charger *di; + int volt_index, curr_index; + u8 value = 0; + + /* TODO: update */ + if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab5500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_STARTUP, + AB5500_MCB, &value); + if (ret) + dev_err(di->dev, "Failed to read!\n"); + + value = value | (SSW_ENABLE_REBOOT | SSW_REBOOT_EN | + SSW_CONTROL_AUTOC | SSW_PSEL_480S); + ret = abx500_set_register_interruptible(di->dev, AB5500_BANK_STARTUP, + AB5500_MCB, value); + if (ret) + dev_err(di->dev, "Failed to kick WD!\n"); + + volt_index = ab5500_voltage_to_regval( + di->bat->bat_type[di->bat->batt_id].normal_vol_lvl); + curr_index = ab5500_current_to_regval(di->max_usb_in_curr); + + /* ChVoltLevel: max voltage upto which battery can be charged */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_VSRC, (u8) volt_index); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + + /* current that can be drawn from the usb */ + ret = ab5500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); + if (ret) { + dev_err(di->dev, "%s setting icsr failed %d\n", + __func__, __LINE__); + return ret; + } + + /* ChOutputCurentLevel: protected output current */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_OCSRV, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + + /* + * Battery voltage when charging should be resumed after + * completion of charging + */ + /* Charger_Vrechar[5:0] = '4.025 V' */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_CVREC, + ab5500_cvrec_voltage_to_regval( + di->bat->bat_type[di->bat->batt_id].recharge_vol)); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + /* + * Battery temperature: + * Input to the TBDATA register corresponds to the battery + * temperature(temp being multiples of 2) + * In order to obatain the value to be written to this reg + * divide the temperature obtained from gpadc by 2 + */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_TBDATA, + di->bat->temp_now / 2); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + return ret; +} + +/** + * ab5500_charger_update_charger_current() - update charger current + * @di: pointer to the ab5500_charger structure + * + * Update the charger output current for the specified charger + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_charger_update_charger_current(struct ux500_charger *charger, + int ich_out) +{ + int ret = 0; + int curr_index; + struct ab5500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab5500_charger_usb_device_info(charger); + else + return -ENXIO; + + curr_index = ab5500_current_to_regval(ich_out); + if (curr_index < 0) { + dev_err(di->dev, + "Charger current too high, " + "charging not started\n"); + return -ENXIO; + } + + ret = abx500_set_register_interruptible(di->dev, AB5500_BANK_CHG, + AB5500_OCSRV, (u8) curr_index); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + + return ret; +} + +/** + * ab5500_charger_check_hw_failure_work() - check main charger failure + * @work: pointer to the work_struct structure + * + * Work queue function for checking the main charger status + */ +static void ab5500_charger_check_hw_failure_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, check_hw_failure_work.work); + + /* Check if the status bits for HW failure is still active */ + if (di->flags.vbus_ovv) { + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_USB_PHY_STATUS, + ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return; + } + if (!(reg_value & VBUS_OVV_TH)) { + di->flags.vbus_ovv = false; + power_supply_changed(&di->usb_chg.psy); + } + } + /* If we still have a failure, schedule a new check */ + if (di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, round_jiffies(HZ)); + } +} + +/** + * ab5500_charger_detect_usb_type_work() - work to detect USB type + * @work: Pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +void ab5500_charger_detect_usb_type_work(struct work_struct *work) +{ + int ret; + + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, detect_usb_type_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab5500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } else { + di->vbus_detected = 1; + + ret = ab5500_charger_detect_usb_type(di); + if (!ret) { + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + + } +} + +/** + * ab5500_charger_usb_link_status_work() - work to detect USB type + * @work: pointer to the work_struct structure + * + * Detect the type of USB plugged + */ +static void ab5500_charger_usb_link_status_work(struct work_struct *work) +{ + int ret; + + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, usb_link_status_work); + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if is + * connected by reading the status register + */ + ret = ab5500_charger_detect_chargers(di); + if (ret < 0) + return; + + if (!(ret & USB_PW_CONN)) { + di->vbus_detected = 0; + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } else { + di->vbus_detected = 1; + ret = ab5500_charger_read_usb_type(di); + if (!ret) { + /* Update maximum input current */ + ret = ab5500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr); + if (ret) + return; + + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } else if (ret == -ENXIO) { + /* No valid charger type detected */ + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + } + } +} + +static void ab5500_charger_usb_state_changed_work(struct work_struct *work) +{ + int ret; + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, usb_state_changed_work); + + if (!di->vbus_detected) + return; + + spin_lock(&di->usb_state.usb_lock); + di->usb_state.usb_changed = false; + spin_unlock(&di->usb_state.usb_lock); + + /* + * wait for some time until you get updates from the usb stack + * and negotiations are completed + */ + msleep(250); + + if (di->usb_state.usb_changed) + return; + + dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n", + __func__, di->usb_state.state, di->usb_state.usb_current); + + switch (di->usb_state.state) { + case AB5500_BM_USB_STATE_RESET_HS: + case AB5500_BM_USB_STATE_RESET_FS: + case AB5500_BM_USB_STATE_SUSPEND: + case AB5500_BM_USB_STATE_MAX: + di->usb.charger_connected = 0; + power_supply_changed(&di->usb_chg.psy); + break; + + case AB5500_BM_USB_STATE_RESUME: + /* + * when suspend->resume there should be delay + * of 1sec for enabling charging + */ + msleep(1000); + /* Intentional fall through */ + case AB5500_BM_USB_STATE_CONFIGURED: + /* + * USB is configured, enable charging with the charging + * input current obtained from USB driver + */ + if (!ab5500_charger_get_usb_cur(di)) { + /* Update maximum input current */ + ret = ab5500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr); + if (ret) + return; + + di->usb.charger_connected = 1; + power_supply_changed(&di->usb_chg.psy); + } + break; + + default: + break; + }; +} + +/** + * ab5500_charger_check_usbchargernotok_work() - check USB chg not ok status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB charger Not OK status + */ +static void ab5500_charger_check_usbchargernotok_work(struct work_struct *work) +{ + int ret; + u8 reg_value; + bool prev_status; + + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, check_usbchgnotok_work.work); + + /* Check if the status bit for usbchargernotok is still active */ + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_CHGFSM_CHARGER_DETECT, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return; + } + prev_status = di->flags.usbchargernotok; + + if (reg_value & VBUS_CH_NOK) { + di->flags.usbchargernotok = true; + /* Check again in 1sec */ + queue_delayed_work(di->charger_wq, + &di->check_usbchgnotok_work, HZ); + } else { + di->flags.usbchargernotok = false; + di->flags.vbus_collapse = false; + } + + if (prev_status != di->flags.usbchargernotok) + power_supply_changed(&di->usb_chg.psy); +} + +/** + * ab5500_charger_check_usb_thermal_prot_work() - check usb thermal status + * @work: pointer to the work_struct structure + * + * Work queue function for checking the USB thermal prot status + */ +static void ab5500_charger_check_usb_thermal_prot_work( + struct work_struct *work) +{ + int ret; + u8 reg_value; + + struct ab5500_charger *di = container_of(work, + struct ab5500_charger, check_usb_thermal_prot_work); + + /* Check if the status bit for usb_thermal_prot is still active */ + /* TODO: Interrupt source reg 15 bit 4 */ + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_CHGFSM_USB_BTEMP_CURR_LIM, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return; + } + if (reg_value & USB_CH_TH_PROT_LOW || reg_value & USB_CH_TH_PROT_HIGH) + di->flags.usb_thermal_prot = true; + else + di->flags.usb_thermal_prot = false; + + power_supply_changed(&di->usb_chg.psy); +} + +/** + * ab5500_charger_vbusdetf_handler() - VBUS falling detected + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_vbusdetf_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, "VBUS falling detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_vbusdetr_handler() - VBUS rising detected + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_vbusdetr_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + di->vbus_detected = true; + dev_dbg(di->dev, "VBUS rising detected\n"); + queue_work(di->charger_wq, &di->detect_usb_type_work); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_usblinkstatus_handler() - USB link status has changed + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_usblinkstatus_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, "USB link status changed\n"); + + queue_work(di->charger_wq, &di->usb_link_status_work); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_usbchthprotr_handler() - Die temp is above usb charger + * thermal protection threshold + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_usbchthprotr_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, + "Die temp above USB charger thermal protection threshold\n"); + queue_work(di->charger_wq, &di->check_usb_thermal_prot_work); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_usbchargernotokr_handler() - USB charger not ok detected + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_usbchargernotokr_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, "Not allowed USB charger detected\n"); + queue_delayed_work(di->charger_wq, &di->check_usbchgnotok_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_chwdexp_handler() - Charger watchdog expired + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_chwdexp_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, "Charger watchdog expired\n"); + + /* + * The charger that was online when the watchdog expired + * needs to be restarted for charging to start again + */ + if (di->usb.charger_online) { + di->usb.wd_expired = true; + power_supply_changed(&di->usb_chg.psy); + } + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_vbusovv_handler() - VBUS overvoltage detected + * @irq: interrupt number + * @_di: pointer to the ab5500_charger structure + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_charger_vbusovv_handler(int irq, void *_di) +{ + struct ab5500_charger *di = _di; + + dev_dbg(di->dev, "VBUS overvoltage detected\n"); + di->flags.vbus_ovv = true; + power_supply_changed(&di->usb_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0); + + return IRQ_HANDLED; +} + +/** + * ab5500_charger_usb_get_property() - get the usb properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the usb + * properties by reading the sysfs files. + * USB properties are online, present and voltage. + * online: usb charging is in progress or not + * present: presence of the usb + * voltage: vbus voltage + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_charger_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab5500_charger *di; + + di = to_ab5500_charger_usb_device_info(psy_to_ux500_charger(psy)); + + switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + if (di->flags.usbchargernotok) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (di->usb.wd_expired) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else if (di->flags.usb_thermal_prot) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (di->flags.vbus_ovv) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = di->usb.charger_online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = di->usb.charger_connected; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + di->usb.charger_voltage = ab5500_charger_get_vbus_voltage(di); + val->intval = di->usb.charger_voltage * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = ab5500_charger_get_usb_current(di) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + /* + * This property is used to indicate when VBUS has collapsed + * due to too high output current from the USB charger + */ + if (di->flags.vbus_collapse) + val->intval = 1; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +/** + * ab5500_charger_hw_registers() - Set up charger related registers + * @di: pointer to the ab5500_charger structure + * + * Set up charger OVV, watchdog and maximum voltage registers as well as + * charging of the backup battery + */ +static int ab5500_charger_init_hw_registers(struct ab5500_charger *di) +{ + int ret = 0; + + /* Enable ID Host and Device detection */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_USB_OTG_CTRL, + USB_ID_HOST_DET_ENA_MASK, USB_ID_HOST_DET_ENA); + if (ret) { + dev_err(di->dev, "failed to enable usb charger detection\n"); + goto out; + } + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_USB_OTG_CTRL, + USB_ID_DEVICE_DET_ENA_MASK, USB_ID_DEVICE_DET_ENA); + if (ret) { + dev_err(di->dev, "failed to enable usb charger detection\n"); + goto out; + } + + /* Enable USB Charger Detection */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_USB_LINE_CTRL2, + USB_CHARG_DET_ENA_MASK, USB_CHARG_DET_ENA); + if (ret) { + dev_err(di->dev, "failed to enable usb charger detection\n"); + goto out; + } + + /* Over current protection for reverse supply */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_CREVS, CHARGER_REV_SUP, + CHARGER_REV_SUP); + if (ret) { + dev_err(di->dev, + "failed to enable over current protection for reverse supply\n"); + goto out; + } + + /* Enable SW EOC at flatcurrent detection */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_CCTRL, SW_EOC, SW_EOC); + if (ret) { + dev_err(di->dev, + "failed to enable end of charge at flatcurrent detection\n"); + goto out; + } +out: + return ret; +} + +/* + * ab5500 charger driver interrupts and their respective isr + */ +static struct ab5500_charger_interrupts ab5500_charger_irq[] = { + {"VBUS_FALLING", ab5500_charger_vbusdetf_handler}, + {"VBUS_RISING", ab5500_charger_vbusdetr_handler}, + {"USB_LINK_UPDATE", ab5500_charger_usblinkstatus_handler}, + {"USB_CH_TH_PROTECTION", ab5500_charger_usbchthprotr_handler}, + {"USB_CH_NOT_OK", ab5500_charger_usbchargernotokr_handler}, + {"OVV", ab5500_charger_vbusovv_handler}, + /* TODO: Interrupt missing, will be available in cut 2 */ + /*{"CHG_SW_TIMER_OUT", ab5500_charger_chwdexp_handler},*/ +}; + +void ab5500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) +{ + struct ab5500_charger *di = static_di; + + dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n", + __func__, bm_usb_state, mA); + + spin_lock(&di->usb_state.usb_lock); + di->usb_state.usb_changed = true; + spin_unlock(&di->usb_state.usb_lock); + + di->usb_state.state = bm_usb_state; + di->usb_state.usb_current = mA; + + queue_work(di->charger_wq, &di->usb_state_changed_work); + + return; +} +EXPORT_SYMBOL(ab5500_charger_usb_state_changed); + +#if defined(CONFIG_PM) +static int ab5500_charger_resume(struct platform_device *pdev) +{ + struct ab5500_charger *di = platform_get_drvdata(pdev); + + /* If we still have a HW failure, schedule a new check */ + if (di->flags.usbchargernotok || di->flags.vbus_ovv) { + queue_delayed_work(di->charger_wq, + &di->check_hw_failure_work, 0); + } + + return 0; +} + +static int ab5500_charger_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab5500_charger *di = platform_get_drvdata(pdev); + + /* Cancel any pending HW failure check */ + if (delayed_work_pending(&di->check_hw_failure_work)) + cancel_delayed_work(&di->check_hw_failure_work); + + return 0; +} +#else +#define ab5500_charger_suspend NULL +#define ab5500_charger_resume NULL +#endif + +static int __devexit ab5500_charger_remove(struct platform_device *pdev) +{ + struct ab5500_charger *di = platform_get_drvdata(pdev); + int i, irq; + + /* Disable USB charging */ + ab5500_charger_usb_en(&di->usb_chg, false, 0, 0); + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_charger_irq[i].name); + free_irq(irq, di); + } + + /* Delete the work queue */ + destroy_workqueue(di->charger_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->usb_chg.psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit ab5500_charger_probe(struct platform_device *pdev) +{ + int irq, i, charger_status, ret = 0; + struct abx500_bm_plat_data *plat_data; + + struct ab5500_charger *di = + kzalloc(sizeof(struct ab5500_charger), GFP_KERNEL); + if (!di) + return -ENOMEM; + + static_di = di; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab5500_gpadc_get("ab5500-adc.0"); + + /* initialize lock */ + spin_lock_init(&di->usb_state.usb_lock); + + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->charger; + di->bat = plat_data->battery; + + /* get charger specific platform data */ + if (!di->pdata) { + dev_err(di->dev, "no charger platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* get battery specific platform data */ + if (!di->bat) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + /* USB supply */ + /* power_supply base class */ + di->usb_chg.psy.name = "ab5500_usb"; + di->usb_chg.psy.type = POWER_SUPPLY_TYPE_USB; + di->usb_chg.psy.properties = ab5500_charger_usb_props; + di->usb_chg.psy.num_properties = ARRAY_SIZE(ab5500_charger_usb_props); + di->usb_chg.psy.get_property = ab5500_charger_usb_get_property; + di->usb_chg.psy.supplied_to = di->pdata->supplied_to; + di->usb_chg.psy.num_supplicants = di->pdata->num_supplicants; + /* ux500_charger sub-class */ + di->usb_chg.ops.enable = &ab5500_charger_usb_en; + di->usb_chg.ops.kick_wd = &ab5500_charger_watchdog_kick; + di->usb_chg.ops.update_curr = &ab5500_charger_update_charger_current; + di->usb_chg.max_out_volt = ab5500_charger_voltage_map[ + ARRAY_SIZE(ab5500_charger_voltage_map) - 1]; + di->usb_chg.max_out_curr = ab5500_charger_current_map[ + ARRAY_SIZE(ab5500_charger_current_map) - 1]; + + + /* Create a work queue for the charger */ + di->charger_wq = + create_singlethread_workqueue("ab5500_charger_wq"); + if (di->charger_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for HW failure check */ + INIT_DELAYED_WORK_DEFERRABLE(&di->check_hw_failure_work, + ab5500_charger_check_hw_failure_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->check_usbchgnotok_work, + ab5500_charger_check_usbchargernotok_work); + + /* Init work for charger detection */ + INIT_WORK(&di->usb_link_status_work, + ab5500_charger_usb_link_status_work); + INIT_WORK(&di->detect_usb_type_work, + ab5500_charger_detect_usb_type_work); + + INIT_WORK(&di->usb_state_changed_work, + ab5500_charger_usb_state_changed_work); + + /* Init work for checking HW status */ + INIT_WORK(&di->check_usb_thermal_prot_work, + ab5500_charger_check_usb_thermal_prot_work); + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(di->dev); + if (ret < 0) { + dev_err(di->dev, "failed to get chip ID\n"); + goto free_charger_wq; + } + di->chip_id = ret; + dev_dbg(di->dev, "AB5500 CID is: 0x%02x\n", di->chip_id); + + /* Initialize OVV, and other registers */ + ret = ab5500_charger_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize ABB registers\n"); + goto free_device_info; + } + + /* Register USB charger class */ + ret = power_supply_register(di->dev, &di->usb_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register USB charger\n"); + goto free_device_info; + } + + /* Identify the connected charger types during startup */ + charger_status = ab5500_charger_detect_chargers(di); + if (charger_status & USB_PW_CONN) { + dev_dbg(di->dev, "VBUS Detect during startup\n"); + di->vbus_detected = true; + di->vbus_detected_start = true; + queue_work(di->charger_wq, + &di->detect_usb_type_work); + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_charger_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_charger_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab5500_charger_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab5500_charger_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab5500_charger_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab5500_charger_irq[i].name, irq, ret); + } + + platform_set_drvdata(pdev, di); + + dev_info(di->dev, "probe success\n"); + return ret; + +free_irq: + power_supply_unregister(&di->usb_chg.psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab5500_charger_irq[i].name); + free_irq(irq, di); + } +free_charger_wq: + destroy_workqueue(di->charger_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab5500_charger_driver = { + .probe = ab5500_charger_probe, + .remove = __devexit_p(ab5500_charger_remove), + .suspend = ab5500_charger_suspend, + .resume = ab5500_charger_resume, + .driver = { + .name = "ab5500-charger", + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_charger_init(void) +{ + return platform_driver_register(&ab5500_charger_driver); +} + +static void __exit ab5500_charger_exit(void) +{ + platform_driver_unregister(&ab5500_charger_driver); +} + +subsys_initcall_sync(ab5500_charger_init); +module_exit(ab5500_charger_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab5500-charger"); +MODULE_DESCRIPTION("AB5500 charger management driver"); diff --git a/include/linux/mfd/abx500/ab5500-bm.h b/include/linux/mfd/abx500/ab5500-bm.h new file mode 100644 index 00000000000..b9800cd8c19 --- /dev/null +++ b/include/linux/mfd/abx500/ab5500-bm.h @@ -0,0 +1,108 @@ +/* + * Copyright ST-Ericsson 2011. + * + * Author: Arun Murthy + * Licensed under GPLv2. + */ + +#ifndef _AB5500_BM_H +#define _AB5500_BM_H + +#define AB5500_MCB 0x2F +/* + * USB/ULPI register offsets + * Bank : 0x5 + */ +#define AB5500_USB_LINE_STATUS 0x80 +#define AB5500_USB_PHY_STATUS 0x89 +#define AB5500_CHGFSM_CHARGER_DETECT 0xBF +#define AB5500_CHGFSM_USB_BTEMP_CURR_LIM 0xAD +#define AB5500_USB_LINE_CTRL2 0x82 +#define AB5500_USB_OTG_CTRL 0x87 + +/* + * Charger / control register offfsets + * Bank : 0x0B + */ +#define AB5500_CVBUSM 0x11 +#define AB5500_LEDT 0x12 +#define AB5500_VSRC 0x13 +#define AB5500_ICSR 0x14 +#define AB5500_OCSRV 0x15 +#define AB5500_CVREC 0x16 +#define AB5500_CREVS 0x17 +#define AB5500_CCTRL 0x18 +#define AB5500_TBDATA 0x19 +#define AB5500_CPWM 0x1A +#define AB5500_DCIOCURRENT 0x1B +#define AB5500_USB_HS_CURR_LIM 0x1C +#define AB5500_WALL_HS_CURR_LIM 0x1D + +/* + * FG, Battcom and ACC registers offsets + * Bank : 0x0C + */ +#define AB5500_FG_CH0 0x00 +#define AB5500_FG_CH1 0x01 +#define AB5500_FG_CH2 0x02 +#define AB5500_FG_DIS_CH0 0x03 +#define AB5500_FG_DIS_CH1 0x04 +#define AB5500_FG_DIS_CH2 0x05 +#define AB5500_FGDIS_COUNT0 0x06 +#define AB5500_FGDIS_COUNT1 0x07 +#define AB5500_FG_VAL_COUNT0 0x08 +#define AB5500_FG_VAL_COUNT1 0x09 +#define AB5500_FGDIR_READ0 0x0A +#define AB5500_FGDIR_READ1 0x0B +#define AB5500_FG_CONTROL_A 0x0C +#define AB5500_FG_CONTROL_B 0x0F +#define AB5500_FG_CONTROL_C 0x10 +#define AB5500_FG_DIS 0x0D +#define AB5500_FG_EOC 0x0E +#define AB5500_FG_CB 0x0F +#define AB5500_FG_CC 0x10 +#define AB5500_UIOR 0x1A +#define AB5500_UART 0x1B +#define AB5500_URI 0x1C +#define AB5500_UART_RQ 0x1D +#define AB5500_ACC_DETECT1 0x20 +#define AB5500_ACC_DETECT2 0x21 +#define AB5500_ACC_DETECTCTRL 0x23 +#define AB5500_ACC_AVCTRL 0x24 +#define AB5500_ACC_DETECT3_DEG_LITCH_TIME 0x30 +#define AB5500_ACC_DETECT3_KEY_PRESS_TIME 0x31 +#define AB5500_ACC_DETECT3_LONG_KEY_TIME 0x32 +#define AB5500_ACC_DETECT3_TIME_READ_MS 0x33 +#define AB5500_ACC_DETECT3_TIME_READ_LS 0x34 +#define AB5500_ACC_DETECT3_CONTROL 0x35 +#define AB5500_ACC_DETECT3_LEVEL 0x36 +#define AB5500_ACC_DETECT3_TIMER_READ_CTL 0x37 + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB5500_IT_SOURCE8 0x28 +#define AB5500_IT_SOURCE9 0x29 + +/* BatCtrl Current Source Constants */ +#define BAT_CTRL_7U_ENA (0x01 << 0) +#define BAT_CTRL_15U_ENA (0x01 << 1) +#define BAT_CTRL_30U_ENA (0x01 << 2) +#define BAT_CTRL_60U_ENA (0x01 << 3) +#define BAT_CTRL_120U_ENA (0x01 << 4) +#define BAT_CTRL_CMP_ENA 0x04 +#define FORCE_BAT_CTRL_CMP_HIGH 0x08 +#define BAT_CTRL_PULL_UP_ENA 0x10 + +/* Battery type */ +#define BATTERY_UNKNOWN 0 + +#ifdef CONFIG_AB5500_BM +void ab5500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); +#else +static void ab5500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) +{ +} +#endif +#endif /* _AB5500_BM_H */ -- cgit v1.2.3 From c88d0f44a09ce0c40e16eda44083c64b218cc01b Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 26 May 2011 16:48:47 +0530 Subject: power: abx500-chargalg: charging algorithm A generic charging algorithm for all abx500 varants. It is a high level algorithm and hence doesn't include any low level or hardware interaction. Each of the abx500 charging drivers will implement low level hardware interaction function and provide inputs to this generic charging algorithm to monitor and implement the charging functionality. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: WP256401 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I8634160b2e67132ae68a09fda7a9fd9d36dd23d2 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23150 Reviewed-by: QATEST Reviewed-by: Johan PALSSON Reviewed-by: Srinidhi KASAGAR --- drivers/power/abx500_chargalg.c | 1921 +++++++++++++++++++++++++++++ include/linux/mfd/abx500/ux500_chargalg.h | 38 + 2 files changed, 1959 insertions(+) create mode 100644 drivers/power/abx500_chargalg.c create mode 100644 include/linux/mfd/abx500/ux500_chargalg.h diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c new file mode 100644 index 00000000000..c85e284e04a --- /dev/null +++ b/drivers/power/abx500_chargalg.c @@ -0,0 +1,1921 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Charging algorithm driver for abx500 variants + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson + * Karl Komierowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Watchdog kick interval */ +#define CHG_WD_INTERVAL (6 * HZ) + +/* End-of-charge criteria counter */ +#define EOC_COND_CNT 10 + +/* Recharge criteria counter */ +#define RCH_COND_CNT 3 + +#define to_abx500_chargalg_device_info(x) container_of((x), \ + struct abx500_chargalg, chargalg_psy); + +enum abx500_chargers { + NO_CHG, + AC_CHG, + USB_CHG, +}; + +struct abx500_chargalg_charger_info { + enum abx500_chargers conn_chg; + enum abx500_chargers prev_conn_chg; + enum abx500_chargers online_chg; + enum abx500_chargers prev_online_chg; + enum abx500_chargers charger_type; + bool usb_chg_ok; + bool ac_chg_ok; + int usb_volt; + int usb_curr; + int ac_volt; + int ac_curr; + int usb_vset; + int usb_iset; + int ac_vset; + int ac_iset; +}; + +struct abx500_chargalg_suspension_status { + bool suspended_change; + bool ac_suspended; + bool usb_suspended; +}; + +struct abx500_chargalg_battery_data { + int temp; + int volt; + int avg_curr; + int inst_curr; + int percent; +}; + +enum abx500_chargalg_states { + STATE_HANDHELD_INIT, + STATE_HANDHELD, + STATE_CHG_NOT_OK_INIT, + STATE_CHG_NOT_OK, + STATE_HW_TEMP_PROTECT_INIT, + STATE_HW_TEMP_PROTECT, + STATE_NORMAL_INIT, + STATE_NORMAL, + STATE_WAIT_FOR_RECHARGE_INIT, + STATE_WAIT_FOR_RECHARGE, + STATE_MAINTENANCE_A_INIT, + STATE_MAINTENANCE_A, + STATE_MAINTENANCE_B_INIT, + STATE_MAINTENANCE_B, + STATE_TEMP_UNDEROVER_INIT, + STATE_TEMP_UNDEROVER, + STATE_TEMP_LOWHIGH_INIT, + STATE_TEMP_LOWHIGH, + STATE_SUSPENDED_INIT, + STATE_SUSPENDED, + STATE_OVV_PROTECT_INIT, + STATE_OVV_PROTECT, + STATE_SAFETY_TIMER_EXPIRED_INIT, + STATE_SAFETY_TIMER_EXPIRED, + STATE_BATT_REMOVED_INIT, + STATE_BATT_REMOVED, + STATE_WD_EXPIRED_INIT, + STATE_WD_EXPIRED, +}; + +static const char *states[] = { + "HANDHELD_INIT", + "HANDHELD", + "CHG_NOT_OK_INIT", + "CHG_NOT_OK", + "HW_TEMP_PROTECT_INIT", + "HW_TEMP_PROTECT", + "NORMAL_INIT", + "NORMAL", + "WAIT_FOR_RECHARGE_INIT", + "WAIT_FOR_RECHARGE", + "MAINTENANCE_A_INIT", + "MAINTENANCE_A", + "MAINTENANCE_B_INIT", + "MAINTENANCE_B", + "TEMP_UNDEROVER_INIT", + "TEMP_UNDEROVER", + "TEMP_LOWHIGH_INIT", + "TEMP_LOWHIGH", + "SUSPENDED_INIT", + "SUSPENDED", + "OVV_PROTECT_INIT", + "OVV_PROTECT", + "SAFETY_TIMER_EXPIRED_INIT", + "SAFETY_TIMER_EXPIRED", + "BATT_REMOVED_INIT", + "BATT_REMOVED", + "WD_EXPIRED_INIT", + "WD_EXPIRED", +}; + +struct abx500_chargalg_events { + bool batt_unknown; + bool mainextchnotok; + bool batt_ovv; + bool batt_rem; + bool btemp_underover; + bool btemp_lowhigh; + bool main_thermal_prot; + bool usb_thermal_prot; + bool main_ovv; + bool vbus_ovv; + bool usbchargernotok; + bool safety_timer_expired; + bool maintenance_timer_expired; + bool ac_wd_expired; + bool usb_wd_expired; + bool ac_cv_active; + bool usb_cv_active; + bool vbus_collapsed; +}; + +/** + * struct abx500_charge_curr_maximization - Charger maximization parameters + * @original_iset: the non optimized/maximised charger current + * @current_iset: the charging current used at this moment + * @test_delta_i: the delta between the current we want to charge and the + current that is really going into the battery + * @condition_cnt: number of iterations needed before a new charger current + is set + * @max_current: maximum charger current + * @wait_cnt: to avoid too fast current step down in case of charger + * voltage collapse, we insert this delay between step + * down + * @level: tells in how many steps the charging current has been + increased + */ +struct abx500_charge_curr_maximization { + int original_iset; + int current_iset; + int test_delta_i; + int condition_cnt; + int max_current; + int wait_cnt; + u8 level; +}; + +enum maxim_ret { + MAXIM_RET_NOACTION, + MAXIM_RET_CHANGE, + MAXIM_RET_IBAT_TOO_HIGH, +}; + +/** + * struct abx500_chargalg - abx500 Charging algorithm device information + * @dev: pointer to the structure device + * @charge_status: battery operating status + * @eoc_cnt: counter used to determine end-of_charge + * @rch_cnt: counter used to determine start of recharge + * @maintenance_chg: indicate if maintenance charge is active + * @t_hyst_norm temperature hysteresis when the temperature has been + * over or under normal limits + * @t_hyst_lowhigh temperature hysteresis when the temperature has been + * over or under the high or low limits + * @charge_state: current state of the charging algorithm + * @ccm charging current maximization parameters + * @chg_info: information about connected charger types + * @batt_data: data of the battery + * @susp_status: current charger suspension status + * @pdata: pointer to the abx500_chargalg platform data + * @bat: pointer to the abx500_bm platform data + * @chargalg_psy: structure that holds the battery properties exposed by + * the charging algorithm + * @events: structure for information about events triggered + * @chargalg_wq: work queue for running the charging algorithm + * @chargalg_periodic_work: work to run the charging algorithm periodically + * @chargalg_wd_work: work to kick the charger watchdog periodically + * @chargalg_work: work to run the charging algorithm instantly + * @safety_timer: charging safety timer + * @maintenance_timer: maintenance charging timer + * @chargalg_kobject: structure of type kobject + */ +struct abx500_chargalg { + struct device *dev; + int charge_status; + int eoc_cnt; + int rch_cnt; + bool maintenance_chg; + int t_hyst_norm; + int t_hyst_lowhigh; + enum abx500_chargalg_states charge_state; + struct abx500_charge_curr_maximization ccm; + struct abx500_chargalg_charger_info chg_info; + struct abx500_chargalg_battery_data batt_data; + struct abx500_chargalg_suspension_status susp_status; + struct abx500_chargalg_platform_data *pdata; + struct abx500_bm_data *bat; + struct power_supply chargalg_psy; + struct ux500_charger *ac_chg; + struct ux500_charger *usb_chg; + struct abx500_chargalg_events events; + struct workqueue_struct *chargalg_wq; + struct delayed_work chargalg_periodic_work; + struct delayed_work chargalg_wd_work; + struct work_struct chargalg_work; + struct timer_list safety_timer; + struct timer_list maintenance_timer; + struct kobject chargalg_kobject; +}; + +/* Main battery properties */ +static enum power_supply_property abx500_chargalg_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, +}; + +/** + * abx500_chargalg_safety_timer_expired() - Expiration of the safety timer + * @data: pointer to the abx500_chargalg structure + * + * This function gets called when the safety timer for the charger + * expires + */ +static void abx500_chargalg_safety_timer_expired(unsigned long data) +{ + struct abx500_chargalg *di = (struct abx500_chargalg *) data; + dev_err(di->dev, "Safety timer expired\n"); + di->events.safety_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_maintenance_timer_expired() - Expiration of + * the maintenance timer + * @i: pointer to the abx500_chargalg structure + * + * This function gets called when the maintenence timer + * expires + */ +static void abx500_chargalg_maintenance_timer_expired(unsigned long data) +{ + + struct abx500_chargalg *di = (struct abx500_chargalg *) data; + dev_dbg(di->dev, "Maintenance timer expired\n"); + di->events.maintenance_timer_expired = true; + + /* Trigger execution of the algorithm instantly */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_state_to() - Change charge state + * @di: pointer to the abx500_chargalg structure + * + * This function gets called when a charge state change should occur + */ +static void abx500_chargalg_state_to(struct abx500_chargalg *di, + enum abx500_chargalg_states state) +{ + dev_dbg(di->dev, + "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n", + di->charge_state == state ? "NO" : "YES", + di->charge_state, + states[di->charge_state], + state, + states[state]); + + di->charge_state = state; +} + +/** + * abx500_chargalg_check_charger_connection() - Check charger connection change + * @di: pointer to the abx500_chargalg structure + * + * This function will check if there is a change in the charger connection + * and change charge state accordingly. AC has precedence over USB. + */ +static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di) +{ + if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg || + di->susp_status.suspended_change) { + /* + * Charger state changed or suspension + * has changed since last update + */ + if ((di->chg_info.conn_chg & AC_CHG) && + !di->susp_status.ac_suspended) { + dev_dbg(di->dev, "Charging source is AC\n"); + if (di->chg_info.charger_type != AC_CHG) { + di->chg_info.charger_type = AC_CHG; + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + } else if ((di->chg_info.conn_chg & USB_CHG) && + !di->susp_status.usb_suspended) { + dev_dbg(di->dev, "Charging source is USB\n"); + di->chg_info.charger_type = USB_CHG; + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else if (di->chg_info.conn_chg && + (di->susp_status.ac_suspended || + di->susp_status.usb_suspended)) { + dev_dbg(di->dev, "Charging is suspended\n"); + di->chg_info.charger_type = NO_CHG; + abx500_chargalg_state_to(di, STATE_SUSPENDED_INIT); + } else { + dev_dbg(di->dev, "Charging source is OFF\n"); + di->chg_info.charger_type = NO_CHG; + abx500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + di->chg_info.prev_conn_chg = di->chg_info.conn_chg; + di->susp_status.suspended_change = false; + } + return di->chg_info.conn_chg; +} + +/** + * abx500_chargalg_start_safety_timer() - Start charging safety timer + * @di: pointer to the abx500_chargalg structure + * + * The safety timer is used to avoid overcharging of old or bad batteries. + * There are different timers for AC and USB + */ +static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di) +{ + unsigned long timer_expiration = 0; + + switch (di->chg_info.charger_type) { + case AC_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->main_safety_tmr_h * 3600 * HZ)); + break; + + case USB_CHG: + timer_expiration = + round_jiffies(jiffies + + (di->bat->usb_safety_tmr_h * 3600 * HZ)); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } + + di->events.safety_timer_expired = false; + di->safety_timer.expires = timer_expiration; + if (!timer_pending(&di->safety_timer)) + add_timer(&di->safety_timer); + else + mod_timer(&di->safety_timer, timer_expiration); +} + +/** + * abx500_chargalg_stop_safety_timer() - Stop charging safety timer + * @di: pointer to the abx500_chargalg structure + * + * The safety timer is stopped whenever the NORMAL state is exited + */ +static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di) +{ + di->events.safety_timer_expired = false; + del_timer(&di->safety_timer); +} + +/** + * abx500_chargalg_start_maintenance_timer() - Start charging maintenance timer + * @di: pointer to the abx500_chargalg structure + * @duration: duration of ther maintenance timer in hours + * + * The maintenance timer is used to maintain the charge in the battery once + * the battery is considered full. These timers are chosen to match the + * discharge curve of the battery + */ +static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di, + int duration) +{ + unsigned long timer_expiration; + + /* Convert from hours to jiffies */ + timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ)); + + di->events.maintenance_timer_expired = false; + di->maintenance_timer.expires = timer_expiration; + if (!timer_pending(&di->maintenance_timer)) + add_timer(&di->maintenance_timer); + else + mod_timer(&di->maintenance_timer, timer_expiration); +} + +/** + * abx500_chargalg_stop_maintenance_timer() - Stop maintenance timer + * @di: pointer to the abx500_chargalg structure + * + * The maintenance timer is stopped whenever maintenance ends or when another + * state is entered + */ +static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di) +{ + di->events.maintenance_timer_expired = false; + del_timer(&di->maintenance_timer); +} + +/** + * abx500_chargalg_kick_watchdog() - Kick charger watchdog + * @di: pointer to the abx500_chargalg structure + * + * The charger watchdog have to be kicked periodically whenever the charger is + * on, else the ABB will reset the system + */ +static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di) +{ + /* Check if charger exists and kick watchdog if charging */ + if (di->ac_chg && di->ac_chg->ops.kick_wd && + di->chg_info.online_chg & AC_CHG) + return di->ac_chg->ops.kick_wd(di->ac_chg); + else if (di->usb_chg && di->usb_chg->ops.kick_wd && + di->chg_info.online_chg & USB_CHG) + return di->usb_chg->ops.kick_wd(di->usb_chg); + + return -ENXIO; +} + +/** + * abx500_chargalg_ac_en() - Turn on/off the AC charger + * @di: pointer to the abx500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The AC charger will be turned on/off with the requested charge voltage and + * current + */ +static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->ac_chg || !di->ac_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->ac_chg->max_out_volt) + vset = min(vset, di->ac_chg->max_out_volt); + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + di->chg_info.ac_iset = iset; + di->chg_info.ac_vset = vset; + + return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset); +} + +/** + * abx500_chargalg_usb_en() - Turn on/off the USB charger + * @di: pointer to the abx500_chargalg structure + * @enable: charger on/off + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * The USB charger will be turned on/off with the requested charge voltage and + * current + */ +static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable, + int vset, int iset) +{ + if (!di->usb_chg || !di->usb_chg->ops.enable) + return -ENXIO; + + /* Select maximum of what both the charger and the battery supports */ + if (di->usb_chg->max_out_volt) + vset = min(vset, di->usb_chg->max_out_volt); + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + di->chg_info.usb_iset = iset; + di->chg_info.usb_vset = vset; + + return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset); +} + +/** + * abx500_chargalg_update_chg_curr() - Update charger current + * @di: pointer to the abx500_chargalg structure + * @iset: requested charger output current + * + * The charger output current will be updated for the charger + * that is currently in use + */ +static int abx500_chargalg_update_chg_curr(struct abx500_chargalg *di, + int iset) +{ + /* Check if charger exists and update current if charging */ + if (di->ac_chg && di->ac_chg->ops.update_curr && + di->chg_info.charger_type & AC_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->ac_chg->max_out_curr) + iset = min(iset, di->ac_chg->max_out_curr); + + di->chg_info.ac_iset = iset; + + return di->ac_chg->ops.update_curr(di->ac_chg, iset); + } else if (di->usb_chg && di->usb_chg->ops.update_curr && + di->chg_info.charger_type & USB_CHG) { + /* + * Select maximum of what both the charger + * and the battery supports + */ + if (di->usb_chg->max_out_curr) + iset = min(iset, di->usb_chg->max_out_curr); + + di->chg_info.usb_iset = iset; + + return di->usb_chg->ops.update_curr(di->usb_chg, iset); + } + + return -ENXIO; +} + +/** + * abx500_chargalg_stop_charging() - Stop charging + * @di: pointer to the abx500_chargalg structure + * + * This function is called from any state where charging should be stopped. + * All charging is disabled and all status parameters and timers are changed + * accordingly + */ +static void abx500_chargalg_stop_charging(struct abx500_chargalg *di) +{ + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(&di->chargalg_psy); +} + +/** + * abx500_chargalg_hold_charging() - Pauses charging + * @di: pointer to the abx500_chargalg structure + * + * This function is called in the case where maintenance charging has been + * disabled and instead a battery voltage mode is entered to check when the + * battery voltage has reached a certain recharge voltage + */ +static void abx500_chargalg_hold_charging(struct abx500_chargalg *di) +{ + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->maintenance_chg = false; + cancel_delayed_work(&di->chargalg_wd_work); + power_supply_changed(&di->chargalg_psy); +} + +/** + * abx500_chargalg_start_charging() - Start the charger + * @di: pointer to the abx500_chargalg structure + * @vset: requested charger output voltage + * @iset: requested charger output current + * + * A charger will be enabled depending on the requested charger type that was + * detected previously. + */ +static void abx500_chargalg_start_charging(struct abx500_chargalg *di, + int vset, int iset) +{ + switch (di->chg_info.charger_type) { + case AC_CHG: + dev_dbg(di->dev, + "AC parameters: Vset %d, Ich %d\n", vset, iset); + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_ac_en(di, true, vset, iset); + break; + + case USB_CHG: + dev_dbg(di->dev, + "USB parameters: Vset %d, Ich %d\n", vset, iset); + abx500_chargalg_ac_en(di, false, 0, 0); + abx500_chargalg_usb_en(di, true, vset, iset); + break; + + default: + dev_err(di->dev, "Unknown charger to charge from\n"); + break; + } +} + +/** + * abx500_chargalg_check_temp() - Check battery temperature ranges + * @di: pointer to the abx500_chargalg structure + * + * The battery temperature is checked against the predefined limits and the + * charge state is changed accordingly + */ +static void abx500_chargalg_check_temp(struct abx500_chargalg *di) +{ + if (di->batt_data.temp > (di->bat->temp_low + di->t_hyst_norm) && + di->batt_data.temp < (di->bat->temp_high - di->t_hyst_norm)) { + /* Temp OK! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = 0; + } else { + if (((di->batt_data.temp >= di->bat->temp_high) && + (di->batt_data.temp < + (di->bat->temp_over - di->t_hyst_lowhigh))) || + ((di->batt_data.temp > + (di->bat->temp_under + di->t_hyst_lowhigh)) && + (di->batt_data.temp <= di->bat->temp_low))) { + /* TEMP minor!!!!! */ + di->events.btemp_underover = false; + di->events.btemp_lowhigh = true; + di->t_hyst_norm = di->bat->temp_hysteresis; + di->t_hyst_lowhigh = 0; + } else if (di->batt_data.temp <= di->bat->temp_under || + di->batt_data.temp >= di->bat->temp_over) { + /* TEMP major!!!!! */ + di->events.btemp_underover = true; + di->events.btemp_lowhigh = false; + di->t_hyst_norm = 0; + di->t_hyst_lowhigh = di->bat->temp_hysteresis; + } else { + /* Within hysteresis */ + dev_dbg(di->dev, "Within hysteresis limit temp: %d " + "hyst_lowhigh %d, hyst normal %d\n", + di->batt_data.temp, di->t_hyst_lowhigh, + di->t_hyst_norm); + } + } +} + +/** + * abx500_chargalg_check_charger_voltage() - Check charger voltage + * @di: pointer to the abx500_chargalg structure + * + * Charger voltage is checked against maximum limit + */ +static void abx500_chargalg_check_charger_voltage(struct abx500_chargalg *di) +{ + if (di->chg_info.usb_volt > di->bat->chg_params->usb_volt_max) + di->chg_info.usb_chg_ok = false; + else + di->chg_info.usb_chg_ok = true; + + if (di->chg_info.ac_volt > di->bat->chg_params->ac_volt_max) + di->chg_info.ac_chg_ok = false; + else + di->chg_info.ac_chg_ok = true; + +} + +/** + * abx500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled + * @di: pointer to the abx500_chargalg structure + * + * End-of-charge criteria is fulfilled when the battery voltage is above a + * certain limit and the battery current is below a certain limit for a + * predefined number of consecutive seconds. If true, the battery is full + */ +static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di) +{ + if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING && + di->charge_state == STATE_NORMAL && + !di->maintenance_chg && (di->batt_data.volt >= + di->bat->bat_type[di->bat->batt_id].termination_vol || + di->events.usb_cv_active || di->events.ac_cv_active) && + di->batt_data.avg_curr < + di->bat->bat_type[di->bat->batt_id].termination_curr && + di->batt_data.avg_curr > 0) { + if (++di->eoc_cnt >= EOC_COND_CNT) { + di->eoc_cnt = 0; + di->charge_status = POWER_SUPPLY_STATUS_FULL; + di->maintenance_chg = true; + dev_dbg(di->dev, "EOC reached!\n"); + power_supply_changed(&di->chargalg_psy); + } else { + dev_dbg(di->dev, + " EOC limit reached for the %d" + " time, out of %d before EOC\n", + di->eoc_cnt, + EOC_COND_CNT); + } + } else { + di->eoc_cnt = 0; + } +} + +static void init_maxim_chg_curr(struct abx500_chargalg *di) +{ + di->ccm.original_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.current_iset = + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl; + di->ccm.test_delta_i = di->bat->maxi->charger_curr_step; + di->ccm.max_current = di->bat->maxi->chg_curr; + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.level = 0; +} + +/** + * abx500_chargalg_chg_curr_maxim - increases the charger current to + * compensate for the system load + * @di pointer to the abx500_chargalg structure + * + * This maximization function is used to raise the charger current to get the + * battery current as close to the optimal value as possible. The battery + * current during charging is affected by the system load + */ +static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di) +{ + int delta_i; + + if (!di->bat->maxi->ena_maxi) + return MAXIM_RET_NOACTION; + + delta_i = di->ccm.original_iset - di->batt_data.inst_curr; + + if (di->events.vbus_collapsed) { + dev_dbg(di->dev, "Charger voltage has collapsed %d\n", + di->ccm.wait_cnt); + if (di->ccm.wait_cnt == 0) { + dev_dbg(di->dev, "lowering current\n"); + di->ccm.wait_cnt++; + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.max_current = + di->ccm.current_iset - di->ccm.test_delta_i; + di->ccm.current_iset = di->ccm.max_current; + di->ccm.level--; + return MAXIM_RET_CHANGE; + } else { + dev_dbg(di->dev, "waiting\n"); + /* Let's go in here twice before lowering curr again */ + di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 3; + return MAXIM_RET_NOACTION; + } + } + + di->ccm.wait_cnt = 0; + + if ((di->batt_data.inst_curr > di->ccm.original_iset)) { + dev_dbg(di->dev, " Maximization Ibat (%dmA) too high" + " (limit %dmA) (current iset: %dmA)!\n", + di->batt_data.inst_curr, di->ccm.original_iset, + di->ccm.current_iset); + + if (di->ccm.current_iset == di->ccm.original_iset) + return MAXIM_RET_NOACTION; + + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset = di->ccm.original_iset; + di->ccm.level = 0; + + return MAXIM_RET_IBAT_TOO_HIGH; + } + + if (delta_i > di->ccm.test_delta_i && + (di->ccm.current_iset + di->ccm.test_delta_i) < + di->ccm.max_current) { + if (di->ccm.condition_cnt-- == 0) { + /* Increse the iset with cco.test_delta_i */ + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + di->ccm.current_iset += di->ccm.test_delta_i; + di->ccm.level++; + dev_dbg(di->dev, " Maximization needed, increase" + " with %d mA to %dmA (Optimal ibat: %d)" + " Level %d\n", + di->ccm.test_delta_i, + di->ccm.current_iset, + di->ccm.original_iset, + di->ccm.level); + return MAXIM_RET_CHANGE; + } else { + return MAXIM_RET_NOACTION; + } + } else { + di->ccm.condition_cnt = di->bat->maxi->wait_cycles; + return MAXIM_RET_NOACTION; + } +} + +static void handle_maxim_chg_curr(struct abx500_chargalg *di) +{ + enum maxim_ret ret; + int result; + + ret = abx500_chargalg_chg_curr_maxim(di); + switch (ret) { + case MAXIM_RET_CHANGE: + result = abx500_chargalg_update_chg_curr(di, + di->ccm.current_iset); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); + break; + case MAXIM_RET_IBAT_TOO_HIGH: + result = abx500_chargalg_update_chg_curr(di, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + if (result) + dev_err(di->dev, "failed to set chg curr\n"); + break; + + case MAXIM_RET_NOACTION: + default: + /* Do nothing..*/ + break; + } +} + +static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct abx500_chargalg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_abx500_chargalg_device_info(psy); + /* For all psy where the driver name appears in any supplied_to */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + /* Initialize chargers if not already done */ + if (!di->ac_chg && + ext->type == POWER_SUPPLY_TYPE_MAINS) + di->ac_chg = psy_to_ux500_charger(ext); + else if (!di->usb_chg && + ext->type == POWER_SUPPLY_TYPE_USB) + di->usb_chg = psy_to_ux500_charger(ext); + + if (ext->get_property(ext, prop, &ret)) + continue; + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + /* Battery present */ + if (ret.intval) + di->events.batt_rem = false; + /* Battery removed */ + else + di->events.batt_rem = true; + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~AC_CHG; + } + /* AC connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & AC_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= AC_CHG; + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && + (di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg &= ~USB_CHG; + } + /* USB connected */ + else if (ret.intval && + !(di->chg_info.conn_chg & USB_CHG)) { + di->chg_info.prev_conn_chg = + di->chg_info.conn_chg; + di->chg_info.conn_chg |= USB_CHG; + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_ONLINE: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + /* AC offline */ + if (!ret.intval && + (di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~AC_CHG; + } + /* AC online */ + else if (ret.intval && + !(di->chg_info.online_chg & AC_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= AC_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + case POWER_SUPPLY_TYPE_USB: + /* USB offline */ + if (!ret.intval && + (di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg &= ~USB_CHG; + } + /* USB online */ + else if (ret.intval && + !(di->chg_info.online_chg & USB_CHG)) { + di->chg_info.prev_online_chg = + di->chg_info.online_chg; + di->chg_info.online_chg |= USB_CHG; + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, 0); + } + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_HEALTH: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + break; + case POWER_SUPPLY_TYPE_MAINS: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.mainextchnotok = true; + di->events.main_thermal_prot = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.ac_wd_expired = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.main_thermal_prot = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.main_thermal_prot = true; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.main_ovv = true; + di->events.mainextchnotok = false; + di->events.main_thermal_prot = false; + di->events.ac_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.main_thermal_prot = false; + di->events.mainextchnotok = false; + di->events.main_ovv = false; + di->events.ac_wd_expired = false; + break; + default: + break; + } + break; + + case POWER_SUPPLY_TYPE_USB: + switch (ret.intval) { + case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE: + di->events.usbchargernotok = true; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_DEAD: + di->events.usb_wd_expired = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + break; + case POWER_SUPPLY_HEALTH_COLD: + case POWER_SUPPLY_HEALTH_OVERHEAT: + di->events.usb_thermal_prot = true; + di->events.usbchargernotok = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_OVERVOLTAGE: + di->events.vbus_ovv = true; + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.usb_wd_expired = false; + break; + case POWER_SUPPLY_HEALTH_GOOD: + di->events.usbchargernotok = false; + di->events.usb_thermal_prot = false; + di->events.vbus_ovv = false; + di->events.usb_wd_expired = false; + break; + default: + break; + } + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_volt = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_volt = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.ac_cv_active = true; + else + di->events.ac_cv_active = false; + + break; + case POWER_SUPPLY_TYPE_USB: + /* AVG is used to indicate when we are + * in CV mode */ + if (ret.intval) + di->events.usb_cv_active = true; + else + di->events.usb_cv_active = false; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->events.batt_unknown = false; + else + di->events.batt_unknown = true; + + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_TEMP: + di->batt_data.temp = ret.intval / 10; + break; + + case POWER_SUPPLY_PROP_CURRENT_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_MAINS: + di->chg_info.ac_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + di->chg_info.usb_curr = + ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.inst_curr = ret.intval / 1000; + break; + default: + break; + } + break; + + case POWER_SUPPLY_PROP_CURRENT_AVG: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->batt_data.avg_curr = ret.intval / 1000; + break; + case POWER_SUPPLY_TYPE_USB: + if (ret.intval) + di->events.vbus_collapsed = true; + else + di->events.vbus_collapsed = false; + break; + default: + break; + } + break; + case POWER_SUPPLY_PROP_CAPACITY: + di->batt_data.percent = ret.intval; + break; + default: + break; + } + } + return 0; +} + +/** + * abx500_chargalg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void abx500_chargalg_external_power_changed(struct power_supply *psy) +{ + struct abx500_chargalg *di = to_abx500_chargalg_device_info(psy); + + /* + * Trigger execution of the algorithm instantly and read + * all power_supply properties there instead + */ + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_algorithm() - Main function for the algorithm + * @di: pointer to the abx500_chargalg structure + * + * This is the main control function for the charging algorithm. + * It is called periodically or when something happens that will + * trigger a state change + */ +static void abx500_chargalg_algorithm(struct abx500_chargalg *di) +{ + int charger_status; + + /* Collect data from all power_supply class devices */ + class_for_each_device(power_supply_class, NULL, + &di->chargalg_psy, abx500_chargalg_get_ext_psy_data); + + abx500_chargalg_end_of_charge(di); + abx500_chargalg_check_temp(di); + abx500_chargalg_check_charger_voltage(di); + + charger_status = abx500_chargalg_check_charger_connection(di); + /* + * First check if we have a charger connected. + * Also we don't allow charging of unknown batteries if configured + * this way + */ + if (!charger_status || + (di->events.batt_unknown && !di->bat->chg_unknown_bat)) { + if (di->charge_state != STATE_HANDHELD) { + di->events.safety_timer_expired = false; + abx500_chargalg_state_to(di, STATE_HANDHELD_INIT); + } + } + + /* If suspended, we should not continue checking the flags */ + else if (di->charge_state == STATE_SUSPENDED_INIT || + di->charge_state == STATE_SUSPENDED) { + /* We don't do anything here, just don,t continue */ + } + + /* Safety timer expiration */ + else if (di->events.safety_timer_expired) { + if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED) + abx500_chargalg_state_to(di, + STATE_SAFETY_TIMER_EXPIRED_INIT); + } + /* + * Check if any interrupts has occured + * that will prevent us from charging + */ + + /* Battery removed */ + else if (di->events.batt_rem) { + if (di->charge_state != STATE_BATT_REMOVED) + abx500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT); + } + /* Main or USB charger not ok. */ + else if (di->events.mainextchnotok || di->events.usbchargernotok) { + /* + * If vbus_collapsed is set, we have to lower the charger + * current, which is done in the normal state below + */ + if (di->charge_state != STATE_CHG_NOT_OK && + !di->events.vbus_collapsed) + abx500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT); + } + /* VBUS, Main or VBAT OVV. */ + else if (di->events.vbus_ovv || + di->events.main_ovv || + di->events.batt_ovv || + !di->chg_info.usb_chg_ok || + !di->chg_info.ac_chg_ok) { + if (di->charge_state != STATE_OVV_PROTECT) + abx500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT); + } + /* USB Thermal, stop charging */ + else if (di->events.main_thermal_prot || + di->events.usb_thermal_prot) { + if (di->charge_state != STATE_HW_TEMP_PROTECT) + abx500_chargalg_state_to(di, + STATE_HW_TEMP_PROTECT_INIT); + } + /* Battery temp over/under */ + else if (di->events.btemp_underover) { + if (di->charge_state != STATE_TEMP_UNDEROVER) + abx500_chargalg_state_to(di, + STATE_TEMP_UNDEROVER_INIT); + } + /* Watchdog expired */ + else if (di->events.ac_wd_expired || + di->events.usb_wd_expired) { + if (di->charge_state != STATE_WD_EXPIRED) + abx500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT); + } + /* Battery temp high/low */ + else if (di->events.btemp_lowhigh) { + if (di->charge_state != STATE_TEMP_LOWHIGH) + abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT); + } + + dev_dbg(di->dev, + "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d " + "State %s Active_chg %d Chg_status %d AC %d USB %d " + "AC_online %d USB_online %d AC_CV %d USB_CV %d AC_I %d " + "USB_I %d AC_Vset %d AC_Iset %d USB_Vset %d USB_Iset %d\n", + di->batt_data.volt, + di->batt_data.avg_curr, + di->batt_data.inst_curr, + di->batt_data.temp, + di->batt_data.percent, + di->maintenance_chg, + states[di->charge_state], + di->chg_info.charger_type, + di->charge_status, + di->chg_info.conn_chg & AC_CHG, + di->chg_info.conn_chg & USB_CHG, + di->chg_info.online_chg & AC_CHG, + di->chg_info.online_chg & USB_CHG, + di->events.ac_cv_active, + di->events.usb_cv_active, + di->chg_info.ac_curr, + di->chg_info.usb_curr, + di->chg_info.ac_vset, + di->chg_info.ac_iset, + di->chg_info.usb_vset, + di->chg_info.usb_iset); + + switch (di->charge_state) { + case STATE_HANDHELD_INIT: + abx500_chargalg_stop_charging(di); + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + abx500_chargalg_state_to(di, STATE_HANDHELD); + /* Intentional fallthrough */ + + case STATE_HANDHELD: + break; + + case STATE_SUSPENDED_INIT: + if (di->susp_status.ac_suspended) + abx500_chargalg_ac_en(di, false, 0, 0); + if (di->susp_status.usb_suspended) + abx500_chargalg_usb_en(di, false, 0, 0); + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->maintenance_chg = false; + abx500_chargalg_state_to(di, STATE_SUSPENDED); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_SUSPENDED: + /* CHARGING is suspended */ + break; + + case STATE_BATT_REMOVED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_BATT_REMOVED); + /* Intentional fallthrough */ + + case STATE_BATT_REMOVED: + if (!di->events.batt_rem) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_HW_TEMP_PROTECT_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT); + /* Intentional fallthrough */ + + case STATE_HW_TEMP_PROTECT: + if (!di->events.main_thermal_prot && + !di->events.usb_thermal_prot) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_OVV_PROTECT_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_OVV_PROTECT); + /* Intentional fallthrough */ + + case STATE_OVV_PROTECT: + if (!di->events.vbus_ovv && + !di->events.main_ovv && + !di->events.batt_ovv && + di->chg_info.usb_chg_ok && + di->chg_info.ac_chg_ok) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_CHG_NOT_OK_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_CHG_NOT_OK); + /* Intentional fallthrough */ + + case STATE_CHG_NOT_OK: + if (!di->events.mainextchnotok && + !di->events.usbchargernotok) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_SAFETY_TIMER_EXPIRED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED); + /* Intentional fallthrough */ + + case STATE_SAFETY_TIMER_EXPIRED: + /* We exit this state when charger is removed */ + break; + + case STATE_NORMAL_INIT: + abx500_chargalg_start_charging(di, + di->bat->bat_type[di->bat->batt_id].normal_vol_lvl, + di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); + abx500_chargalg_state_to(di, STATE_NORMAL); + abx500_chargalg_start_safety_timer(di); + abx500_chargalg_stop_maintenance_timer(di); + init_maxim_chg_curr(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->eoc_cnt = 0; + di->maintenance_chg = false; + power_supply_changed(&di->chargalg_psy); + + break; + + case STATE_NORMAL: + handle_maxim_chg_curr(di); + if (di->charge_status == POWER_SUPPLY_STATUS_FULL && + di->maintenance_chg) { + if (di->bat->no_maintenance) + abx500_chargalg_state_to(di, + STATE_WAIT_FOR_RECHARGE_INIT); + else + abx500_chargalg_state_to(di, + STATE_MAINTENANCE_A_INIT); + } + break; + + /* This state will be used when the maintenance state is disabled */ + case STATE_WAIT_FOR_RECHARGE_INIT: + abx500_chargalg_hold_charging(di); + abx500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE); + di->rch_cnt = RCH_COND_CNT; + /* Intentional fallthrough */ + + case STATE_WAIT_FOR_RECHARGE: + if (di->batt_data.volt <= + di->bat->bat_type[di->bat->batt_id].recharge_vol) { + if (di->rch_cnt-- == 0) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } else + di->rch_cnt = RCH_COND_CNT; + break; + + case STATE_MAINTENANCE_A_INIT: + abx500_chargalg_stop_safety_timer(di); + abx500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_chg_timer_h); + abx500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_a_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_a_cur_lvl); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_A); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_A: + if (di->events.maintenance_timer_expired) { + abx500_chargalg_stop_maintenance_timer(di); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT); + } + break; + + case STATE_MAINTENANCE_B_INIT: + abx500_chargalg_start_maintenance_timer(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_chg_timer_h); + abx500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].maint_b_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].maint_b_cur_lvl); + abx500_chargalg_state_to(di, STATE_MAINTENANCE_B); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough*/ + + case STATE_MAINTENANCE_B: + if (di->events.maintenance_timer_expired) { + abx500_chargalg_stop_maintenance_timer(di); + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + } + break; + + case STATE_TEMP_LOWHIGH_INIT: + abx500_chargalg_start_charging(di, + di->bat->bat_type[ + di->bat->batt_id].low_high_vol_lvl, + di->bat->bat_type[ + di->bat->batt_id].low_high_cur_lvl); + abx500_chargalg_stop_maintenance_timer(di); + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH); + power_supply_changed(&di->chargalg_psy); + /* Intentional fallthrough */ + + case STATE_TEMP_LOWHIGH: + if (!di->events.btemp_lowhigh) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_WD_EXPIRED_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_WD_EXPIRED); + /* Intentional fallthrough */ + + case STATE_WD_EXPIRED: + if (!di->events.ac_wd_expired && + !di->events.usb_wd_expired) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + + case STATE_TEMP_UNDEROVER_INIT: + abx500_chargalg_stop_charging(di); + abx500_chargalg_state_to(di, STATE_TEMP_UNDEROVER); + /* Intentional fallthrough */ + + case STATE_TEMP_UNDEROVER: + if (!di->events.btemp_underover) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + } + + /* Start charging directly if the new state is a charge state */ + if (di->charge_state == STATE_NORMAL_INIT || + di->charge_state == STATE_MAINTENANCE_A_INIT || + di->charge_state == STATE_MAINTENANCE_B_INIT) + queue_work(di->chargalg_wq, &di->chargalg_work); +} + +/** + * abx500_chargalg_periodic_work() - Periodic work for the algorithm + * @work: pointer to the work_struct structure + * + * Work queue function for the charging algorithm + */ +static void abx500_chargalg_periodic_work(struct work_struct *work) +{ + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_periodic_work.work); + + abx500_chargalg_algorithm(di); + + /* + * If a charger is connected then the battery has to be monitored + * frequently, else the work can be delayed. + */ + if (di->chg_info.conn_chg) + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_charging * HZ); + else + queue_delayed_work(di->chargalg_wq, + &di->chargalg_periodic_work, + di->bat->interval_not_charging * HZ); +} + +/** + * abx500_chargalg_wd_work() - periodic work to kick the charger watchdog + * @work: pointer to the work_struct structure + * + * Work queue function for kicking the charger watchdog + */ +static void abx500_chargalg_wd_work(struct work_struct *work) +{ + int ret; + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_wd_work.work); + + dev_dbg(di->dev, "abx500_chargalg_wd_work\n"); + + ret = abx500_chargalg_kick_watchdog(di); + if (ret < 0) + dev_err(di->dev, "failed to kick watchdog\n"); + + queue_delayed_work(di->chargalg_wq, + &di->chargalg_wd_work, CHG_WD_INTERVAL); +} + +/** + * abx500_chargalg_work() - Work to run the charging algorithm instantly + * @work: pointer to the work_struct structure + * + * Work queue function for calling the charging algorithm + */ +static void abx500_chargalg_work(struct work_struct *work) +{ + struct abx500_chargalg *di = container_of(work, + struct abx500_chargalg, chargalg_work); + + abx500_chargalg_algorithm(di); +} + +/** + * abx500_chargalg_get_property() - get the chargalg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * chargalg properties by reading the sysfs files. + * status: charging/discharging/full/unknown + * health: health of the battery + * Returns error code in case of failure else 0 on success + */ +static int abx500_chargalg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct abx500_chargalg *di; + + di = to_abx500_chargalg_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + break; + case POWER_SUPPLY_PROP_HEALTH: + if (di->events.batt_ovv) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (di->events.btemp_underover) { + if (di->batt_data.temp <= di->bat->temp_under) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + } else { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + } + break; + default: + return -EINVAL; + } + return 0; +} + +/* Exposure to the sysfs interface */ + +/** + * abx500_chargalg_sysfs_charger() - sysfs store operations + * @kobj: pointer to the struct kobject + * @attr: pointer to the struct attribute + * @buf: buffer that holds the parameter passed from userspace + * @length: length of the parameter passed + * + * Returns length of the buffer(input taken from user space) on success + * else error code on failure + * The operation to be performed on passing the parameters from the user space. + */ +static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t length) +{ + struct abx500_chargalg *di = container_of(kobj, + struct abx500_chargalg, chargalg_kobject); + long int param; + int ac_usb; + int ret; + char entry = *attr->name; + + switch (entry) { + case 'c': + ret = strict_strtol(buf, 10, ¶m); + if (ret < 0) + return ret; + + ac_usb = param; + switch (ac_usb) { + case 0: + /* Disable charging */ + di->susp_status.ac_suspended = true; + di->susp_status.usb_suspended = true; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 1: + /* Enable AC Charging */ + di->susp_status.ac_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 2: + /* Enable USB charging */ + di->susp_status.usb_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + default: + dev_info(di->dev, "Wrong input\n" + "Enter 0. Disable AC/USB Charging\n" + "1. Enable AC charging\n" + "2. Enable USB Charging\n"); + }; + break; + }; + return strlen(buf); +} + +static struct attribute abx500_chargalg_en_charger = \ +{ + .name = "chargalg", + .mode = S_IWUGO, +}; + +static struct attribute *abx500_chargalg_chg[] = { + &abx500_chargalg_en_charger, + NULL +}; + +const struct sysfs_ops abx500_chargalg_sysfs_ops = { + .store = abx500_chargalg_sysfs_charger, +}; + +static struct kobj_type abx500_chargalg_ktype = { + .sysfs_ops = &abx500_chargalg_sysfs_ops, + .default_attrs = abx500_chargalg_chg, +}; + +/** + * abx500_chargalg_sysfs_exit() - de-init of sysfs entry + * @di: pointer to the struct abx500_chargalg + * + * This function removes the entry in sysfs. + */ +static void abx500_chargalg_sysfs_exit(struct abx500_chargalg *di) +{ + kobject_del(&di->chargalg_kobject); +} + +/** + * abx500_chargalg_sysfs_init() - init of sysfs entry + * @di: pointer to the struct abx500_chargalg + * + * This function adds an entry in sysfs. + * Returns error code in case of failure else 0(on success) + */ +static int abx500_chargalg_sysfs_init(struct abx500_chargalg *di) +{ + int ret = 0; + + ret = kobject_init_and_add(&di->chargalg_kobject, + &abx500_chargalg_ktype, + NULL, "abx500_chargalg"); + if (ret < 0) + dev_err(di->dev, "failed to create sysfs entry\n"); + + return ret; +} +/* Exposure to the sysfs interface <> */ + +#if defined(CONFIG_PM) +static int abx500_chargalg_resume(struct platform_device *pdev) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + /* Kick charger watchdog if charging (any charger online) */ + if (di->chg_info.online_chg) + queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0); + + /* + * Run the charging algorithm directly to be sure we don't + * do it too seldom + */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + return 0; +} + +static int abx500_chargalg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + if (di->chg_info.online_chg) + cancel_delayed_work_sync(&di->chargalg_wd_work); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + + return 0; +} +#else +#define abx500_chargalg_suspend NULL +#define abx500_chargalg_resume NULL +#endif + +static int __devexit abx500_chargalg_remove(struct platform_device *pdev) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + /* sysfs interface to enable/disbale charging from user space */ + abx500_chargalg_sysfs_exit(di); + + /* Delete the work queue */ + destroy_workqueue(di->chargalg_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->chargalg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di); + + return 0; +} + +static int __devinit abx500_chargalg_probe(struct platform_device *pdev) +{ + struct abx500_bm_plat_data *plat_data; + int ret = 0; + + struct abx500_chargalg *di = + kzalloc(sizeof(struct abx500_chargalg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get device struct */ + di->dev = &pdev->dev; + + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->chargalg; + di->bat = plat_data->battery; + + /* chargalg supply */ + di->chargalg_psy.name = "abx500_chargalg"; + di->chargalg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->chargalg_psy.properties = abx500_chargalg_props; + di->chargalg_psy.num_properties = ARRAY_SIZE(abx500_chargalg_props); + di->chargalg_psy.get_property = abx500_chargalg_get_property; + di->chargalg_psy.supplied_to = di->pdata->supplied_to; + di->chargalg_psy.num_supplicants = di->pdata->num_supplicants; + di->chargalg_psy.external_power_changed = + abx500_chargalg_external_power_changed; + + /* Initilialize safety timer */ + init_timer(&di->safety_timer); + di->safety_timer.function = abx500_chargalg_safety_timer_expired; + di->safety_timer.data = (unsigned long) di; + + /* Initilialize maintenance timer */ + init_timer(&di->maintenance_timer); + di->maintenance_timer.function = + abx500_chargalg_maintenance_timer_expired; + di->maintenance_timer.data = (unsigned long) di; + + /* Create a work queue for the chargalg */ + di->chargalg_wq = + create_singlethread_workqueue("abx500_chargalg_wq"); + if (di->chargalg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for chargalg */ + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_periodic_work, + abx500_chargalg_periodic_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_wd_work, + abx500_chargalg_wd_work); + + /* Init work for chargalg */ + INIT_WORK(&di->chargalg_work, abx500_chargalg_work); + + /* To detect charger at startup */ + di->chg_info.prev_conn_chg = -1; + + /* Register chargalg power supply class */ + ret = power_supply_register(di->dev, &di->chargalg_psy); + if (ret) { + dev_err(di->dev, "failed to register chargalg psy\n"); + goto free_chargalg_wq; + } + + platform_set_drvdata(pdev, di); + + /* sysfs interface to enable/disable charging from user space */ + ret = abx500_chargalg_sysfs_init(di); + if (ret) { + dev_err(di->dev, "failed to create sysfs entry\n"); + goto free_psy; + } + + /* Run the charging algorithm */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + dev_info(di->dev, "probe success\n"); + return ret; + +free_psy: + power_supply_unregister(&di->chargalg_psy); +free_chargalg_wq: + destroy_workqueue(di->chargalg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver abx500_chargalg_driver = { + .probe = abx500_chargalg_probe, + .remove = __devexit_p(abx500_chargalg_remove), + .suspend = abx500_chargalg_suspend, + .resume = abx500_chargalg_resume, + .driver = { + .name = "abx500-chargalg", + .owner = THIS_MODULE, + }, +}; + +static int __init abx500_chargalg_init(void) +{ + return platform_driver_register(&abx500_chargalg_driver); +} + +static void __exit abx500_chargalg_exit(void) +{ + platform_driver_unregister(&abx500_chargalg_driver); +} + +module_init(abx500_chargalg_init); +module_exit(abx500_chargalg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:abx500-chargalg"); +MODULE_DESCRIPTION("abx500 battery charging algorithm"); diff --git a/include/linux/mfd/abx500/ux500_chargalg.h b/include/linux/mfd/abx500/ux500_chargalg.h new file mode 100644 index 00000000000..f04e47ff56a --- /dev/null +++ b/include/linux/mfd/abx500/ux500_chargalg.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * Author: Johan Gardsmark for ST-Ericsson. + * License terms: GNU General Public License (GPL), version 2 + */ + +#ifndef _UX500_CHARGALG_H +#define _UX500_CHARGALG_H + +#include + +#define psy_to_ux500_charger(x) container_of((x), \ + struct ux500_charger, psy) + +/* Forward declaration */ +struct ux500_charger; + +struct ux500_charger_ops { + int (*enable) (struct ux500_charger *, int, int, int); + int (*kick_wd) (struct ux500_charger *); + int (*update_curr) (struct ux500_charger *, int); +}; + +/** + * struct ux500_charger - power supply ux500 charger sub class + * @psy power supply base class + * @ops ux500 charger operations + * @max_out_volt maximum output charger voltage in mV + * @max_out_curr maximum output charger current in mA + */ +struct ux500_charger { + struct power_supply psy; + struct ux500_charger_ops ops; + int max_out_volt; + int max_out_curr; +}; + +#endif -- cgit v1.2.3 From b0e617ecf6af971a4433a72a7db36922a9afcdc1 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 26 May 2011 16:49:55 +0530 Subject: power: ab5500-fg: fuel gauge driver for ab5500 This is a low level ab5500 fuel gauge driver. A power supply class of type fg is registered to display the battery parameters such as energy, charge, capacity, voltage. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: WP332221 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ibe508dd84836a9a4873539ce194b50788ea25cba Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23151 Reviewed-by: QATEST Reviewed-by: Johan PALSSON Reviewed-by: Srinidhi KASAGAR --- drivers/power/ab5500_fg.c | 1830 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1830 insertions(+) create mode 100644 drivers/power/ab5500_fg.c diff --git a/drivers/power/ab5500_fg.c b/drivers/power/ab5500_fg.c new file mode 100644 index 00000000000..1c866e8fec5 --- /dev/null +++ b/drivers/power/ab5500_fg.c @@ -0,0 +1,1830 @@ +/* + * Copyright (C) ST-Ericsson AB 2011 + * + * Main and Back-up battery management driver. + * + * Note: Backup battery management is required in case of Li-Ion battery and not + * for capacitive battery. HREF boards have capacitive battery and hence backup + * battery management is not used and the supported code is available in this + * driver. + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson + * Karl Komierowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static LIST_HEAD(ab5500_fg_list); + +/* U5500 Constants */ +#define FG_ON_MASK 0x04 +#define FG_ON 0x04 +#define FG_ACC_RESET_ON_READ_MASK 0x08 +#define FG_ACC_RESET_ON_READ 0x08 +#define EN_READOUT_MASK 0x01 +#define EN_READOUT 0x01 +#define RESET 0x00 +#define EOC_52_mA 0x04 +#define MILLI_TO_MICRO 1000 +#define FG_LSB_IN_MA 770 +#define QLSB_NANO_AMP_HOURS_X10 1129 +#define SEC_TO_SAMPLE(S) (S * 4) +#define NBR_AVG_SAMPLES 20 +#define LOW_BAT_CHECK_INTERVAL (2 * HZ) + +#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */ + +#define interpolate(x, x1, y1, x2, y2) \ + ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1)))); + +#define to_ab5500_fg_device_info(x) container_of((x), \ + struct ab5500_fg, fg_psy); + +/** + * struct ab5500_fg_interrupts - ab5500 fg interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab5500_fg_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +enum ab5500_fg_discharge_state { + AB5500_FG_DISCHARGE_INIT, + AB5500_FG_DISCHARGE_INITMEASURING, + AB5500_FG_DISCHARGE_INIT_RECOVERY, + AB5500_FG_DISCHARGE_RECOVERY, + AB5500_FG_DISCHARGE_READOUT, + AB5500_FG_DISCHARGE_WAKEUP, +}; + +static char *discharge_state[] = { + "DISCHARGE_INIT", + "DISCHARGE_INITMEASURING", + "DISCHARGE_INIT_RECOVERY", + "DISCHARGE_RECOVERY", + "DISCHARGE_READOUT", + "DISCHARGE_WAKEUP", +}; + +enum ab5500_fg_charge_state { + AB5500_FG_CHARGE_INIT, + AB5500_FG_CHARGE_READOUT, +}; + +static char *charge_state[] = { + "CHARGE_INIT", + "CHARGE_READOUT", +}; + +enum ab5500_fg_calibration_state { + AB5500_FG_CALIB_INIT, + AB5500_FG_CALIB_WAIT, + AB5500_FG_CALIB_END, +}; + +struct ab5500_fg_avg_cap { + int avg; + int samples[NBR_AVG_SAMPLES]; + __kernel_time_t time_stamps[NBR_AVG_SAMPLES]; + int pos; + int nbr_samples; + int sum; +}; + +struct ab5500_fg_battery_capacity { + int max_mah_design; + int max_mah; + int mah; + int permille; + int level; + int prev_mah; + int prev_percent; + int prev_level; +}; + +struct ab5500_fg_flags { + bool fg_enabled; + bool conv_done; + bool charging; + bool fully_charged; + bool low_bat_delay; + bool low_bat; + bool bat_ovv; + bool batt_unknown; + bool calibrate; +}; + +/** + * struct ab5500_fg - ab5500 FG device information + * @dev: Pointer to the structure device + * @vbat: Battery voltage in mV + * @vbat_nom: Nominal battery voltage in mV + * @inst_curr: Instantenous battery current in mA + * @avg_curr: Average battery current in mA + * @fg_samples: Number of samples used in the FG accumulation + * @accu_charge: Accumulated charge from the last conversion + * @recovery_cnt: Counter for recovery mode + * @high_curr_cnt: Counter for high current mode + * @init_cnt: Counter for init mode + * @v_to_cap: capacity based on battery voltage + * @recovery_needed: Indicate if recovery is needed + * @high_curr_mode: Indicate if we're in high current mode + * @init_capacity: Indicate if initial capacity measuring should be done + * @calib_state State during offset calibration + * @discharge_state: Current discharge state + * @charge_state: Current charge state + * @flags: Structure for information about events triggered + * @bat_cap: Structure for battery capacity specific parameters + * @avg_cap: Average capacity filter + * @parent: Pointer to the struct ab5500 + * @gpadc: Pointer to the struct gpadc + * @gpadc_auto: Pointer tot he struct adc_auto_input + * @pdata: Pointer to the ab5500_fg platform data + * @bat: Pointer to the ab5500_bm platform data + * @fg_psy: Structure that holds the FG specific battery properties + * @fg_wq: Work queue for running the FG algorithm + * @fg_periodic_work: Work to run the FG algorithm periodically + * @fg_low_bat_work: Work to check low bat condition + * @fg_work: Work to run the FG algorithm instantly + * @fg_acc_cur_work: Work to read the FG accumulator + * @cc_lock: Mutex for locking the CC + * @node: struct of type list_head + */ +struct ab5500_fg { + struct device *dev; + int vbat; + int vbat_nom; + int inst_curr; + int avg_curr; + int fg_samples; + int accu_charge; + int recovery_cnt; + int high_curr_cnt; + int init_cnt; + int v_to_cap; + bool recovery_needed; + bool high_curr_mode; + bool init_capacity; + enum ab5500_fg_calibration_state calib_state; + enum ab5500_fg_discharge_state discharge_state; + enum ab5500_fg_charge_state charge_state; + struct ab5500_fg_flags flags; + struct ab5500_fg_battery_capacity bat_cap; + struct ab5500_fg_avg_cap avg_cap; + struct ab5500 *parent; + struct ab5500_gpadc *gpadc; + struct adc_auto_input *gpadc_auto; + struct abx500_fg_platform_data *pdata; + struct abx500_bm_data *bat; + struct power_supply fg_psy; + struct workqueue_struct *fg_wq; + struct delayed_work fg_periodic_work; + struct delayed_work fg_low_bat_work; + struct work_struct fg_work; + struct delayed_work fg_acc_cur_work; + struct mutex cc_lock; + struct list_head node; + struct timer_list avg_current_timer; +}; + +/* Main battery properties */ +static enum power_supply_property ab5500_fg_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, +}; + +struct ab5500_fg *ab5500_fg_get(void) +{ + struct ab5500_fg *di; + di = list_first_entry(&ab5500_fg_list, struct ab5500_fg, node); + + return di; +} + +/** + * ab5500_fg_is_low_curr() - Low or high current mode + * @di: pointer to the ab5500_fg structure + * @curr: the current to base or our decision on + * + * Low current mode if the current consumption is below a certain threshold + */ +static int ab5500_fg_is_low_curr(struct ab5500_fg *di, int curr) +{ + /* + * We want to know if we're in low current mode + */ + if (curr > -di->bat->fg_params->high_curr_threshold) + return true; + else + return false; +} + +/** + * ab5500_fg_add_cap_sample() - Add capacity to average filter + * @di: pointer to the ab5500_fg structure + * @sample: the capacity in mAh to add to the filter + * + * A capacity is added to the filter and a new mean capacity is calculated and + * returned + */ +static int ab5500_fg_add_cap_sample(struct ab5500_fg *di, int sample) +{ + struct timespec ts; + struct ab5500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + do { + avg->sum += sample - avg->samples[avg->pos]; + avg->samples[avg->pos] = sample; + avg->time_stamps[avg->pos] = ts.tv_sec; + avg->pos++; + + if (avg->pos == NBR_AVG_SAMPLES) + avg->pos = 0; + + if (avg->nbr_samples < NBR_AVG_SAMPLES) + avg->nbr_samples++; + + /* + * Check the time stamp for each sample. If too old, + * replace with latest sample + */ + } while (ts.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]); + + avg->avg = avg->sum / avg->nbr_samples; + + return avg->avg; +} + +/** + * ab5500_fg_fill_cap_sample() - Fill average filter + * @di: pointer to the ab5500_fg structure + * @sample: the capacity in mAh to fill the filter with + * + * The capacity filter is filled with a capacity in mAh + */ +static void ab5500_fg_fill_cap_sample(struct ab5500_fg *di, int sample) +{ + int i; + struct timespec ts; + struct ab5500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = sample; + avg->time_stamps[i] = ts.tv_sec; + } + + avg->pos = 0; + avg->nbr_samples = NBR_AVG_SAMPLES; + avg->sum = sample * NBR_AVG_SAMPLES; + avg->avg = sample; +} + +/** + * ab5500_fg_coulomb_counter() - enable coulomb counter + * @di: pointer to the ab5500_fg structure + * @enable: enable/disable + * + * Enable/Disable coulomb counter. + * On failure returns negative value. + */ +static int ab5500_fg_coulomb_counter(struct ab5500_fg *di, bool enable) +{ + int ret = 0; + mutex_lock(&di->cc_lock); + if (enable) { + /* Power-up the CC */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A, + (FG_ON | FG_ACC_RESET_ON_READ)); + if (ret) + goto cc_err; + + di->flags.fg_enabled = true; + } else { + /* Stop the CC */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A, + FG_ON_MASK, RESET); + if (ret) + goto cc_err; + + di->flags.fg_enabled = false; + + } + dev_dbg(di->dev, " CC enabled: %d Samples: %d\n", + enable, di->fg_samples); + + mutex_unlock(&di->cc_lock); + + return ret; +cc_err: + dev_err(di->dev, "%s Enabling coulomb counter failed\n", __func__); + mutex_unlock(&di->cc_lock); + return ret; +} + +/** + * ab5500_fg_inst_curr() - battery instantaneous current + * @di: pointer to the ab5500_fg structure + * + * Returns battery instantenous current(on success) else error code + */ +static int ab5500_fg_inst_curr(struct ab5500_fg *di) +{ + u8 low, high, value; + static int val; + int ret = 0; + bool fg_off = false; + + if (!di->flags.fg_enabled) { + fg_off = true; + /* Power-up the CC */ + ab5500_fg_coulomb_counter(di, true); + msleep(250); + } + + mutex_lock(&di->cc_lock); + /* + * Since there is no interrupt for this, just wait for 250ms + * 250ms is one sample conversion time with 32.768 Khz RTC clock + */ + msleep(250); + + /* Enable read request */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_B, + EN_READOUT_MASK, EN_READOUT); + if (ret) + goto inst_curr_err; + + /* Read CC Sample conversion value Low and high */ + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FGDIR_READ0, &low); + if (ret < 0) + goto inst_curr_err; + + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FGDIR_READ1, &high); + if (ret < 0) + goto inst_curr_err; + + /* + * negative value for Discharging + * convert 2's compliment into decimal + */ + if (high & 0x10) + val = (low | (high << 8) | 0xFFFFE000); + else + val = (low | (high << 8)); + + /* + * Convert to unit value in mA + * R(FGSENSE) = 20 mOhm + * Scaling of LSB: This corresponds fro R(FGSENSE) to a current of + * I = Q/t = 192.7 uC * 4 Hz = 0.77mA + */ + val = (val * 770) / 1000; + + mutex_unlock(&di->cc_lock); + + if (fg_off) { + dev_dbg(di->dev, "%s Disable FG\n", __func__); + /* Power-off the CC */ + ab5500_fg_coulomb_counter(di, false); + } + + return val; + +inst_curr_err: + dev_err(di->dev, "%s Get instanst current failed\n", __func__); + mutex_unlock(&di->cc_lock); + return ret; +} + +static void ab5500_fg_acc_cur_timer_expired(unsigned long data) +{ + struct ab5500_fg *di = (struct ab5500_fg *) data; + dev_dbg(di->dev, "Avg current timer expired\n"); + + /* Trigger execution of the algorithm instantly */ + queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work, 0); +} + +/** + * ab5500_fg_acc_cur_work() - average battery current + * @work: pointer to the work_struct structure + * + * Updated the average battery current obtained from the + * coulomb counter. + */ +static void ab5500_fg_acc_cur_work(struct work_struct *work) +{ + int val; + int ret; + u8 low, med, high, cnt_low, cnt_high; + + struct ab5500_fg *di = container_of(work, + struct ab5500_fg, fg_acc_cur_work.work); + + if (!di->flags.fg_enabled) { + /* Power-up the CC */ + ab5500_fg_coulomb_counter(di, true); + msleep(250); + } + mutex_lock(&di->cc_lock); + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_C, + EN_READOUT_MASK, EN_READOUT); + if (ret < 0) + goto exit; + /* If charging read charging registers for accumulated values */ + if (di->flags.charging) { + /* Read CC Sample conversion value Low and high */ + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_CH0, &low); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_CH1, &med); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_CH2, &high); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_VAL_COUNT0, &cnt_low); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_VAL_COUNT1, &cnt_high); + if (ret < 0) + goto exit; + queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work, + di->bat->interval_charging * HZ); + } else { /* discharging */ + /* Read CC Sample conversion value Low and high */ + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_DIS_CH0, &low); + if (ret < 0) + goto exit; + + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_DIS_CH1, &med); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_DIS_CH2, &high); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_VAL_COUNT0, &cnt_low); + if (ret < 0) + goto exit; + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, + AB5500_FG_VAL_COUNT1, &cnt_high); + if (ret < 0) + goto exit; + queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work, + di->bat->interval_not_charging * HZ); + } + di->fg_samples = (cnt_low | (cnt_high << 8)); + val = (low | (med << 8) | (high << 16)); + + di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10)/10000; + di->avg_curr = (val * FG_LSB_IN_MA) / (di->fg_samples * 1000); + di->flags.conv_done = true; + + mutex_unlock(&di->cc_lock); + + queue_work(di->fg_wq, &di->fg_work); + + return; +exit: + dev_err(di->dev, + "Failed to read or write gas gauge registers\n"); + mutex_unlock(&di->cc_lock); + queue_work(di->fg_wq, &di->fg_work); +} + +/** + * ab5500_fg_bat_voltage() - get battery voltage + * @di: pointer to the ab5500_fg structure + * + * Returns battery voltage(on success) else error code + */ +static int ab5500_fg_bat_voltage(struct ab5500_fg *di) +{ + int vbat; + static int prev; + + vbat = ab5500_gpadc_convert(di->gpadc, MAIN_BAT_V); + if (vbat < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value\n", + __func__); + return prev; + } + + prev = vbat; + return vbat; +} + +/** + * ab5500_fg_volt_to_capacity() - Voltage based capacity + * @di: pointer to the ab5500_fg structure + * @voltage: The voltage to convert to a capacity + * + * Returns battery capacity in per mille based on voltage + */ +static int ab5500_fg_volt_to_capacity(struct ab5500_fg *di, int voltage) +{ + int i, tbl_size; + struct abx500_v_to_cap *tbl; + int cap = 0; + + tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl, + tbl_size = di->bat->bat_type[di->bat->batt_id].n_v_cap_tbl_elements; + + for (i = 0; i < tbl_size; ++i) { + if (di->vbat < tbl[i].voltage && di->vbat > tbl[i+1].voltage) + di->v_to_cap = tbl[i].capacity; + } + + for (i = 0; i < tbl_size; ++i) { + if (voltage > tbl[i].voltage) + break; + } + + if ((i > 0) && (i < tbl_size)) { + cap = interpolate(voltage, + tbl[i].voltage, + tbl[i].capacity * 10, + tbl[i-1].voltage, + tbl[i-1].capacity * 10); + } else if (i == 0) { + cap = 1000; + } else { + cap = 0; + } + + dev_dbg(di->dev, "%s Vbat: %d, Cap: %d per mille", + __func__, voltage, cap); + + return cap; +} + +/** + * ab5500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity + * @di: pointer to the ab5500_fg structure + * + * Returns battery capacity based on battery voltage that is not compensated + * for the voltage drop due to the load + */ +static int ab5500_fg_uncomp_volt_to_capacity(struct ab5500_fg *di) +{ + di->vbat = ab5500_fg_bat_voltage(di); + return ab5500_fg_volt_to_capacity(di, di->vbat); +} + +/** + * ab5500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity + * @di: pointer to the ab5500_fg structure + * + * Returns battery capacity based on battery voltage that is load compensated + * for the voltage drop + */ +static int ab5500_fg_load_comp_volt_to_capacity(struct ab5500_fg *di) +{ + int vbat_comp; + + di->inst_curr = ab5500_fg_inst_curr(di); + di->vbat = ab5500_fg_bat_voltage(di); + + /* Use Ohms law to get the load compensated voltage */ + vbat_comp = di->vbat - (di->inst_curr * + di->bat->bat_type[di->bat->batt_id].battery_resistance) / 1000; + + dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, " + "R: %dmOhm, Current: %dmA\n", + __func__, + di->vbat, + vbat_comp, + di->bat->bat_type[di->bat->batt_id].battery_resistance, + di->inst_curr); + + return ab5500_fg_volt_to_capacity(di, vbat_comp); +} + +/** + * ab5500_fg_convert_mah_to_permille() - Capacity in mAh to permille + * @di: pointer to the ab5500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in permille + */ +static int ab5500_fg_convert_mah_to_permille(struct ab5500_fg *di, int cap_mah) +{ + return (cap_mah * 1000) / di->bat_cap.max_mah_design; +} + +/** + * ab5500_fg_convert_permille_to_mah() - Capacity in permille to mAh + * @di: pointer to the ab5500_fg structure + * @cap_pm: capacity in permille + * + * Converts capacity in permille to capacity in mAh + */ +static int ab5500_fg_convert_permille_to_mah(struct ab5500_fg *di, int cap_pm) +{ + return cap_pm * di->bat_cap.max_mah_design / 1000; +} + +/** + * ab5500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh + * @di: pointer to the ab5500_fg structure + * @cap_mah: capacity in mAh + * + * Converts capacity in mAh to capacity in uWh + */ +static int ab5500_fg_convert_mah_to_uwh(struct ab5500_fg *di, int cap_mah) +{ + u64 div_res; + u32 div_rem; + + div_res = ((u64) cap_mah) * ((u64) di->vbat_nom); + div_rem = do_div(div_res, 1000); + + /* Make sure to round upwards if necessary */ + if (div_rem >= 1000 / 2) + div_res++; + + return (int) div_res; +} + +/** + * ab5500_fg_calc_cap_charging() - Calculate remaining capacity while charging + * @di: pointer to the ab5500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. The filter is filled with this capacity + */ +static int ab5500_fg_calc_cap_charging(struct ab5500_fg *di) +{ + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + /* + * We force capacity to 100% as long as the algorithm + * reports that it's full. + */ + if (di->bat_cap.mah >= di->bat_cap.max_mah_design || + di->flags.fully_charged) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + ab5500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + /* We need to update battery voltage and inst current when charging */ + di->vbat = ab5500_fg_bat_voltage(di); + di->inst_curr = ab5500_fg_inst_curr(di); + + return di->bat_cap.mah; +} + +/** + * ab5500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage + * @di: pointer to the ab5500_fg structure + * @comp: if voltage should be load compensated before capacity calc + * + * Return the capacity in mAh based on the battery voltage. The voltage can + * either be load compensated or not. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab5500_fg_calc_cap_discharge_voltage(struct ab5500_fg *di, bool comp) +{ + int permille, mah; + + if (comp) + permille = ab5500_fg_load_comp_volt_to_capacity(di); + else + permille = ab5500_fg_uncomp_volt_to_capacity(di); + + mah = ab5500_fg_convert_permille_to_mah(di, permille); + + di->bat_cap.mah = ab5500_fg_add_cap_sample(di, mah); + di->bat_cap.permille = + ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + + return di->bat_cap.mah; +} + +/** + * ab5500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG + * @di: pointer to the ab5500_fg structure + * + * Return the capacity in mAh based on previous calculated capcity and the FG + * accumulator register value. This value is added to the filter and a + * new mean value is calculated and returned. + */ +static int ab5500_fg_calc_cap_discharge_fg(struct ab5500_fg *di) +{ + int permille_volt, permille; + + dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n", + __func__, + di->bat_cap.mah, + di->accu_charge); + + /* Capacity should not be less than 0 */ + if (di->bat_cap.mah + di->accu_charge > 0) + di->bat_cap.mah += di->accu_charge; + else + di->bat_cap.mah = 0; + + if (di->bat_cap.mah >= di->bat_cap.max_mah_design) + di->bat_cap.mah = di->bat_cap.max_mah_design; + + /* + * Check against voltage based capacity. It can not be lower + * than what the uncompensated voltage says + */ + permille = ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + permille_volt = ab5500_fg_uncomp_volt_to_capacity(di); + + if (permille < permille_volt) { + di->bat_cap.permille = permille_volt; + di->bat_cap.mah = ab5500_fg_convert_permille_to_mah(di, + di->bat_cap.permille); + + dev_dbg(di->dev, "%s voltage based: perm %d perm_volt %d\n", + __func__, + permille, + permille_volt); + + ab5500_fg_fill_cap_sample(di, di->bat_cap.mah); + } else { + ab5500_fg_fill_cap_sample(di, di->bat_cap.mah); + di->bat_cap.permille = + ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah); + } + + return di->bat_cap.mah; +} + +/** + * ab5500_fg_capacity_level() - Get the battery capacity level + * @di: pointer to the ab5500_fg structure + * + * Get the battery capacity level based on the capacity in percent + */ +static int ab5500_fg_capacity_level(struct ab5500_fg *di) +{ + int ret, percent; + + percent = di->bat_cap.permille / 10; + + if (percent <= di->bat->cap_levels->critical || + di->flags.low_bat) + ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else if (percent <= di->bat->cap_levels->low) + ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (percent <= di->bat->cap_levels->normal) + ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (percent <= di->bat->cap_levels->high) + ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else + ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + + return ret; +} + +/** + * ab5500_fg_check_capacity_limits() - Check if capacity has changed + * @di: pointer to the ab5500_fg structure + * @init: capacity is allowed to go up in init mode + * + * Check if capacity or capacity limit has changed and notify the system + * about it using the power_supply framework + */ +static void ab5500_fg_check_capacity_limits(struct ab5500_fg *di, bool init) +{ + bool changed = false; + + di->bat_cap.level = ab5500_fg_capacity_level(di); + + if (di->bat_cap.level != di->bat_cap.prev_level) { + /* + * We do not allow reported capacity level to go up + * unless we're charging or if we're in init + */ + if (!(!di->flags.charging && di->bat_cap.level > + di->bat_cap.prev_level) || init) { + dev_dbg(di->dev, "level changed from %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + di->bat_cap.prev_level = di->bat_cap.level; + changed = true; + } else { + dev_dbg(di->dev, "level not allowed to go up " + "since no charger is connected: %d to %d\n", + di->bat_cap.prev_level, + di->bat_cap.level); + } + } + + /* + * If we have received the LOW_BAT IRQ, set capacity to 0 to initiate + * shutdown + */ + if (di->flags.low_bat) { + dev_dbg(di->dev, "Battery low, set capacity to 0\n"); + di->bat_cap.prev_percent = 0; + di->bat_cap.permille = 0; + di->bat_cap.prev_mah = 0; + di->bat_cap.mah = 0; + changed = true; + } else if (di->bat_cap.prev_percent != di->bat_cap.permille / 10) { + if (di->bat_cap.permille / 10 == 0) { + /* + * We will not report 0% unless we've got + * the LOW_BAT IRQ, no matter what the FG + * algorithm says. + */ + di->bat_cap.prev_percent = 1; + di->bat_cap.permille = 1; + di->bat_cap.prev_mah = 1; + di->bat_cap.mah = 1; + + changed = true; + } else if (!(!di->flags.charging && + (di->bat_cap.permille / 10) > + di->bat_cap.prev_percent) || init) { + /* + * We do not allow reported capacity to go up + * unless we're charging or if we're in init + */ + dev_dbg(di->dev, + "capacity changed from %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + di->bat_cap.prev_percent = di->bat_cap.permille / 10; + di->bat_cap.prev_mah = di->bat_cap.mah; + + changed = true; + } else { + dev_dbg(di->dev, "capacity not allowed to go up since " + "no charger is connected: %d to %d (%d)\n", + di->bat_cap.prev_percent, + di->bat_cap.permille / 10, + di->bat_cap.permille); + } + } + + if (changed) + power_supply_changed(&di->fg_psy); + +} + +static void ab5500_fg_charge_state_to(struct ab5500_fg *di, + enum ab5500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Charge state from %d [%s] to %d [%s]\n", + di->charge_state, + charge_state[di->charge_state], + new_state, + charge_state[new_state]); + + di->charge_state = new_state; +} + +static void ab5500_fg_discharge_state_to(struct ab5500_fg *di, + enum ab5500_fg_charge_state new_state) +{ + dev_dbg(di->dev, "Disharge state from %d [%s] to %d [%s]\n", + di->discharge_state, + discharge_state[di->discharge_state], + new_state, + discharge_state[new_state]); + + di->discharge_state = new_state; +} + +/** + * ab5500_fg_algorithm_charging() - FG algorithm for when charging + * @di: pointer to the ab5500_fg structure + * + * Battery capacity calculation state machine for when we're charging + */ +static void ab5500_fg_algorithm_charging(struct ab5500_fg *di) +{ + /* + * If we change to discharge mode + * we should start with recovery + */ + if (di->discharge_state != AB5500_FG_DISCHARGE_INIT_RECOVERY) + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_INIT_RECOVERY); + + switch (di->charge_state) { + case AB5500_FG_CHARGE_INIT: + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_charging); + + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_charge_state_to(di, AB5500_FG_CHARGE_READOUT); + + break; + + case AB5500_FG_CHARGE_READOUT: + /* + * Read the FG and calculate the new capacity + */ + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + ab5500_fg_calc_cap_charging(di); + + break; + + default: + break; + } + + /* Check capacity limits */ + ab5500_fg_check_capacity_limits(di, false); +} + +/** + * ab5500_fg_algorithm_discharging() - FG algorithm for when discharging + * @di: pointer to the ab5500_fg structure + * + * Battery capacity calculation state machine for when we're discharging + */ +static void ab5500_fg_algorithm_discharging(struct ab5500_fg *di) +{ + int sleep_time; + + /* If we change to charge mode we should start with init */ + if (di->charge_state != AB5500_FG_CHARGE_INIT) + ab5500_fg_charge_state_to(di, AB5500_FG_CHARGE_INIT); + + switch (di->discharge_state) { + case AB5500_FG_DISCHARGE_INIT: + /* We use the FG IRQ to work on */ + di->init_cnt = 0; + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_INITMEASURING); + + /* Intentional fallthrough */ + case AB5500_FG_DISCHARGE_INITMEASURING: + /* + * Discard a number of samples during startup. + * After that, use compensated voltage for a few + * samples to get an initial capacity. + * Then go to READOUT + */ + sleep_time = di->bat->fg_params->init_timer; + + /* Discard the first [x] seconds */ + if (di->init_cnt > + di->bat->fg_params->init_discard_time) { + + ab5500_fg_calc_cap_discharge_voltage(di, true); + + ab5500_fg_check_capacity_limits(di, true); + } + + di->init_cnt += sleep_time; + if (di->init_cnt > + di->bat->fg_params->init_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_READOUT); + } + + break; + + case AB5500_FG_DISCHARGE_INIT_RECOVERY: + di->recovery_cnt = 0; + di->recovery_needed = true; + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_RECOVERY); + + /* Intentional fallthrough */ + + case AB5500_FG_DISCHARGE_RECOVERY: + sleep_time = di->bat->fg_params->recovery_sleep_timer; + + /* + * We should check the power consumption + * If low, go to READOUT (after x min) or + * RECOVERY_SLEEP if time left. + * If high, go to READOUT + */ + di->inst_curr = ab5500_fg_inst_curr(di); + + if (ab5500_fg_is_low_curr(di, di->inst_curr)) { + if (di->recovery_cnt > + di->bat->fg_params->recovery_total_time) { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_READOUT); + di->recovery_needed = false; + } else { + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, + sleep_time * HZ); + } + di->recovery_cnt += sleep_time; + } else { + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_READOUT); + } + + break; + + case AB5500_FG_DISCHARGE_READOUT: + di->inst_curr = ab5500_fg_inst_curr(di); + + if (ab5500_fg_is_low_curr(di, di->inst_curr)) { + /* Detect mode change */ + if (di->high_curr_mode) { + di->high_curr_mode = false; + di->high_curr_cnt = 0; + } + + if (di->recovery_needed) { + ab5500_fg_discharge_state_to(di, + AB5500_FG_DISCHARGE_RECOVERY); + + queue_delayed_work(di->fg_wq, + &di->fg_periodic_work, + 0); + + break; + } + + ab5500_fg_calc_cap_discharge_voltage(di, true); + } else { + mutex_lock(&di->cc_lock); + if (!di->flags.conv_done) { + /* Wasn't the CC IRQ that got us here */ + mutex_unlock(&di->cc_lock); + dev_dbg(di->dev, "%s CC conv not done\n", + __func__); + + break; + } + di->flags.conv_done = false; + mutex_unlock(&di->cc_lock); + + /* Detect mode change */ + if (!di->high_curr_mode) { + di->high_curr_mode = true; + di->high_curr_cnt = 0; + } + + di->high_curr_cnt += + di->bat->fg_params->accu_high_curr; + if (di->high_curr_cnt > + di->bat->fg_params->high_curr_time) + di->recovery_needed = true; + + ab5500_fg_calc_cap_discharge_fg(di); + } + + ab5500_fg_check_capacity_limits(di, false); + + break; + + case AB5500_FG_DISCHARGE_WAKEUP: + ab5500_fg_coulomb_counter(di, true); + di->inst_curr = ab5500_fg_inst_curr(di); + + ab5500_fg_calc_cap_discharge_voltage(di, true); + + di->fg_samples = SEC_TO_SAMPLE( + di->bat->fg_params->accu_high_curr); + /* Re-program number of samples set above */ + ab5500_fg_coulomb_counter(di, true); + ab5500_fg_discharge_state_to(di, AB5500_FG_DISCHARGE_READOUT); + + ab5500_fg_check_capacity_limits(di, false); + + break; + + default: + break; + } +} + +/** + * ab5500_fg_algorithm_calibrate() - Internal columb counter offset calibration + * @di: pointer to the ab5500_fg structure + * + */ +static void ab5500_fg_algorithm_calibrate(struct ab5500_fg *di) +{ + int ret; + + switch (di->calib_state) { + case AB5500_FG_CALIB_INIT: + dev_dbg(di->dev, "Calibration ongoing...\n"); + /* TODO: For Cut 1.1 no calibration */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A, + FG_ACC_RESET_ON_READ_MASK, FG_ACC_RESET_ON_READ); + if (ret) + goto err; + di->calib_state = AB5500_FG_CALIB_WAIT; + break; + case AB5500_FG_CALIB_END: + di->flags.calibrate = false; + dev_dbg(di->dev, "Calibration done...\n"); + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + break; + case AB5500_FG_CALIB_WAIT: + dev_dbg(di->dev, "Calibration WFI\n"); + default: + break; + } + return; +err: + /* Something went wrong, don't calibrate then */ + dev_err(di->dev, "failed to calibrate the CC\n"); + di->flags.calibrate = false; + di->calib_state = AB5500_FG_CALIB_INIT; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); +} + +/** + * ab5500_fg_algorithm() - Entry point for the FG algorithm + * @di: pointer to the ab5500_fg structure + * + * Entry point for the battery capacity calculation state machine + */ +static void ab5500_fg_algorithm(struct ab5500_fg *di) +{ + if (di->flags.calibrate) + ab5500_fg_algorithm_calibrate(di); + else { + if (di->flags.charging) + ab5500_fg_algorithm_charging(di); + else + ab5500_fg_algorithm_discharging(di); + } + + dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d " + "%d %d %d %d %d %d %d\n", + di->bat_cap.max_mah_design, + di->bat_cap.mah, + di->bat_cap.permille, + di->bat_cap.level, + di->bat_cap.prev_mah, + di->bat_cap.prev_percent, + di->bat_cap.prev_level, + di->vbat, + di->inst_curr, + di->avg_curr, + di->accu_charge, + di->flags.charging, + di->charge_state, + di->discharge_state, + di->high_curr_mode, + di->recovery_needed); +} + +/** + * ab5500_fg_periodic_work() - Run the FG state machine periodically + * @work: pointer to the work_struct structure + * + * Work queue function for periodic work + */ +static void ab5500_fg_periodic_work(struct work_struct *work) +{ + struct ab5500_fg *di = container_of(work, struct ab5500_fg, + fg_periodic_work.work); + + if (di->init_capacity) { + /* A dummy read that will return 0 */ + di->inst_curr = ab5500_fg_inst_curr(di); + /* Get an initial capacity calculation */ + ab5500_fg_calc_cap_discharge_voltage(di, true); + ab5500_fg_check_capacity_limits(di, true); + di->init_capacity = false; + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + } else + ab5500_fg_algorithm(di); +} + +/** + * ab5500_fg_low_bat_work() - Check LOW_BAT condition + * @work: pointer to the work_struct structure + * + * Work queue function for checking the LOW_BAT condition + */ +static void ab5500_fg_low_bat_work(struct work_struct *work) +{ + int vbat; + + struct ab5500_fg *di = container_of(work, struct ab5500_fg, + fg_low_bat_work.work); + + vbat = ab5500_fg_bat_voltage(di); + + /* Check if LOW_BAT still fulfilled */ + if (vbat < di->bat->fg_params->lowbat_threshold) { + di->flags.low_bat = true; + dev_warn(di->dev, "Battery voltage still LOW\n"); + + /* + * We need to re-schedule this check to be able to detect + * if the voltage increases again during charging + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } else { + di->flags.low_bat = false; + dev_warn(di->dev, "Battery voltage OK again\n"); + } + + /* This is needed to dispatch LOW_BAT */ + ab5500_fg_check_capacity_limits(di, false); + + /* Set this flag to check if LOW_BAT IRQ still occurs */ + di->flags.low_bat_delay = false; +} + +/** + * ab5500_fg_instant_work() - Run the FG state machine instantly + * @work: pointer to the work_struct structure + * + * Work queue function for instant work + */ +static void ab5500_fg_instant_work(struct work_struct *work) +{ + struct ab5500_fg *di = container_of(work, struct ab5500_fg, fg_work); + + ab5500_fg_algorithm(di); +} + +/** + * ab5500_fg_get_property() - get the fg properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the + * fg properties by reading the sysfs files. + * voltage_now: battery voltage + * current_now: battery instant current + * current_avg: battery average current + * charge_full_design: capacity where battery is considered full + * charge_now: battery capacity in nAh + * capacity: capacity in percent + * capacity_level: capacity level + * + * Returns error code in case of failure else 0 on success + */ +static int ab5500_fg_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab5500_fg *di; + int i, tbl_size; + struct abx500_v_to_cap *tbl; + + di = to_ab5500_fg_device_info(psy); + + /* + * If battery is identified as unknown and charging of unknown + * batteries is disabled, we always report 100% capacity and + * capacity level UNKNOWN, since we can't calculate + * remaining capacity + */ + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (di->flags.bat_ovv) + val->intval = 47500000; + else + val->intval = ab5500_gpadc_convert + (di->gpadc, MAIN_BAT_V) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + di->inst_curr = ab5500_fg_inst_curr(di); + val->intval = di->inst_curr * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = di->avg_curr * 1000; + break; + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + val->intval = ab5500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah_design); + break; + case POWER_SUPPLY_PROP_ENERGY_FULL: + val->intval = ab5500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + break; + case POWER_SUPPLY_PROP_ENERGY_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = ab5500_fg_convert_mah_to_uwh(di, + di->bat_cap.max_mah); + else + val->intval = ab5500_fg_convert_mah_to_uwh(di, + di->bat_cap.prev_mah); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = di->bat_cap.max_mah_design; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = di->bat_cap.max_mah; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = di->bat_cap.max_mah; + else + val->intval = di->bat_cap.prev_mah; + break; + case POWER_SUPPLY_PROP_CAPACITY: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = 100; + else if (di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl) { + tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl, + tbl_size = di->bat->bat_type[ + di->bat->batt_id].n_v_cap_tbl_elements; + + for (i = 0; i < tbl_size; ++i) { + if (di->vbat < tbl[i].voltage && + di->vbat > tbl[i+1].voltage) { + di->v_to_cap = tbl[i].capacity; + break; + } + } + val->intval = di->v_to_cap; + } else + val->intval = di->bat_cap.prev_percent; + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (di->flags.batt_unknown && !di->bat->chg_unknown_bat) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + else + val->intval = di->bat_cap.prev_level; + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab5500_fg_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab5500_fg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab5500_fg_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + switch (ret.intval) { + case POWER_SUPPLY_STATUS_UNKNOWN: + case POWER_SUPPLY_STATUS_DISCHARGING: + case POWER_SUPPLY_STATUS_NOT_CHARGING: + if (!di->flags.charging) + break; + di->flags.charging = false; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_FULL: + if (di->flags.fully_charged) + break; + di->flags.fully_charged = true; + /* Save current capacity as maximum */ + di->bat_cap.max_mah = di->bat_cap.mah; + queue_work(di->fg_wq, &di->fg_work); + break; + case POWER_SUPPLY_STATUS_CHARGING: + if (di->flags.charging) + break; + di->flags.charging = true; + di->flags.fully_charged = false; + queue_work(di->fg_wq, &di->fg_work); + break; + }; + default: + break; + }; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + if (ret.intval) + di->flags.batt_unknown = false; + else + di->flags.batt_unknown = true; + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +static int ab5500_fg_bat_v_trig(int mux) +{ + struct ab5500_fg *di = ab5500_fg_get(); + + /* check if the battery voltage is below low threshold */ + if (di->vbat < 2700) { + dev_warn(di->dev, "Battery voltage is below LOW threshold\n"); + di->flags.low_bat_delay = true; + /* + * Start a timer to check LOW_BAT again after some time + * This is done to avoid shutdown on single voltage dips + */ + queue_delayed_work(di->fg_wq, &di->fg_low_bat_work, + round_jiffies(LOW_BAT_CHECK_INTERVAL)); + } + /* check if battery votlage is above OVV */ + else if (di->vbat > 4200) { + dev_dbg(di->dev, "Battery OVV\n"); + di->flags.bat_ovv = true; + + power_supply_changed(&di->fg_psy); + } else + return -EINVAL; + + return 0; +} + +/** + * ab5500_fg_init_hw_registers() - Set up FG related registers + * @di: pointer to the ab5500_fg structure + * + * Set up battery OVV, low battery voltage registers + */ +static int ab5500_fg_init_hw_registers(struct ab5500_fg *di) +{ + int ret; + struct adc_auto_input *auto_ip; + + auto_ip = kzalloc(sizeof(struct adc_auto_input), GFP_KERNEL); + if (!auto_ip) { + dev_err(di->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + auto_ip->mux = MAIN_BAT_V; + auto_ip->freq = MS500; + auto_ip->min = 2700; + auto_ip->max = 4200; + auto_ip->auto_adc_callback = ab5500_fg_bat_v_trig; + di->gpadc_auto = auto_ip; + ret = ab5500_gpadc_convert_auto(di->gpadc, di->gpadc_auto); + if (ret) + dev_err(di->dev, + "failed to set auto trigger for battery votlage\n"); + /* set End Of Charge current to 247mA */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_EOC, EOC_52_mA); + return ret; +} + +/** + * ab5500_fg_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is the entry point of the pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in any external power + * supply that this driver needs to be notified of. + */ +static void ab5500_fg_external_power_changed(struct power_supply *psy) +{ + struct ab5500_fg *di = to_ab5500_fg_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->fg_psy, ab5500_fg_get_ext_psy_data); +} + +#if defined(CONFIG_PM) +static int ab5500_fg_resume(struct platform_device *pdev) +{ + struct ab5500_fg *di = platform_get_drvdata(pdev); + + /* + * Change state if we're not charging. If we're charging we will wake + * up on the FG IRQ + */ + if (!di->flags.charging) { + ab5500_fg_discharge_state_to(di, AB5500_FG_DISCHARGE_WAKEUP); + queue_work(di->fg_wq, &di->fg_work); + } + + return 0; +} + +static int ab5500_fg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab5500_fg *di = platform_get_drvdata(pdev); + + flush_delayed_work(&di->fg_periodic_work); + + /* + * If the FG is enabled we will disable it before going to suspend + * only if we're not charging + */ + if (di->flags.fg_enabled && !di->flags.charging) + ab5500_fg_coulomb_counter(di, false); + + return 0; +} +#else +#define ab5500_fg_suspend NULL +#define ab5500_fg_resume NULL +#endif + +static int __devexit ab5500_fg_remove(struct platform_device *pdev) +{ + int ret = 0; + struct ab5500_fg *di = platform_get_drvdata(pdev); + + /* Disable coulomb counter */ + ret = ab5500_fg_coulomb_counter(di, false); + if (ret) + dev_err(di->dev, "failed to disable coulomb counter\n"); + + destroy_workqueue(di->fg_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->fg_psy); + platform_set_drvdata(pdev, NULL); + kfree(di->gpadc_auto); + kfree(di); + return ret; +} + +static int __devinit ab5500_fg_probe(struct platform_device *pdev) +{ + struct abx500_bm_plat_data *plat_data; + int ret = 0; + + struct ab5500_fg *di = + kzalloc(sizeof(struct ab5500_fg), GFP_KERNEL); + if (!di) + return -ENOMEM; + + mutex_init(&di->cc_lock); + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab5500_gpadc_get("ab5500-adc.0"); + + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->fg; + di->bat = plat_data->battery; + + /* get fg specific platform data */ + if (!di->pdata) { + dev_err(di->dev, "no fg platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* get battery specific platform data */ + if (!di->bat) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + di->fg_psy.name = "ab5500_fg"; + di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->fg_psy.properties = ab5500_fg_props; + di->fg_psy.num_properties = ARRAY_SIZE(ab5500_fg_props); + di->fg_psy.get_property = ab5500_fg_get_property; + di->fg_psy.supplied_to = di->pdata->supplied_to; + di->fg_psy.num_supplicants = di->pdata->num_supplicants; + di->fg_psy.external_power_changed = ab5500_fg_external_power_changed; + + di->bat_cap.max_mah_design = MILLI_TO_MICRO * + di->bat->bat_type[di->bat->batt_id].charge_full_design; + + di->bat_cap.max_mah = di->bat_cap.max_mah_design; + + di->vbat_nom = di->bat->bat_type[di->bat->batt_id].nominal_voltage; + + di->init_capacity = true; + + ab5500_fg_charge_state_to(di, AB5500_FG_CHARGE_INIT); + ab5500_fg_discharge_state_to(di, AB5500_FG_DISCHARGE_INIT); + + /* Create a work queue for running the FG algorithm */ + di->fg_wq = create_singlethread_workqueue("ab5500_fg_wq"); + if (di->fg_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for running the fg algorithm instantly */ + INIT_WORK(&di->fg_work, ab5500_fg_instant_work); + + /* Init work for getting the battery accumulated current */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_acc_cur_work, + ab5500_fg_acc_cur_work); + + /* Work delayed Queue to run the state machine */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_periodic_work, + ab5500_fg_periodic_work); + + /* Work to check low battery condition */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_low_bat_work, + ab5500_fg_low_bat_work); + + list_add_tail(&di->node, &ab5500_fg_list); + + /* Initialize OVV, and other registers */ + ret = ab5500_fg_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize registers\n"); + goto free_fg_wq; + } + + /* Consider battery unknown until we're informed otherwise */ + di->flags.batt_unknown = true; + + /* Register FG power supply class */ + ret = power_supply_register(di->dev, &di->fg_psy); + if (ret) { + dev_err(di->dev, "failed to register FG psy\n"); + goto free_fg_wq; + } + + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); + ab5500_fg_coulomb_counter(di, true); + + /* Initilialize avg current timer */ + init_timer(&di->avg_current_timer); + di->avg_current_timer.function = ab5500_fg_acc_cur_timer_expired; + di->avg_current_timer.data = (unsigned long) di; + di->avg_current_timer.expires = 60 * HZ;; + if (!timer_pending(&di->avg_current_timer)) + add_timer(&di->avg_current_timer); + else + mod_timer(&di->avg_current_timer, 60 * HZ); + + platform_set_drvdata(pdev, di); + + /* Calibrate the fg first time */ + di->flags.calibrate = true; + di->calib_state = AB5500_FG_CALIB_INIT; + /* Run the FG algorithm */ + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work, 0); + + dev_info(di->dev, "probe success\n"); + return ret; + +free_fg_wq: + destroy_workqueue(di->fg_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab5500_fg_driver = { + .probe = ab5500_fg_probe, + .remove = __devexit_p(ab5500_fg_remove), + .suspend = ab5500_fg_suspend, + .resume = ab5500_fg_resume, + .driver = { + .name = "ab5500-fg", + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_fg_init(void) +{ + return platform_driver_register(&ab5500_fg_driver); +} + +static void __exit ab5500_fg_exit(void) +{ + platform_driver_unregister(&ab5500_fg_driver); +} + +subsys_initcall_sync(ab5500_fg_init); +module_exit(ab5500_fg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab5500-fg"); +MODULE_DESCRIPTION("AB5500 Fuel Gauge driver"); -- cgit v1.2.3 From a55f7457e871b9bb3821a9bea5a7f68a0afe2e7e Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 26 May 2011 16:50:32 +0530 Subject: power: ab5500-btemp: temperature monitoring driver Temperature monitoring driver for ab5500. Since temperature is the most important aspect of a battery, this driver monitors the battery temperature and takes precautionary measurements even in case of hardware failure. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: WP332221 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Idd0a4a7f220ffa9693516757aba395ab7f75cfc2 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23152 Reviewed-by: QATEST Reviewed-by: Johan PALSSON Reviewed-by: Srinidhi KASAGAR --- drivers/power/ab5500_btemp.c | 870 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 870 insertions(+) create mode 100644 drivers/power/ab5500_btemp.c diff --git a/drivers/power/ab5500_btemp.c b/drivers/power/ab5500_btemp.c new file mode 100644 index 00000000000..57241c96f60 --- /dev/null +++ b/drivers/power/ab5500_btemp.c @@ -0,0 +1,870 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * Battery temperature driver for ab5500 + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson + * Karl Komierowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BTEMP_THERMAL_LOW_LIMIT -10 +#define BTEMP_THERMAL_MED_LIMIT 0 +#define BTEMP_THERMAL_HIGH_LIMIT_62 62 + +#define BTEMP_BATCTRL_CURR_SRC_7UA 7 +#define BTEMP_BATCTRL_CURR_SRC_15UA 15 +#define BTEMP_BATCTRL_CURR_SRC_20UA 20 + +#define UART_MODE 0x0F +#define BAT_CUR_SRC 0x1F +#define RESIS_ID_MODE 0x03 +#define RESET 0x00 +#define ADOUT_10K_PULL_UP 0x07 + +#define to_ab5500_btemp_device_info(x) container_of((x), \ + struct ab5500_btemp, btemp_psy); + +/** + * struct ab5500_btemp_interrupts - ab5500 interrupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab5500_btemp_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab5500_btemp_events { + bool batt_rem; + bool usb_conn; +}; + +/** + * struct ab5500_btemp - ab5500 BTEMP device information + * @dev: Pointer to the structure device + * @chip_id: Chip-Id of the AB5500 + * @curr_source: What current source we use, in uA + * @bat_temp: Battery temperature in degree Celcius + * @prev_bat_temp Last dispatched battery temperature + * @node: struct of type list_head + * @parent: Pointer to the struct ab5500 + * @gpadc: Pointer to the struct gpadc + * @gpadc-auto: Pointer to the struct adc_auto_input + * @pdata: Pointer to the ab5500_btemp platform data + * @bat: Pointer to the ab5500_bm platform data + * @btemp_psy: Structure for BTEMP specific battery properties + * @events: Structure for information about events triggered + * @btemp_wq: Work queue for measuring the temperature periodically + * @btemp_periodic_work: Work for measuring the temperature periodically + */ +struct ab5500_btemp { + struct device *dev; + u8 chip_id; + int curr_source; + int bat_temp; + int prev_bat_temp; + struct list_head node; + struct ab5500 *parent; + struct ab5500_gpadc *gpadc; + struct adc_auto_input *gpadc_auto; + struct abx500_btemp_platform_data *pdata; + struct abx500_bm_data *bat; + struct power_supply btemp_psy; + struct ab5500_btemp_events events; + struct workqueue_struct *btemp_wq; + struct delayed_work btemp_periodic_work; +}; + +/* BTEMP power supply properties */ +static enum power_supply_property ab5500_btemp_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_TEMP, +}; + +static LIST_HEAD(ab5500_btemp_list); + +struct ab5500_btemp *ab5500_btemp_get(void) +{ + struct ab5500_btemp *di; + di = list_first_entry(&ab5500_btemp_list, struct ab5500_btemp, node); + + return di; +} + +/** + * ab5500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance + * @di: pointer to the ab5500_btemp structure + * @v_batctrl: measured batctrl voltage + * + * This function returns the battery resistance that is + * derived from the BATCTRL voltage. + * Returns value in Ohms. + */ +static int ab5500_btemp_batctrl_volt_to_res(struct ab5500_btemp *di, + int v_batctrl) +{ + int rbs; + + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL) { + /* + * If the battery has internal NTC, we use the current + * source to calculate the resistance, 7uA or 20uA + */ + rbs = v_batctrl * 1000 / di->curr_source; + } else { + /* + * BAT_CTRL is internally + * connected to 1.8V through a 10k resistor + */ + rbs = (10000 * (v_batctrl)) / (1800 - v_batctrl); + } + return rbs; +} + +/** + * ab5500_btemp_read_batctrl_voltage() - measure batctrl voltage + * @di: pointer to the ab5500_btemp structure + * + * This function returns the voltage on BATCTRL. Returns value in mV. + */ +static int ab5500_btemp_read_batctrl_voltage(struct ab5500_btemp *di) +{ + int vbtemp; + static int prev; + + vbtemp = ab5500_gpadc_convert(di->gpadc, BAT_CTRL); + if (vbtemp < 0) { + dev_err(di->dev, + "%s gpadc conversion failed, using previous value", + __func__); + return prev; + } + prev = vbtemp; + return vbtemp; +} + +/** + * ab5500_btemp_curr_source_enable() - enable/disable batctrl current source + * @di: pointer to the ab5500_btemp structure + * @enable: enable or disable the current source + * + * Enable or disable the current sources for the BatCtrl AD channel + */ +static int ab5500_btemp_curr_source_enable(struct ab5500_btemp *di, + bool enable) +{ + int ret = 0; + + /* Only do this for batteries with internal NTC */ + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) { + + dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_UART, + UART_MODE, RESIS_ID_MODE); + if (ret) { + dev_err(di->dev, + "%s failed setting resistance identification mode\n", + __func__); + return ret; + } + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_URI, + BAT_CUR_SRC, BAT_CTRL_15U_ENA); + if (ret) { + dev_err(di->dev, "%s failed enabling current source\n", + __func__); + goto disable_curr_source; + } + } else if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) { + dev_dbg(di->dev, "Disable BATCTRL curr source\n"); + + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_URI, + BAT_CUR_SRC, RESET); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + goto disable_curr_source; + } + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_UART, + UART_MODE, RESET); + if (ret) { + dev_err(di->dev, "%s failed disabling force comp\n", + __func__); + } + } + return ret; +disable_curr_source: + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_URI, + BAT_CUR_SRC, RESET); + if (ret) { + dev_err(di->dev, "%s failed disabling current source\n", + __func__); + } + return ret; +} + +/** + * ab5500_btemp_get_batctrl_res() - get battery resistance + * @di: pointer to the ab5500_btemp structure + * + * This function returns the battery pack identification resistance. + * Returns value in Ohms. + */ +static int ab5500_btemp_get_batctrl_res(struct ab5500_btemp *di) +{ + int ret; + int batctrl; + int res; + + ret = ab5500_btemp_curr_source_enable(di, true); + /* TODO: This delay has to be optimised */ + mdelay(1000); + if (ret) { + dev_err(di->dev, "%s curr source enable failed\n", __func__); + return ret; + } + + batctrl = ab5500_btemp_read_batctrl_voltage(di); + res = ab5500_btemp_batctrl_volt_to_res(di, batctrl); + + ret = ab5500_btemp_curr_source_enable(di, false); + if (ret) { + dev_err(di->dev, "%s curr source disable failed\n", __func__); + return ret; + } + + dev_dbg(di->dev, "%s batctrl: %d res: %d ", + __func__, batctrl, res); + + return res; +} + +/** + * ab5500_btemp_res_to_temp() - resistance to temperature + * @di: pointer to the ab5500_btemp structure + * @tbl: pointer to the resiatance to temperature table + * @tbl_size: size of the resistance to temperature table + * @res: resistance to calculate the temperature from + * + * This function returns the battery temperature in degrees Celcius + * based on the NTC resistance. + */ +static int ab5500_btemp_res_to_temp(struct ab5500_btemp *di, + const struct abx500_res_to_temp *tbl, int tbl_size, int res) +{ + int i, temp; + /* + * Calculate the formula for the straight line + * Simple interpolation if we are within + * the resistance table limits, extrapolate + * if resistance is outside the limits. + */ + if (res > tbl[0].resist) + i = 0; + else if (res <= tbl[tbl_size - 1].resist) + i = tbl_size - 2; + else { + i = 0; + while (!(res <= tbl[i].resist && + res > tbl[i + 1].resist)) + i++; + } + + temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) * + (res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); + return temp; +} + +/** + * ab5500_btemp_measure_temp() - measure battery temperature + * @di: pointer to the ab5500_btemp structure + * + * Returns battery temperature (on success) else the previous temperature + */ +static int ab5500_btemp_measure_temp(struct ab5500_btemp *di) +{ + int temp, ret; + static int prev; + int rbat, vntc; + int rntc = 0; + u8 id; + + id = di->bat->batt_id; + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && + id != BATTERY_UNKNOWN) { + rbat = ab5500_btemp_get_batctrl_res(di); + if (rbat < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", + __func__); + /* + * Return out-of-range temperature so that + * charging is stopped + */ + return BTEMP_THERMAL_LOW_LIMIT; + } + + temp = ab5500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rbat); + } else { + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB5500_BANK_FG_BATTCOM_ACC, AB5500_UART, + UART_MODE, ADOUT_10K_PULL_UP); + if (ret) { + dev_err(di->dev, + "failed to enable 10k pull up to Vadout\n"); + } + vntc = ab5500_gpadc_convert(di->gpadc, BTEMP_BALL); + if (vntc < 0) { + dev_err(di->dev, + "%s gpadc conversion failed," + " using previous value\n", __func__); + return prev; + } + /* + * The PCB NTC is sourced from 2.75v via a 10kOhm + * resistor. + */ + rntc = 10000 * vntc / (27500 - vntc); + + temp = ab5500_btemp_res_to_temp(di, + di->bat->bat_type[id].r_to_t_tbl, + di->bat->bat_type[id].n_temp_tbl_elements, rntc); + prev = temp; + } + dev_dbg(di->dev, "Battery temperature is %d\n", temp); + return temp; +} + +/** + * ab5500_btemp_id() - Identify the connected battery + * @di: pointer to the ab5500_btemp structure + * + * This function will try to identify the battery by reading the ID + * resistor. Some brands use a combined ID resistor with a NTC resistor to + * both be able to identify and to read the temperature of it. + */ +static int ab5500_btemp_id(struct ab5500_btemp *di) +{ + int res; + u8 i; + + di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA; + di->bat->batt_id = BATTERY_UNKNOWN; + + res = ab5500_btemp_get_batctrl_res(di); + if (res < 0) { + dev_err(di->dev, "%s get batctrl res failed\n", __func__); + return -ENXIO; + } + + /* BATTERY_UNKNOWN is defined on position 0, skip it! */ + for (i = BATTERY_UNKNOWN + 1; i < di->bat->n_btypes; i++) { + if ((res <= di->bat->bat_type[i].resis_high) && + (res >= di->bat->bat_type[i].resis_low)) { + dev_dbg(di->dev, "Battery detected on %s" + " low %d < res %d < high: %d" + " index: %d\n", + di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL ? + "BATCTRL" : "BATTEMP", + di->bat->bat_type[i].resis_low, res, + di->bat->bat_type[i].resis_high, i); + + di->bat->batt_id = i; + break; + } + } + + if (di->bat->batt_id == BATTERY_UNKNOWN) { + dev_warn(di->dev, "Battery identified as unknown" + ", resistance %d Ohm\n", res); + return -ENXIO; + } + + /* + * We only have to change current source if the + * detected type is Type 1, else we use the 7uA source + */ + if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && + di->bat->batt_id == 1) { + dev_dbg(di->dev, "Set BATCTRL current source to 15uA\n"); + di->curr_source = BTEMP_BATCTRL_CURR_SRC_15UA; + } + + return di->bat->batt_id; +} + +/** + * ab5500_btemp_periodic_work() - Measuring the temperature periodically + * @work: pointer to the work_struct structure + * + * Work function for measuring the temperature periodically + */ +static void ab5500_btemp_periodic_work(struct work_struct *work) +{ + struct ab5500_btemp *di = container_of(work, + struct ab5500_btemp, btemp_periodic_work.work); + + di->bat_temp = ab5500_btemp_measure_temp(di); + + if (di->bat_temp != di->prev_bat_temp) { + di->prev_bat_temp = di->bat_temp; + power_supply_changed(&di->btemp_psy); + } + di->bat->temp_now = di->bat_temp; + + /* Schedule a new measurement */ + queue_delayed_work(di->btemp_wq, + &di->btemp_periodic_work, + round_jiffies(20 * HZ)); +} + +/** + * ab5500_btemp_batt_removal_handler() - battery removal detected + * @irq: interrupt number + * @_di: void pointer that has to address of ab5500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_btemp_batt_removal_handler(int irq, void *_di) +{ + struct ab5500_btemp *di = _di; + dev_err(di->dev, "Battery removal detected!\n"); + + di->events.batt_rem = true; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab5500_btemp_batt_attach_handler() - battery insertion detected + * @irq: interrupt number + * @_di: void pointer that has to address of ab5500_btemp + * + * Returns IRQ status(IRQ_HANDLED) + */ +static irqreturn_t ab5500_btemp_batt_attach_handler(int irq, void *_di) +{ + struct ab5500_btemp *di = _di; + dev_err(di->dev, "Battery attached!\n"); + + di->events.batt_rem = false; + power_supply_changed(&di->btemp_psy); + + return IRQ_HANDLED; +} + +/** + * ab5500_btemp_periodic() - Periodic temperature measurements + * @di: pointer to the ab5500_btemp structure + * @enable: enable or disable periodic temperature measurements + * + * Starts of stops periodic temperature measurements. Periodic measurements + * should only be done when a charger is connected. + */ +static void ab5500_btemp_periodic(struct ab5500_btemp *di, + bool enable) +{ + dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n", + enable); + + if (enable) + queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0); + else + cancel_delayed_work_sync(&di->btemp_periodic_work); +} + +/** + * ab5500_btemp_get_property() - get the btemp properties + * @psy: pointer to the power_supply structure + * @psp: pointer to the power_supply_property structure + * @val: pointer to the power_supply_propval union + * + * This function gets called when an application tries to get the btemp + * properties by reading the sysfs files. + * online: presence of the battery + * present: presence of the battery + * technology: battery technology + * temp: battery temperature + * Returns error code in case of failure else 0(on success) + */ +static int ab5500_btemp_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ab5500_btemp *di; + + di = to_ab5500_btemp_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + case POWER_SUPPLY_PROP_ONLINE: + if (di->events.batt_rem) + val->intval = 0; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = di->bat->bat_type[di->bat->batt_id].name; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = di->bat_temp * 10; + break; + default: + return -EINVAL; + } + return 0; +} + +static int ab5500_btemp_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab5500_btemp *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab5500_btemp_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->type) { + case POWER_SUPPLY_TYPE_USB: + /* USB disconnected */ + if (!ret.intval && di->events.usb_conn) { + di->events.usb_conn = false; + ab5500_btemp_periodic(di, + false); + } + /* USB connected */ + else if (ret.intval && !di->events.usb_conn) { + di->events.usb_conn = true; + ab5500_btemp_periodic(di, true); + } + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab5500_btemp_external_power_changed() - callback for power supply changes + * @psy: pointer to the structure power_supply + * + * This function is pointing to the function pointer external_power_changed + * of the structure power_supply. + * This function gets executed when there is a change in the external power + * supply to the btemp. + */ +static void ab5500_btemp_external_power_changed(struct power_supply *psy) +{ + struct ab5500_btemp *di = to_ab5500_btemp_device_info(psy); + + class_for_each_device(power_supply_class, NULL, + &di->btemp_psy, ab5500_btemp_get_ext_psy_data); +} + +/* ab5500 btemp driver interrupts and their respective isr */ +static struct ab5500_btemp_interrupts ab5500_btemp_irq[] = { + {"BATT_REMOVAL", ab5500_btemp_batt_removal_handler}, + {"BATT_ATTACH", ab5500_btemp_batt_attach_handler}, +}; +static int ab5500_btemp_bat_temp_trig(int mux) +{ + struct ab5500_btemp *di = ab5500_btemp_get(); + + if (di->bat_temp < BTEMP_THERMAL_LOW_LIMIT) { + dev_err(di->dev, + "battery temp less than lower threshold (-10 deg cel)\n"); + power_supply_changed(&di->btemp_psy); + } else if (di->bat_temp > BTEMP_THERMAL_HIGH_LIMIT_62) { + dev_err(di->dev, "battery temp greater them max threshold\n"); + power_supply_changed(&di->btemp_psy); + } + return 0;; +} + +static int ab5500_btemp_auto_temp(struct ab5500_btemp *di) +{ + struct adc_auto_input *auto_ip; + int ret = 0; + + auto_ip = kzalloc(sizeof(struct adc_auto_input), GFP_KERNEL); + if (!auto_ip) { + dev_err(di->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + auto_ip->mux = BTEMP_BALL; + auto_ip->freq = MS500; + auto_ip->min = BTEMP_THERMAL_LOW_LIMIT; + auto_ip->max = BTEMP_THERMAL_HIGH_LIMIT_62; + auto_ip->auto_adc_callback = ab5500_btemp_bat_temp_trig; + di->gpadc_auto = auto_ip; + ret = ab5500_gpadc_convert_auto(di->gpadc, di->gpadc_auto); + if (ret) + dev_err(di->dev, + "failed to set auto trigger for battery temp\n"); + return ret; +} + +#if defined(CONFIG_PM) +static int ab5500_btemp_resume(struct platform_device *pdev) +{ + struct ab5500_btemp *di = platform_get_drvdata(pdev); + + if (di->events.usb_conn) + ab5500_btemp_periodic(di, true); + + return 0; +} + +static int ab5500_btemp_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab5500_btemp *di = platform_get_drvdata(pdev); + + if (di->events.usb_conn) + ab5500_btemp_periodic(di, false); + + return 0; +} +#else +#define ab5500_btemp_suspend NULL +#define ab5500_btemp_resume NULL +#endif + +static int __devexit ab5500_btemp_remove(struct platform_device *pdev) +{ + struct ab5500_btemp *di = platform_get_drvdata(pdev); + int i, irq; + + /* Disable interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_btemp_irq[i].name); + free_irq(irq, di); + } + + /* Delete the work queue */ + destroy_workqueue(di->btemp_wq); + + flush_scheduled_work(); + power_supply_unregister(&di->btemp_psy); + platform_set_drvdata(pdev, NULL); + kfree(di->gpadc_auto); + kfree(di); + + return 0; +} + +static int __devinit ab5500_btemp_probe(struct platform_device *pdev) +{ + int irq, i, ret = 0; + struct abx500_bm_plat_data *plat_data; + + struct ab5500_btemp *di = + kzalloc(sizeof(struct ab5500_btemp), GFP_KERNEL); + if (!di) + return -ENOMEM; + + /* get parent data */ + di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); + di->gpadc = ab5500_gpadc_get("ab5500-adc.0"); + + plat_data = pdev->dev.platform_data; + di->pdata = plat_data->btemp; + di->bat = plat_data->battery; + + /* get btemp specific platform data */ + if (!di->pdata) { + dev_err(di->dev, "no btemp platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* get battery specific platform data */ + if (!di->bat) { + dev_err(di->dev, "no battery platform data supplied\n"); + ret = -EINVAL; + goto free_device_info; + } + + /* BTEMP supply */ + di->btemp_psy.name = "ab5500_btemp"; + di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->btemp_psy.properties = ab5500_btemp_props; + di->btemp_psy.num_properties = ARRAY_SIZE(ab5500_btemp_props); + di->btemp_psy.get_property = ab5500_btemp_get_property; + di->btemp_psy.supplied_to = di->pdata->supplied_to; + di->btemp_psy.num_supplicants = di->pdata->num_supplicants; + di->btemp_psy.external_power_changed = + ab5500_btemp_external_power_changed; + + + /* Create a work queue for the btemp */ + di->btemp_wq = + create_singlethread_workqueue("ab5500_btemp_wq"); + if (di->btemp_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_device_info; + } + + /* Init work for measuring temperature periodically */ + INIT_DELAYED_WORK_DEFERRABLE(&di->btemp_periodic_work, + ab5500_btemp_periodic_work); + + /* Get Chip ID of the ABB ASIC */ + ret = abx500_get_chip_id(di->dev); + if (ret < 0) { + dev_err(di->dev, "failed to get chip ID\n"); + goto free_btemp_wq; + } + di->chip_id = ret; + dev_dbg(di->dev, "ab5500 CID is: 0x%02x\n", + di->chip_id); + + /* Identify the battery */ + if (ab5500_btemp_id(di) < 0) + dev_warn(di->dev, "failed to identify the battery\n"); + + /* Measure temperature once initially */ + di->bat_temp = ab5500_btemp_measure_temp(di); + di->bat->temp_now = di->bat_temp; + + /* Register BTEMP power supply class */ + ret = power_supply_register(di->dev, &di->btemp_psy); + if (ret) { + dev_err(di->dev, "failed to register BTEMP psy\n"); + goto free_btemp_wq; + } + + /* Register interrupts */ + for (i = 0; i < ARRAY_SIZE(ab5500_btemp_irq); i++) { + irq = platform_get_irq_byname(pdev, ab5500_btemp_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab5500_btemp_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab5500_btemp_irq[i].name, di); + + if (ret) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab5500_btemp_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab5500_btemp_irq[i].name, irq, ret); + } + ret = ab5500_btemp_auto_temp(di); + if (ret) { + dev_err(di->dev, + "failed to register auto trigger for battery temp\n"); + goto free_irq; + } + + platform_set_drvdata(pdev, di); + + dev_info(di->dev, "probe success\n"); + return ret; + +free_irq: + power_supply_unregister(&di->btemp_psy); + + /* We also have to free all successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab5500_btemp_irq[i].name); + free_irq(irq, di); + } +free_btemp_wq: + destroy_workqueue(di->btemp_wq); +free_device_info: + kfree(di); + + return ret; +} + +static struct platform_driver ab5500_btemp_driver = { + .probe = ab5500_btemp_probe, + .remove = __devexit_p(ab5500_btemp_remove), + .suspend = ab5500_btemp_suspend, + .resume = ab5500_btemp_resume, + .driver = { + .name = "ab5500-btemp", + .owner = THIS_MODULE, + }, +}; + +static int __init ab5500_btemp_init(void) +{ + return platform_driver_register(&ab5500_btemp_driver); +} + +static void __exit ab5500_btemp_exit(void) +{ + platform_driver_unregister(&ab5500_btemp_driver); +} + +subsys_initcall_sync(ab5500_btemp_init); +module_exit(ab5500_btemp_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); +MODULE_ALIAS("platform:ab5500-btemp"); +MODULE_DESCRIPTION("AB5500 battery temperature driver"); -- cgit v1.2.3 From fec73e6898af8fa735c0ab7455f578537e5bf46a Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 26 May 2011 16:51:49 +0530 Subject: mach-ux500: platform data for ab5500 battery driver ST-Ericsson Linux next: Not Tested ST-Ericsson ID: WP256401 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I81619ad06bc6f89962b2cfd4404e911cc77082ec Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23153 Reviewed-by: Johan PALSSON Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- arch/arm/mach-ux500/board-u5500-bm.c | 496 +++++++++++++++++++++++++++++++++++ arch/arm/mach-ux500/board-u5500-bm.h | 26 ++ 2 files changed, 522 insertions(+) create mode 100644 arch/arm/mach-ux500/board-u5500-bm.c create mode 100644 arch/arm/mach-ux500/board-u5500-bm.h diff --git a/arch/arm/mach-ux500/board-u5500-bm.c b/arch/arm/mach-ux500/board-u5500-bm.c new file mode 100644 index 00000000000..2616d7fa517 --- /dev/null +++ b/arch/arm/mach-ux500/board-u5500-bm.c @@ -0,0 +1,496 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U5500 board specific charger and battery initialization parameters. + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson + * Karl Komierowski + */ + +#include +#include +#include +#include +#include "board-u5500-bm.h" + +#ifdef CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL +/* + * These are the defined batteries that uses a NTC and ID resistor placed + * inside of the battery pack. + * Note that the abx500_res_to_temp table must be strictly sorted by falling resistance + * values to work. + */ +static struct abx500_res_to_temp temp_tbl_type1[] = { + {-20, 67400}, + { 0, 49200}, + { 5, 44200}, + { 10, 39400}, + { 15, 35000}, + { 20, 31000}, + { 25, 27400}, + { 30, 24300}, + { 35, 21700}, + { 40, 19400}, + { 45, 17500}, + { 50, 15900}, + { 55, 14600}, + { 60, 13500}, + { 65, 12500}, + { 70, 11800}, + {100, 9200}, +}; + +static struct abx500_res_to_temp temp_tbl_type2[] = { + {-20, 180700}, + { 0, 160000}, + { 5, 152700}, + { 10, 144900}, + { 15, 136800}, + { 20, 128700}, + { 25, 121000}, + { 30, 113800}, + { 35, 107300}, + { 40, 101500}, + { 45, 96500}, + { 50, 92200}, + { 55, 88600}, + { 60, 85600}, + { 65, 83000}, + { 70, 80900}, + {100, 73900}, +}; + +static struct abx500_res_to_temp temp_tbl_A[] = { + {-5, 53407}, + { 0, 48594}, + { 5, 43804}, + {10, 39188}, + {15, 34870}, + {20, 30933}, + {25, 27422}, + {30, 24347}, + {35, 21694}, + {40, 19431}, + {45, 17517}, + {50, 15908}, + {55, 14561}, + {60, 13437}, + {65, 12500}, +}; + +static struct abx500_res_to_temp temp_tbl_B[] = { + {-5, 165418}, + { 0, 159024}, + { 5, 151921}, + {10, 144300}, + {15, 136424}, + {20, 128565}, + {25, 120978}, + {30, 113875}, + {35, 107397}, + {40, 101629}, + {45, 96592}, + {50, 92253}, + {55, 88569}, + {60, 85461}, + {65, 82869}, +}; + +static struct abx500_v_to_cap cap_tbl_type1[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; + +static struct abx500_v_to_cap cap_tbl_A[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; +static struct abx500_v_to_cap cap_tbl_B[] = { + {4161, 100}, + {4124, 98}, + {4044, 90}, + {4003, 85}, + {3966, 80}, + {3933, 75}, + {3888, 67}, + {3849, 60}, + {3813, 55}, + {3787, 47}, + {3772, 30}, + {3751, 25}, + {3718, 20}, + {3681, 16}, + {3660, 14}, + {3589, 10}, + {3546, 7}, + {3495, 4}, + {3404, 2}, + {3250, 0}, +}; +#endif +static struct abx500_v_to_cap cap_tbl[] = { + {4186, 100}, + {4163, 99}, + {4114, 95}, + {4068, 90}, + {3990, 80}, + {3926, 70}, + {3898, 65}, + {3866, 60}, + {3833, 55}, + {3812, 50}, + {3787, 40}, + {3768, 30}, + {3747, 25}, + {3730, 20}, + {3705, 15}, + {3699, 14}, + {3684, 12}, + {3672, 9}, + {3657, 7}, + {3638, 6}, + {3556, 4}, + {3424, 2}, + {3317, 1}, + {3094, 0}, +}; + +/* + * Note that the abx500_res_to_temp table must be strictly sorted by falling + * resistance values to work. + */ +static struct abx500_res_to_temp temp_tbl[] = { + {-5, 214834}, + { 0, 162943}, + { 5, 124820}, + {10, 96520}, + {15, 75306}, + {20, 59254}, + {25, 47000}, + {30, 37566}, + {35, 30245}, + {40, 24520}, + {45, 20010}, + {50, 16432}, + {55, 13576}, + {60, 11280}, + {65, 9425}, +}; + +static const struct abx500_battery_type bat_type[] = { + [BATTERY_UNKNOWN] = { + /* First element always represent the UNKNOWN battery */ + .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, + .resis_high = 0, + .resis_low = 0, + .battery_resistance = 300, + .charge_full_design = 612, + .nominal_voltage = 3700, + .termination_vol = 4050, + .termination_curr = 200, + .recharge_vol = 3990, + .normal_cur_lvl = 400, + .normal_vol_lvl = 4100, + .maint_a_cur_lvl = 400, + .maint_a_vol_lvl = 4050, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 400, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + +#ifdef CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 70000, + .resis_low = 8200, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_type1), + .r_to_t_tbl = temp_tbl_type1, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_type1), + .v_to_cap_tbl = cap_tbl_type1, + + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 165418, + .resis_low = 82869, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_B), + .r_to_t_tbl = temp_tbl_B, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_B), + .v_to_cap_tbl = cap_tbl_B, + }, +#else +/* + * These are the batteries that doesn't have an internal NTC resistor to measure + * its temperature. The temperature in this case is measure with a NTC placed + * near the battery but on the PCB. + */ + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 76000, + .resis_low = 53000, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 30000, + .resis_low = 10000, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 95000, + .resis_low = 76001, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_vol = 4025, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4025, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, +#endif +}; + +static char *ab5500_charger_supplied_to[] = { + "abx500_chargalg", + "ab5500_fg", + "ab5500_btemp", +}; + +static char *ab5500_btemp_supplied_to[] = { + "abx500_chargalg", + "ab5500_fg", +}; + +static char *ab5500_fg_supplied_to[] = { + "abx500_chargalg", +}; + +static char *abx500_chargalg_supplied_to[] = { + "ab5500_fg", +}; + +struct abx500_charger_platform_data ab5500_charger_plat_data = { + .supplied_to = ab5500_charger_supplied_to, + .num_supplicants = ARRAY_SIZE(ab5500_charger_supplied_to), +}; + +struct abx500_btemp_platform_data ab5500_btemp_plat_data = { + .supplied_to = ab5500_btemp_supplied_to, + .num_supplicants = ARRAY_SIZE(ab5500_btemp_supplied_to), +}; + +struct abx500_fg_platform_data ab5500_fg_plat_data = { + .supplied_to = ab5500_fg_supplied_to, + .num_supplicants = ARRAY_SIZE(ab5500_fg_supplied_to), +}; + +struct abx500_chargalg_platform_data abx500_chargalg_plat_data = { + .supplied_to = abx500_chargalg_supplied_to, + .num_supplicants = ARRAY_SIZE(abx500_chargalg_supplied_to), +}; + +static const struct abx500_bm_capacity_levels cap_levels = { + .critical = 2, + .low = 10, + .normal = 70, + .high = 95, + .full = 100, +}; + +static const struct abx500_fg_parameters fg = { + .recovery_sleep_timer = 10, + .recovery_total_time = 100, + .init_timer = 1, + .init_discard_time = 5, + .init_total_time = 40, + .high_curr_time = 60, + .accu_charging = 30, + .accu_high_curr = 30, + .high_curr_threshold = 50, + .lowbat_threshold = 3100, +}; + +static const struct abx500_maxim_parameters maxi_params = { + .ena_maxi = true, + .chg_curr = 910, + .wait_cycles = 10, + .charger_curr_step = 100, +}; + +static const struct abx500_bm_charger_parameters chg = { + .usb_volt_max = 5500, + .usb_curr_max = 1500, + .ac_volt_max = 7500, + .ac_curr_max = 1500, +}; + +struct abx500_bm_data ab5500_bm_data = { + .temp_under = 3, + .temp_low = 8, + /* TODO: Need to verify the temp values */ + .temp_high = 155, + .temp_over = 160, + .main_safety_tmr_h = 4, + .usb_safety_tmr_h = 4, + .bkup_bat_v = 0x00, + .bkup_bat_i = 0x00, + .no_maintenance = true, +#ifdef CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL + .adc_therm = ABx500_ADC_THERM_BATCTRL, +#else + .adc_therm = ABx500_ADC_THERM_BATTEMP, +#endif + .chg_unknown_bat = false, + .enable_overshoot = false, + .fg_res = 20, + .cap_levels = &cap_levels, + .bat_type = bat_type, + .n_btypes = ARRAY_SIZE(bat_type), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .maxi = &maxi_params, + .chg_params = &chg, + .fg_params = &fg, +}; + +/* ab5500 energy management platform data */ +struct abx500_bm_plat_data abx500_bm_pt_data = { + .battery = &ab5500_bm_data, + .charger = &ab5500_charger_plat_data, + .btemp = &ab5500_btemp_plat_data, + .fg = &ab5500_fg_plat_data, + .chargalg = &abx500_chargalg_plat_data, +}; diff --git a/arch/arm/mach-ux500/board-u5500-bm.h b/arch/arm/mach-ux500/board-u5500-bm.h new file mode 100644 index 00000000000..a6346905911 --- /dev/null +++ b/arch/arm/mach-ux500/board-u5500-bm.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U5500 board specific charger and battery initialization parameters. + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson + * Karl Komierowski + */ + +#ifndef __BOARD_U5500_BM_H +#define __BOARD_U5500_BM_H + +#include + +extern struct abx500_charger_platform_data ab5500_charger_plat_data; +extern struct abx500_btemp_platform_data ab5500_btemp_plat_data; +extern struct abx500_fg_platform_data ab5500_fg_plat_data; +extern struct abx500_chargalg_platform_data abx500_chargalg_plat_data; +extern struct abx500_bm_data ab5500_bm_data; +extern struct abx500_bm_plat_data abx500_bm_pt_data; + +#endif -- cgit v1.2.3 From c4a31d036bca0d09860446cbbe7157fc73e52aab Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 26 May 2011 16:52:33 +0530 Subject: power: Add ab5500 battery, temp, fg, driver Enable compilation of ab5500 energy management driver. It depends on ab5500 mfd and ab5500 gpadc driver. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: WP256401 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I072f6bfc643897d3a43ad478f05d78ef2fdbffd8 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23154 Reviewed-by: Johan PALSSON Reviewed-by: Srinidhi KASAGAR --- drivers/power/Kconfig | 13 +++++++++++++ drivers/power/Makefile | 1 + 2 files changed, 14 insertions(+) diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index e167394bc15..697a040baf3 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -248,6 +248,19 @@ config AB8500_BATTERY_THERM_ON_BATCTRL Say Y to enable battery temperature measurements using thermistor connected on BATCTRL ADC. +config AB5500_BM + bool "AB5500 Battery Management Driver" + depends on AB5500_CORE && MACH_B5500 + help + Say Y to include support for AB5500 battery management. + +config AB5500_BATTERY_THERM_ON_BATCTRL + bool "Thermistor connected on BATCTRL ADC" + depends on AB5500_BM + help + Say Y to enable battery temperature measurements using + thermistor connected on BATCTRL ADC. + config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" depends on MFD_MAX8997 && REGULATOR_MAX8997 diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 7f9f62dcd91..6585c0aee99 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -37,5 +37,6 @@ obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o ab8500_chargalg.o +obj-$(CONFIG_AB5500_BM) += ab5500_charger.o abx500_chargalg.o ab5500_fg.o ab5500_btemp.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o -- cgit v1.2.3 From 82ce97f2df640e2f9cf777b46a86fa7eb6b78634 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 27 Jun 2011 11:35:42 +0200 Subject: power: u5500: Fix and activate Battery Management for U5500 Signed-off-by: Philippe Langlais --- arch/arm/configs/u8500_defconfig | 3 +++ arch/arm/mach-ux500/board-u5500-bm.c | 1 - drivers/power/Kconfig | 2 +- drivers/power/ab5500_btemp.c | 1 - drivers/power/ab5500_charger.c | 1 - drivers/power/ab5500_fg.c | 1 - drivers/power/abx500_chargalg.c | 1 - 7 files changed, 4 insertions(+), 6 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index d39c3b58956..58ffb23c617 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -152,6 +152,9 @@ CONFIG_GPIO_TC3589X=y CONFIG_AB8500_GPIO=y CONFIG_POWER_SUPPLY=y CONFIG_AB8500_BM=y +CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=y +CONFIG_AB5500_BM=y +CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL=y CONFIG_SENSORS_AB8500=y CONFIG_SENSORS_DB8500=y CONFIG_SENSORS_LSM303DLH=y diff --git a/arch/arm/mach-ux500/board-u5500-bm.c b/arch/arm/mach-ux500/board-u5500-bm.c index 2616d7fa517..7cfc6386b35 100644 --- a/arch/arm/mach-ux500/board-u5500-bm.c +++ b/arch/arm/mach-ux500/board-u5500-bm.c @@ -13,7 +13,6 @@ #include #include -#include #include #include "board-u5500-bm.h" diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 697a040baf3..86f782f8127 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -250,7 +250,7 @@ config AB8500_BATTERY_THERM_ON_BATCTRL config AB5500_BM bool "AB5500 Battery Management Driver" - depends on AB5500_CORE && MACH_B5500 + depends on AB5500_CORE && AB8500_GPADC && ARCH_U8500 help Say Y to include support for AB5500 battery management. diff --git a/drivers/power/ab5500_btemp.c b/drivers/power/ab5500_btemp.c index 57241c96f60..8b6ff82a86b 100644 --- a/drivers/power/ab5500_btemp.c +++ b/drivers/power/ab5500_btemp.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include diff --git a/drivers/power/ab5500_charger.c b/drivers/power/ab5500_charger.c index 1b6bfa21d00..2fd7888ad9d 100644 --- a/drivers/power/ab5500_charger.c +++ b/drivers/power/ab5500_charger.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include diff --git a/drivers/power/ab5500_fg.c b/drivers/power/ab5500_fg.c index 1c866e8fec5..919af5faadf 100644 --- a/drivers/power/ab5500_fg.c +++ b/drivers/power/ab5500_fg.c @@ -25,7 +25,6 @@ #include #include #include -#include #include static LIST_HEAD(ab5500_fg_list); diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c index c85e284e04a..bb0fa42b109 100644 --- a/drivers/power/abx500_chargalg.c +++ b/drivers/power/abx500_chargalg.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include -- cgit v1.2.3 From 375e4f77365a70ec9c5e2558e4f36d3df487a740 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 26 May 2011 16:57:32 +0530 Subject: mfd: ab5500-gpadc: Discard adc converted value if < 5 ADC Convertion made on BDATA, using resistance mode, sometimes provides a null value or a value < 5. Discard susch values and provide the previous converted value ST-Ericsson Linux next: Not Tested ST-Ericsson ID: WP332221 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ib0eaaed520789b91ea77b2a90c640d33a6c913f6 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23607 Reviewed-by: Johan PALSSON Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/mfd/ab5500-gpadc.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/ab5500-gpadc.c b/drivers/mfd/ab5500-gpadc.c index e90bd9a3aaa..dbfc9c30e38 100644 --- a/drivers/mfd/ab5500-gpadc.c +++ b/drivers/mfd/ab5500-gpadc.c @@ -47,7 +47,7 @@ #define AB5500_GPADC_AUTO_TRIG_ADOUT0_CTRL 0x34 #define AB5500_GPADC_AUTO_TRIG_ADOUT1_CTRL 0x35 #define AB5500_GPADC_AUTO_TRIG0_MUX_CTRL 0x37 -#define AB5500_GPADC_AUTO_XALTEMP_CTRL 0x57 +#define AB5500_GPADC_AUTO_XTALTEMP_CTRL 0x57 #define AB5500_GPADC_KELVIN_CTRL 0xFE /* gpadc constants */ @@ -194,6 +194,7 @@ struct ab5500_gpadc { struct mutex ab5500_gpadc_lock; struct regulator *regu; int irq; + int prev_bdata; spinlock_t gpadc_auto_lock; struct adc_auto_trigger adc_trig[N_AUTO_TRIGGER]; struct workqueue_struct *gpadc_wq; @@ -358,7 +359,7 @@ int ab5500_gpadc_convert(struct ab5500_gpadc *gpadc, u8 input) break; case XTAL_TEMP: ret = abx500_mask_and_set_register_interruptible(gpadc->dev, - AB5500_BANK_ADC, AB5500_GPADC_AUTO_XALTEMP_CTRL, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_XTALTEMP_CTRL, ADC_XTAL_FORCE_MASK, ADC_XTAL_FORCE_EN); if (ret < 0) { dev_err(gpadc->dev, "gpadc: fail to set xtaltemp\n"); @@ -454,7 +455,7 @@ int ab5500_gpadc_convert(struct ab5500_gpadc *gpadc, u8 input) */ if (input == XTAL_TEMP) { ret = abx500_mask_and_set_register_interruptible(gpadc->dev, - AB5500_BANK_ADC, AB5500_GPADC_AUTO_XALTEMP_CTRL, + AB5500_BANK_ADC, AB5500_GPADC_AUTO_XTALTEMP_CTRL, ADC_XTAL_FORCE_MASK, ADC_XTAL_FORCE_DI); if (ret < 0) { dev_err(gpadc->dev, @@ -478,6 +479,17 @@ int ab5500_gpadc_convert(struct ab5500_gpadc *gpadc, u8 input) } data = (high_data << 2) | (low_data >> 6); + if (input == BAT_CTRL || input == BTEMP_BALL) { + /* + * TODO: Re-check with h/w team + * discard null or value < 5, as there is some error + * in conversion + */ + if (data < 5) + data = gpadc->prev_bdata; + else + gpadc->prev_bdata = data; + } result = ab5500_gpadc_ad_to_voltage(gpadc, input, data); mutex_unlock(&gpadc->ab5500_gpadc_lock); -- cgit v1.2.3 From 2fd2f873e9e36306b4032cd1a7c5b80aaf9db9f1 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Tue, 14 Jun 2011 11:56:20 +0200 Subject: drivers: watchdog: Add ux500-watchdog driver This driver kicks a watchdog that is located in the prcmu firmware. ST-Ericsson Linux next: Not tested, ask SSM for ER ST-Ericsson ID: 339924 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I5fcabc0a409f18914129f6a5c8e17f4267974c61 Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/25029 Reviewed-by: QATEST Reviewed-by: Linus WALLEIJ --- drivers/watchdog/Kconfig | 16 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/ux500_wdt.c | 410 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 427 insertions(+) create mode 100644 drivers/watchdog/ux500_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 86b0735e6aa..7e173851411 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -287,6 +287,22 @@ config COH901327_WATCHDOG This watchdog is used to reset the system and thus cannot be compiled as a module. +config UX500_WATCHDOG + bool "ST-Ericsson UX500 watchdog" + depends on ARCH_U8500 + default y if MACH_U8500 + help + Say Y here to include Watchdog timer support for the + watchdog existing in the prcmu of ST-Ericsson UX500 series platforms. + This watchdog is used to reset the system and thus cannot be + compiled as a module. + +config UX500_WATCHDOG_DEBUG + bool "ST-Ericsson UX500 watchdog DEBUG" + depends on ARCH_U8500 && DEBUG_FS + help + Say Y here to add various debugfs entries in wdog/ + config TWL4030_WATCHDOG tristate "TWL4030 Watchdog" depends on TWL4030_CORE diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 55bd5740e91..02be29b78f8 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o +obj-$(CONFIG_UX500_WATCHDOG) += ux500_wdt.o obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o obj-$(CONFIG_ADX_WATCHDOG) += adx_wdt.o diff --git a/drivers/watchdog/ux500_wdt.c b/drivers/watchdog/ux500_wdt.c new file mode 100644 index 00000000000..fd343197681 --- /dev/null +++ b/drivers/watchdog/ux500_wdt.c @@ -0,0 +1,410 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Jonas Aaberg for ST-Ericsson + * + * Heavily based upon geodewdt.c + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define WATCHDOG_TIMEOUT 600 /* 10 minutes */ + +#define WDT_FLAGS_OPEN 1 +#define WDT_FLAGS_ORPHAN 2 + +static unsigned long wdt_flags; + +static int timeout = WATCHDOG_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. 1<= timeout <=131, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) "."); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +static u8 wdog_id; +static bool wdt_en; +static bool wdt_auto_off = true; +static bool safe_close; + +static int ux500_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(WDT_FLAGS_OPEN, &wdt_flags)) + return -EBUSY; + + if (!test_and_clear_bit(WDT_FLAGS_ORPHAN, &wdt_flags)) + __module_get(THIS_MODULE); + + prcmu_enable_a9wdog(wdog_id); + wdt_en = true; + + return nonseekable_open(inode, file); +} + +static int ux500_wdt_release(struct inode *inode, struct file *file) +{ + if (safe_close) { + prcmu_disable_a9wdog(wdog_id); + module_put(THIS_MODULE); + } else { + pr_crit("Unexpected close - watchdog is not stopping.\n"); + prcmu_kick_a9wdog(wdog_id); + + set_bit(WDT_FLAGS_ORPHAN, &wdt_flags); + } + + clear_bit(WDT_FLAGS_OPEN, &wdt_flags); + safe_close = false; + return 0; +} + +static ssize_t ux500_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + if (!len) + return len; + + if (!nowayout) { + size_t i; + safe_close = false; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + + if (c == 'V') + safe_close = true; + } + } + + prcmu_kick_a9wdog(wdog_id); + + return len; +} + +static long ux500_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int interval; + + static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "UX500 WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, + sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + { + int options; + int ret = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + prcmu_disable_a9wdog(wdog_id); + wdt_en = false; + ret = 0; + } + + if (options & WDIOS_ENABLECARD) { + prcmu_enable_a9wdog(wdog_id); + wdt_en = true; + ret = 0; + } + + return ret; + } + case WDIOC_KEEPALIVE: + return prcmu_kick_a9wdog(wdog_id); + + case WDIOC_SETTIMEOUT: + if (get_user(interval, p)) + return -EFAULT; + + /* 28 bit resolution in ms, becomes 268435455 ms */ + if (interval > 26843 || interval < 0) + return -EINVAL; + timeout = interval; + prcmu_disable_a9wdog(wdog_id); + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + + /* Fall through */ + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + + default: + return -ENOTTY; + } + + return 0; +} + +static const struct file_operations ux500_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ux500_wdt_write, + .unlocked_ioctl = ux500_wdt_ioctl, + .open = ux500_wdt_open, + .release = ux500_wdt_release, +}; + +static struct miscdevice ux500_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &ux500_wdt_fops, +}; + +#ifdef CONFIG_UX500_WATCHDOG_DEBUG + +enum wdog_dbg { + WDOG_DBG_CONFIG, + WDOG_DBG_LOAD, + WDOG_DBG_KICK, + WDOG_DBG_EN, + WDOG_DBG_DIS, +}; + +static ssize_t wdog_dbg_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + unsigned long val; + int err; + enum wdog_dbg v = (enum wdog_dbg)((struct seq_file *) + (file->private_data))->private; + + switch(v) { + case WDOG_DBG_CONFIG: + err = kstrtoul_from_user(user_buf, count, 0, &val); + + if (!err) { + wdt_auto_off = val != 0; + (void) prcmu_config_a9wdog(1, + wdt_auto_off); + } + else { + pr_err("ux500_wdt:dbg: unknown value\n"); + } + break; + case WDOG_DBG_LOAD: + err = kstrtoul_from_user(user_buf, count, 0, &val); + + if (!err) { + timeout = val; + /* Convert seconds to ms */ + prcmu_disable_a9wdog(wdog_id); + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + } + else { + pr_err("ux500_wdt:dbg: unknown value\n"); + } + break; + case WDOG_DBG_KICK: + (void) prcmu_kick_a9wdog(wdog_id); + break; + case WDOG_DBG_EN: + wdt_en = true; + (void) prcmu_enable_a9wdog(wdog_id); + break; + case WDOG_DBG_DIS: + wdt_en = false; + (void) prcmu_disable_a9wdog(wdog_id); + break; + } + + return count; +} + +static int wdog_dbg_read(struct seq_file *s, void *p) +{ + enum wdog_dbg v = (enum wdog_dbg)s->private; + + switch(v) { + case WDOG_DBG_CONFIG: + seq_printf(s,"wdog is on id %d, auto off on sleep: %s\n", + (int)wdog_id, + wdt_auto_off ? "enabled": "disabled"); + break; + case WDOG_DBG_LOAD: + /* In 1s */ + seq_printf(s, "wdog load is: %d s\n", + timeout); + break; + case WDOG_DBG_KICK: + break; + case WDOG_DBG_EN: + case WDOG_DBG_DIS: + seq_printf(s, "wdog is %sabled.\n", + wdt_en ? "en" : "dis"); + break; + } + return 0; +} + +static int wdog_dbg_open(struct inode *inode, + struct file *file) +{ + return single_open(file, wdog_dbg_read, inode->i_private); +} + +static const struct file_operations wdog_dbg_fops = { + .open = wdog_dbg_open, + .write = wdog_dbg_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int __init wdog_dbg_init(void) +{ + struct dentry *wdog_dir; + + wdog_dir = debugfs_create_dir("wdog", NULL); + if (IS_ERR_OR_NULL(wdog_dir)) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_u8("id", + S_IWUGO | S_IRUGO, wdog_dir, + &wdog_id))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("config", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_CONFIG, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("load", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_LOAD, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("kick", + S_IWUGO, wdog_dir, + (void *)WDOG_DBG_KICK, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("enable", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_EN, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("disable", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_DIS, + &wdog_dbg_fops))) + goto fail; + + return 0; +fail: + pr_err("ux500:wdog: Failed to initialize wdog dbg.\n"); + debugfs_remove_recursive(wdog_dir); + + return -EFAULT; +} + +#else +static inline int __init wdog_dbg_init(void) +{ + return 0; +} +#endif + +static int __init ux500_wdt_probe(struct platform_device *pdev) +{ + int ret; + + /* Number of watch dogs */ + prcmu_config_a9wdog(1, wdt_auto_off); + /* convert to ms */ + prcmu_load_a9wdog(wdog_id, timeout * 1000); + + ret = misc_register(&ux500_wdt_miscdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register misc.\n"); + return ret; + } + + ret = wdog_dbg_init(); + if (ret < 0) + goto fail; + + dev_info(&pdev->dev, "initialized.\n"); + + return 0; +fail: + misc_deregister(&ux500_wdt_miscdev); + return ret; +} + +static int __exit ux500_wdt_remove(struct platform_device *dev) +{ + prcmu_disable_a9wdog(wdog_id); + wdt_en = false; + misc_deregister(&ux500_wdt_miscdev); + return 0; +} + +static struct platform_driver ux500_wdt_driver = { + .remove = __exit_p(ux500_wdt_remove), + .driver = { + .owner = THIS_MODULE, + .name = "ux500_wdt", + }, +}; + +static int __init ux500_wdt_init(void) +{ + return platform_driver_probe(&ux500_wdt_driver, ux500_wdt_probe); +} + +static void __exit ux500_wdt_exit(void) +{ + platform_driver_unregister(&ux500_wdt_driver); +} + +module_init(ux500_wdt_init); +module_exit(ux500_wdt_exit); + +MODULE_AUTHOR("Jonas Aaberg "); +MODULE_DESCRIPTION("UX500 Watchdog Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -- cgit v1.2.3 From 5c27bb6d5d19524f4982a4b884c3f5ade4aa92d1 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Wed, 22 Jun 2011 14:58:53 +0530 Subject: power: ab5500-btemp: do not measure temp if battery is unknown Since the battery is unknown, do not measure the battery temp it might provide some value maybe too high or low leading to system reboot ST-Ericsson Linux next: Not Tested ST-Ericsson ID: WP332221 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I70b4cc959bfd2792a25379cc17a25dada4b1c051 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/25661 Reviewed-by: Srinidhi KASAGAR --- drivers/power/ab5500_btemp.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/power/ab5500_btemp.c b/drivers/power/ab5500_btemp.c index 8b6ff82a86b..04ad6bbb7ac 100644 --- a/drivers/power/ab5500_btemp.c +++ b/drivers/power/ab5500_btemp.c @@ -536,7 +536,15 @@ static int ab5500_btemp_get_property(struct power_supply *psy, val->intval = di->bat->bat_type[di->bat->batt_id].name; break; case POWER_SUPPLY_PROP_TEMP: - val->intval = di->bat_temp * 10; + if (di->bat->batt_id == BATTERY_UNKNOWN) + /* + * In case the battery is not identified, its assumed that + * we are using the power supply and since no monitoring is + * done for the same, a nominal temp is hardocded. + */ + val->intval = 250; + else + val->intval = di->bat_temp * 10; break; default: return -EINVAL; -- cgit v1.2.3 From 3543004e28cce7c964c8938b0f86ce70bc386348 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 23 Jun 2011 18:51:06 +0530 Subject: mfd: ab5500 - maintain proper ordering of the deviceid ab5500 core driver appends device deviceid to the device name and hence the order should be maintained in defining the deviceid Ex: sound/soc/ux500/u5500.c: .codec_name = "ab5500-codec.9", Since 2 new devices chargalg and btemp were added prior to codec audio stopped working. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: WP256401 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Id44e60a16d2c2b7181edd8fb553c3220788d6326 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/25788 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- include/linux/mfd/abx500.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 37a4bfecc30..7a1d3179f6a 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -220,9 +220,7 @@ enum ab5500_devid { AB5500_DEVID_SIM, AB5500_DEVID_RTC, AB5500_DEVID_CHARGER, - AB5500_DEVID_CHARGALG, AB5500_DEVID_FG, - AB5500_DEVID_BTEMP, AB5500_DEVID_VIBRATOR, AB5500_DEVID_CODEC, AB5500_DEVID_USB, @@ -230,6 +228,8 @@ enum ab5500_devid { AB5500_DEVID_VIDEO, AB5500_DEVID_DBIECI, AB5500_DEVID_ONSWA, + AB5500_DEVID_CHARGALG, + AB5500_DEVID_BTEMP, AB5500_NUM_DEVICES, }; -- cgit v1.2.3 From b52f6932e355c2c14856f7c72df4dbf490c0776e Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 7 Jul 2011 13:28:33 +0200 Subject: ux500: config: Align u8500_defconfig with u5500+u8500 default configs Signed-off-by: Philippe Langlais --- arch/arm/configs/u8500_defconfig | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 58ffb23c617..c751a5001f8 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -27,6 +27,7 @@ CONFIG_MACH_SNOWBALL=y CONFIG_MACH_U5500=y CONFIG_UX500_PRCMU_TIMER=y CONFIG_DB8500_MLOADER=y +CONFIG_U5500_MLOADER=y CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y @@ -34,8 +35,6 @@ CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_STANDBY=y CONFIG_UX500_SUSPEND_MEM=y -CONFIG_MODEM=y -CONFIG_MODEM_U8500=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y @@ -104,9 +103,8 @@ CONFIG_MISC_DEVICES=y CONFIG_AB8500_PWM=y CONFIG_SENSORS_BH1780=y CONFIG_STE_TRACE_MODEM=y +CONFIG_U8500_SIM_DETECT=y CONFIG_STM_TRACE=y -CONFIG_U8500_SHRM=y -CONFIG_U8500_SHRM_MODEM_SILENT_RESET=y # CONFIG_STE_AUDIO_IO_DEV is not set CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y @@ -119,7 +117,9 @@ CONFIG_TUN=y CONFIG_SMSC_PHY=y CONFIG_NET_ETHERNET=y CONFIG_SMSC911X=y -CONFIG_CAIF_SHM=y +# CONFIG_NETDEV_1000 is not set +# CONFIG_NETDEV_10000 is not set +# CONFIG_WLAN is not set # CONFIG_INPUT_MOUSEDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set @@ -148,6 +148,7 @@ CONFIG_SPI=y # CONFIG_STM_MSP_SPI is not set CONFIG_SPI_PL022=y CONFIG_GPIO_SYSFS=y +CONFIG_GPIO_STMPE=y CONFIG_GPIO_TC3589X=y CONFIG_AB8500_GPIO=y CONFIG_POWER_SUPPLY=y @@ -160,7 +161,6 @@ CONFIG_SENSORS_DB8500=y CONFIG_SENSORS_LSM303DLH=y CONFIG_SENSORS_L3G4200D=y CONFIG_WATCHDOG=y -CONFIG_MPCORE_WATCHDOG=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y CONFIG_AB5500_CORE=y @@ -226,6 +226,7 @@ CONFIG_RTC_DRV_PL031=y CONFIG_DMADEVICES=y CONFIG_STE_DMA40=y CONFIG_STAGING=y +CONFIG_AB5500_SIM=y CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y CONFIG_CG2900=y CONFIG_CG2900_CHIP=y @@ -235,8 +236,13 @@ CONFIG_CG2900_AUDIO=y CONFIG_CG2900_TEST=y CONFIG_BT_CG2900=y CONFIG_U8500_MMIO=y +CONFIG_U5500_MMIO=y CONFIG_U8500_CM=y CONFIG_U8500_FLASH=y +CONFIG_MODEM=y +CONFIG_MODEM_U8500=y +CONFIG_U8500_SHRM=y +CONFIG_U8500_SHRM_MODEM_SILENT_RESET=y CONFIG_EXT2_FS=y CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y @@ -287,7 +293,7 @@ CONFIG_TIMER_STATS=y CONFIG_DEBUG_SPINLOCK_SLEEP=y CONFIG_DEBUG_INFO=y CONFIG_SYSCTL_SYSCALL_CHECK=y -# CONFIG_FTRACE is not set +CONFIG_FUNCTION_TRACER=y CONFIG_DEBUG_USER=y CONFIG_KEYS=y CONFIG_CRYPTO_ECB=y -- cgit v1.2.3 From e693ba3f24e84160756582535994f04e8d989ef2 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Fri, 22 Jul 2011 11:57:36 +0200 Subject: gpio: nomadik: Rename AB8500_GPIO by GPIO_AB8500 To be compliant with future gpio framework Signed-off-by: Philippe Langlais --- arch/arm/configs/u8500_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index c751a5001f8..bc1ad15b815 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -150,7 +150,7 @@ CONFIG_SPI_PL022=y CONFIG_GPIO_SYSFS=y CONFIG_GPIO_STMPE=y CONFIG_GPIO_TC3589X=y -CONFIG_AB8500_GPIO=y +CONFIG_GPIO_AB8500=y CONFIG_POWER_SUPPLY=y CONFIG_AB8500_BM=y CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=y -- cgit v1.2.3 From cbef6a9d1f2474c31e3e609867663817c5584bad Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 12 Aug 2011 11:46:36 +0200 Subject: Doc: Fixing documentation for html generation Change documentation to align with header files and source file renames and move. Change-Id: I8e67e41e90dfb61255db5e2e84ead09972361c1c Signed-off-by: Robert Marklund --- Documentation/DocBook/stylesheet.xsl | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) mode change 100644 => 100755 Documentation/DocBook/stylesheet.xsl diff --git a/Documentation/DocBook/stylesheet.xsl b/Documentation/DocBook/stylesheet.xsl old mode 100644 new mode 100755 index 85b25275196..b2769ce5c8f --- a/Documentation/DocBook/stylesheet.xsl +++ b/Documentation/DocBook/stylesheet.xsl @@ -1,10 +1,18 @@ - - -1 -ansi -80 -0 - -2 -1 - + + + + + + + 1 + + + + + + + + \ No newline at end of file -- cgit v1.2.3 From 158d00b2277ea5dd55bdbf4dc6e288ec0f88db9c Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Thu, 3 Mar 2011 11:26:01 +0100 Subject: RTC: rtc-ab8500: Round alarm time upwards Due to strange android design and the ab8500 hw has a wake up alarm resolution in minutes, we have to add one minute to make sure that we always wake up. ST-Ericsson Linux next: Not tested, ask SSM for ER ST-Ericsson ID: ER324148 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I6a424984cc41757e4d3ba98e8ea48975a85213eb Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17496 Reviewed-by: QATOOLS Reviewed-by: Rickard ANDERSSON --- drivers/rtc/rtc-ab8500.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/rtc/rtc-ab8500.c b/drivers/rtc/rtc-ab8500.c index d6bfc4f3658..89e282292d3 100644 --- a/drivers/rtc/rtc-ab8500.c +++ b/drivers/rtc/rtc-ab8500.c @@ -241,8 +241,19 @@ static int ab8500_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) */ secs -= get_elapsed_seconds(AB8500_RTC_EPOCH); +#ifndef CONFIG_ANDROID + secs += 30; /* Round to nearest minute */ +#endif + mins = secs / 60; +#ifdef CONFIG_ANDROID + /* + * Needed due to Android believes all hw have a wake-up resolution + * in seconds. + */ + mins++; +#endif buf[2] = mins & 0xFF; buf[1] = (mins >> 8) & 0xFF; buf[0] = (mins >> 16) & 0xFF; -- cgit v1.2.3 From 853c57dbd2aa2a5d870e30890a213263b4cbd988 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 12 Jul 2011 13:43:54 +0200 Subject: ux500: Update u8500_defconfig and Add Snowball defconfig for Android Change-Id: I1cef8427a8f931b1be7b60c624a80b05c52d8217 Signed-off-by: Philippe Langlais Signed-off-by: Robert Marklund --- arch/arm/configs/u8500_defconfig | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index bc1ad15b815..68318c7c527 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -41,6 +41,7 @@ CONFIG_SMP=y CONFIG_NR_CPUS=2 CONFIG_PREEMPT=y CONFIG_AEABI=y +CONFIG_HIGHMEM=y CONFIG_CMDLINE="root=/dev/ram0 init=init rw console=ttyAMA2,115200n8 mem=256M initrd=0x800000,72M" CONFIG_KEXEC=y CONFIG_CRASH_DUMP=y @@ -148,7 +149,6 @@ CONFIG_SPI=y # CONFIG_STM_MSP_SPI is not set CONFIG_SPI_PL022=y CONFIG_GPIO_SYSFS=y -CONFIG_GPIO_STMPE=y CONFIG_GPIO_TC3589X=y CONFIG_GPIO_AB8500=y CONFIG_POWER_SUPPLY=y @@ -200,17 +200,11 @@ CONFIG_USB_STORAGE=y CONFIG_USB_LIBUSUAL=y CONFIG_USB_GADGET=y CONFIG_USB_GADGET_VBUS_DRAW=500 -CONFIG_USB_ZERO=m -CONFIG_USB_ETH=m -CONFIG_USB_MASS_STORAGE=m -CONFIG_USB_G_SERIAL=m -CONFIG_USB_CDC_COMPOSITE=m -CONFIG_USB_G_MULTI=m -# CONFIG_USB_G_MULTI_RNDIS is not set CONFIG_AB8500_USB=y CONFIG_MMC=y CONFIG_MMC_UNSAFE_RESUME=y CONFIG_MMC_CLKGATE=y +CONFIG_MMC_PARANOID_SD_INIT=y # CONFIG_MMC_BLOCK_BOUNCE is not set CONFIG_MMC_ARMMMCI=y CONFIG_NEW_LEDS=y @@ -219,6 +213,7 @@ CONFIG_LEDS_LM3530=y CONFIG_LEDS_LP5521=y CONFIG_LEDS_PWM=y CONFIG_LEDS_TRIGGER_TIMER=y +CONFIG_SWITCH=y CONFIG_RTC_CLASS=y CONFIG_RTC_DRV_AB=y CONFIG_RTC_DRV_AB8500=y -- cgit v1.2.3 From ed5320abf75fc23a6506cd96af73649fa1e755a0 Mon Sep 17 00:00:00 2001 From: Marcus Cooper Date: Mon, 16 May 2011 10:08:49 +0200 Subject: bh1780gli: Add early suspend/late resume ST-Ericsson Linux next: Not tested, ask SSM for ER. ST-Ericsson ID: ER337907 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I66430fe4369c1320c78b1a8dca5e4abbb3b9abcf Signed-off-by: Marcus Cooper Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23099 Reviewed-by: QATEST Reviewed-by: Martin PERSSON Reviewed-by: Chethan Krishna N Reviewed-by: Jonas ABERG Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28973 Tested-by: Naga RADHESH Y Reviewed-by: Bibek BASU Tested-by: Bibek BASU --- drivers/misc/bh1780gli.c | 124 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 94 insertions(+), 30 deletions(-) diff --git a/drivers/misc/bh1780gli.c b/drivers/misc/bh1780gli.c index 0a731371fb5..c49d45fa236 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -25,6 +25,10 @@ #include #include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + #define BH1780_REG_CONTROL 0x80 #define BH1780_REG_PARTID 0x8A #define BH1780_REG_MANFID 0x8B @@ -42,11 +46,19 @@ struct bh1780_data { struct i2c_client *client; struct regulator *regulator; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif int power_state; /* lock for sysfs operations */ struct mutex lock; }; +#ifdef CONFIG_HAS_EARLYSUSPEND +static void bh1780_early_suspend(struct early_suspend *ddata); +static void bh1780_late_resume(struct early_suspend *ddata); +#endif + static int bh1780_write(struct bh1780_data *ddata, u8 reg, u8 val, char *msg) { int ret = i2c_smbus_write_byte_data(ddata->client, reg, val); @@ -191,6 +203,12 @@ static int __devinit bh1780_probe(struct i2c_client *client, dev_err(&client->dev, "failed to read part ID\n"); goto put_regulator; } +#ifdef CONFIG_HAS_EARLYSUSPEND + ddata->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + ddata->early_suspend.suspend = bh1780_early_suspend; + ddata->early_suspend.resume = bh1780_late_resume; + register_early_suspend(&ddata->early_suspend); +#endif regulator_disable(ddata->regulator); ddata->power_state = BH1780_POFF; @@ -225,57 +243,101 @@ static int __devexit bh1780_remove(struct i2c_client *client) return 0; } -#ifdef CONFIG_PM -static int bh1780_suspend(struct device *dev) +static int bh1780_do_suspend(struct bh1780_data *ddata) { - struct bh1780_data *ddata; - int state, ret; - struct i2c_client *client = to_i2c_client(dev); + int ret = 0; - ddata = i2c_get_clientdata(client); - state = bh1780_read(ddata, BH1780_REG_CONTROL, "CONTROL"); - if (state < 0) - return state; + mutex_lock(&ddata->lock); - ddata->power_state = state & BH1780_POWMASK; + if (ddata->power_state == BH1780_POFF) + goto unlock; - ret = bh1780_write(ddata, BH1780_REG_CONTROL, BH1780_POFF, - "CONTROL"); + ret = bh1780_write(ddata, BH1780_REG_CONTROL, BH1780_POFF, "CONTROL"); if (ret < 0) - return ret; - - regulator_disable(ddata->regulator); + goto unlock; - return 0; + if (ddata->regulator) + regulator_disable(ddata->regulator); +unlock: + mutex_unlock(&ddata->lock); + return ret; } -static int bh1780_resume(struct device *dev) +static int bh1780_do_resume(struct bh1780_data *ddata) { - struct bh1780_data *ddata; - int state, ret; - struct i2c_client *client = to_i2c_client(dev); + int ret = 0; - ddata = i2c_get_clientdata(client); - state = ddata->power_state; - ret = bh1780_write(ddata, BH1780_REG_CONTROL, state, - "CONTROL"); + mutex_lock(&ddata->lock); - regulator_enable(ddata->regulator); + if (ddata->power_state == BH1780_POFF) + goto unlock; + + if (ddata->regulator) + regulator_enable(ddata->regulator); + + ret = bh1780_write(ddata, BH1780_REG_CONTROL, + ddata->power_state, "CONTROL"); - ret = bh1780_write(ddata, BH1780_REG_CONTROL, ddata->power_state, - "CONTROL"); +unlock: + mutex_unlock(&ddata->lock); + return ret; +} +#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) +static int bh1780_suspend(struct device *dev) +{ + struct bh1780_data *ddata = dev_get_drvdata(dev); + int ret = 0; + + ret = bh1780_do_suspend(ddata); if (ret < 0) - return ret; + dev_err(&ddata->client->dev, + "Error while suspending the device\n"); - return 0; + return ret; +} + +static int bh1780_resume(struct device *dev) +{ + struct bh1780_data *ddata = dev_get_drvdata(dev); + int ret = 0; + + ret = bh1780_do_resume(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while resuming the device\n"); + + return ret; } static SIMPLE_DEV_PM_OPS(bh1780_pm, bh1780_suspend, bh1780_resume); #define BH1780_PMOPS (&bh1780_pm) #else #define BH1780_PMOPS NULL -#endif /* CONFIG_PM */ +static void bh1780_early_suspend(struct early_suspend *data) +{ + struct bh1780_data *ddata = + container_of(data, struct bh1780_data, early_suspend); + int ret; + + ret = bh1780_do_suspend(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while suspending the device\n"); +} + +static void bh1780_late_resume(struct early_suspend *data) +{ + struct bh1780_data *ddata = + container_of(data, struct bh1780_data, early_suspend); + int ret; + + ret = bh1780_do_resume(ddata); + if (ret < 0) + dev_err(&ddata->client->dev, + "Error while resuming the device\n"); +} +#endif /*!CONFIG_HAS_EARLYSUSPEND && CONFIG_PM */ static const struct i2c_device_id bh1780_id[] = { { "bh1780", 0 }, @@ -288,7 +350,9 @@ static struct i2c_driver bh1780_driver = { .id_table = bh1780_id, .driver = { .name = "bh1780", +#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) .pm = BH1780_PMOPS, +#endif }, }; -- cgit v1.2.3 From 40a31b5fc8795ed0b75fee482b9f77c452e78783 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Tue, 24 May 2011 11:46:28 +0200 Subject: misc: bh1780gli.c: Fix ifdefs ST-Ericsson Linux next: Not tested, ask SSM for ER ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ie68a18f1120899167026b8b5116dd14c658d2d1d Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23701 Reviewed-by: Marcus COOPER Reviewed-by: QATEST Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28974 Tested-by: Naga RADHESH Y Reviewed-by: Bibek BASU Tested-by: Bibek BASU --- drivers/misc/bh1780gli.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/misc/bh1780gli.c b/drivers/misc/bh1780gli.c index c49d45fa236..060441599cb 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -243,6 +243,7 @@ static int __devexit bh1780_remove(struct i2c_client *client) return 0; } +#if defined(CONFIG_HAS_EARLYSUSPEND) || defined(CONFIG_PM) static int bh1780_do_suspend(struct bh1780_data *ddata) { int ret = 0; @@ -283,7 +284,10 @@ unlock: mutex_unlock(&ddata->lock); return ret; } -#if (!defined(CONFIG_HAS_EARLYSUSPEND) && defined(CONFIG_PM)) +#endif + +#ifndef CONFIG_HAS_EARLYSUSPEND +#ifdef CONFIG_PM static int bh1780_suspend(struct device *dev) { struct bh1780_data *ddata = dev_get_drvdata(dev); @@ -312,6 +316,7 @@ static int bh1780_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(bh1780_pm, bh1780_suspend, bh1780_resume); #define BH1780_PMOPS (&bh1780_pm) +#endif /* CONFIG_PM */ #else #define BH1780_PMOPS NULL static void bh1780_early_suspend(struct early_suspend *data) @@ -337,7 +342,7 @@ static void bh1780_late_resume(struct early_suspend *data) dev_err(&ddata->client->dev, "Error while resuming the device\n"); } -#endif /*!CONFIG_HAS_EARLYSUSPEND && CONFIG_PM */ +#endif /*!CONFIG_HAS_EARLYSUSPEND */ static const struct i2c_device_id bh1780_id[] = { { "bh1780", 0 }, -- cgit v1.2.3 From 2df1749ca9e01a66e9fc9b07ba91906036d86930 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Fri, 24 Jun 2011 11:28:58 +0530 Subject: power: ab5500-charger - usb line status interrupt correction After getting usb rising edge interrupt, usb chip select should be enabled in order to get the usb line status interrupt. This sequence is done in the usb driver, in battery driver, usb charger detect was enabled. This was conflicting with the sequence followed in the usb driver and hence usb line status interrupt was not triggered. Hence reverted the usb detection enable in battery driver. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: ER349171 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Iaa055cdbe59699fe0442489586fd5c7e21c1ed5b Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/25822 Reviewed-by: QATEST Reviewed-by: Johan PALSSON Reviewed-by: Srinidhi KASAGAR --- drivers/power/ab5500_charger.c | 61 ++++++------------------------------------ 1 file changed, 8 insertions(+), 53 deletions(-) diff --git a/drivers/power/ab5500_charger.c b/drivers/power/ab5500_charger.c index 2fd7888ad9d..736be487e14 100644 --- a/drivers/power/ab5500_charger.c +++ b/drivers/power/ab5500_charger.c @@ -37,8 +37,6 @@ /* AB5500 Charger constants */ #define AB5500_USB_LINK_STATUS 0x78 -#define USB_CHARG_DET_ENA_MASK 0x01 -#define USB_CHARG_DET_ENA 0x01 #define CHARGER_REV_SUP 0x10 #define SW_EOC 0x40 #define USB_CHAR_DET 0x02 @@ -396,12 +394,6 @@ static int ab5500_charger_read_usb_type(struct ab5500_charger *di) int ret; u8 val; - ret = abx500_get_register_interruptible(di->dev, - AB5500_BANK_IT, AB5500_IT_SOURCE22_REG, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab5500 read failed\n", __func__); - return ret; - } ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_USB, AB5500_USB_LINE_STATUS, &val); if (ret < 0) { @@ -426,45 +418,17 @@ static int ab5500_charger_read_usb_type(struct ab5500_charger *di) */ static int ab5500_charger_detect_usb_type(struct ab5500_charger *di) { - int i, ret; + int ret; u8 val; - /* - * On getting the VBUS rising edge detect interrupt there - * is a 250ms delay after which the register UsbLineStatus - * is filled with valid data. - */ - for (i = 0; i < 10; i++) { - msleep(250); - ret = abx500_get_register_interruptible(di->dev, - AB5500_BANK_IT, AB5500_IT_SOURCE22_REG, - &val); - if (ret < 0) { - dev_err(di->dev, "%s ab5500 read failed\n", __func__); - return ret; - } - if (!(val & USB_LINK_UPDATE)) - continue; - - ret = abx500_get_register_interruptible(di->dev, - AB5500_BANK_USB, AB5500_USB_LINE_STATUS, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab5500 read failed\n", __func__); - return ret; - } - /* - * Until the IT source register is read the UsbLineStatus - * register is not updated, hence doing the same - * Revisit this: - */ - - /* get the USB type */ - val = (val & AB5500_USB_LINK_STATUS) >> 3; - if (val) { - dev_dbg(di->dev, "values = %d\n", val); - break; - } + ret = abx500_get_register_interruptible(di->dev, + AB5500_BANK_USB, AB5500_USB_LINE_STATUS, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab5500 read failed\n", __func__); + return ret; } + /* get the USB type */ + val = (val & AB5500_USB_LINK_STATUS) >> 3; ret = ab5500_charger_max_usb_curr(di, (enum ab5500_charger_link_status) val); @@ -1533,15 +1497,6 @@ static int ab5500_charger_init_hw_registers(struct ab5500_charger *di) goto out; } - /* Enable USB Charger Detection */ - ret = abx500_mask_and_set_register_interruptible(di->dev, - AB5500_BANK_USB, AB5500_USB_LINE_CTRL2, - USB_CHARG_DET_ENA_MASK, USB_CHARG_DET_ENA); - if (ret) { - dev_err(di->dev, "failed to enable usb charger detection\n"); - goto out; - } - /* Over current protection for reverse supply */ ret = abx500_mask_and_set_register_interruptible(di->dev, AB5500_BANK_CHG, AB5500_CREVS, CHARGER_REV_SUP, -- cgit v1.2.3 From 7fb10a482143e211c6f2e4a1086b8c5556513638 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Tue, 28 Jun 2011 10:10:11 +0530 Subject: power: ab5500-fg: update battery capacity Battery capacity is updated at boot time and thereafter not updated. Battery capacity is based on the lookup table provided by the battery vendors. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: ER349552 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I9cb7b01bccf2dc76e5d68c374fbb1d95120baa06 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/25933 Reviewed-by: QATEST Reviewed-by: Johan PALSSON Reviewed-by: Linus WALLEIJ --- drivers/power/ab5500_fg.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/power/ab5500_fg.c b/drivers/power/ab5500_fg.c index 919af5faadf..95f31cb84cb 100644 --- a/drivers/power/ab5500_fg.c +++ b/drivers/power/ab5500_fg.c @@ -359,7 +359,7 @@ cc_err: */ static int ab5500_fg_inst_curr(struct ab5500_fg *di) { - u8 low, high, value; + u8 low, high; static int val; int ret = 0; bool fg_off = false; @@ -1378,9 +1378,11 @@ static int ab5500_fg_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_VOLTAGE_NOW: if (di->flags.bat_ovv) val->intval = 47500000; - else - val->intval = ab5500_gpadc_convert - (di->gpadc, MAIN_BAT_V) * 1000; + else { + di->vbat = ab5500_gpadc_convert + (di->gpadc, MAIN_BAT_V); + val->intval = di->vbat * 1000; + } break; case POWER_SUPPLY_PROP_CURRENT_NOW: di->inst_curr = ab5500_fg_inst_curr(di); -- cgit v1.2.3 From 9d4f715eeba78a20884c14fc1eb5ff4fbe4730c9 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 7 Jul 2011 11:11:34 +0530 Subject: power: ab5500-charger: update the usb presence status When usb is removed, since there are no status registers, verify the same by reading the interrupt source register and send an uevent. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: ER351099 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ife573551319e91e3651c19e40601bb850c186a14 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/26703 Reviewed-by: Johan PALSSON Reviewed-by: Srinidhi KASAGAR --- drivers/power/ab5500_charger.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/drivers/power/ab5500_charger.c b/drivers/power/ab5500_charger.c index 736be487e14..1eee43a5c8f 100644 --- a/drivers/power/ab5500_charger.c +++ b/drivers/power/ab5500_charger.c @@ -40,8 +40,8 @@ #define CHARGER_REV_SUP 0x10 #define SW_EOC 0x40 #define USB_CHAR_DET 0x02 -#define USB_CHAR_DET_DONE 0x02 #define VBUS_RISING 0x20 +#define VBUS_FALLING 0x40 #define USB_LINK_UPDATE 0x02 #define USB_CH_TH_PROT_LOW 0x02 #define USB_CH_TH_PROT_HIGH 0x01 @@ -285,16 +285,8 @@ static int ab5500_charger_detect_chargers(struct ab5500_charger *di) if (val & VBUS_RISING) result |= USB_PW_CONN; - - ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_IT, - AB5500_IT_SOURCE9, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab5500 read failed\n", __func__); - return ret; - } - - if (val & USB_CHAR_DET_DONE) - result |= USB_PW_CONN; + else if (val & VBUS_FALLING) + result = NO_PW_CONN; return result; } -- cgit v1.2.3 From 2635efc98596d9ab2e6458322b30b40df0fee850 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 7 Jul 2011 12:26:58 +0530 Subject: power: ab5500-power: usb type detection usb type can be detected based on the usb line status register. USB line status register gets updated on receiving usb line status interrupt. This usb line status interrupt is obtained after doing a chip select in usb rising edge interrupt handler and this is done by the usb driver. Now on getting usb rising edge interrupt, if the usb line status register is read, it ends up with unknown device connected. USB line status register must be read only after getting the usb line status interrupt, else will not be updated. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: ER350732 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ibca3a8e8032e23fb2b52ac45f9172269f868ae42 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/26709 Reviewed-by: QATEST Reviewed-by: Johan PALSSON Reviewed-by: Srinidhi KASAGAR --- drivers/power/ab5500_charger.c | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/drivers/power/ab5500_charger.c b/drivers/power/ab5500_charger.c index 1eee43a5c8f..69b808f06d7 100644 --- a/drivers/power/ab5500_charger.c +++ b/drivers/power/ab5500_charger.c @@ -401,32 +401,6 @@ static int ab5500_charger_read_usb_type(struct ab5500_charger *di) return ret; } -/** - * ab5500_charger_detect_usb_type() - get the type of usb connected - * @di: pointer to the ab5500_charger structure - * - * Detect the type of the plugged USB - * Returns error code in case of failure else 0 on success - */ -static int ab5500_charger_detect_usb_type(struct ab5500_charger *di) -{ - int ret; - u8 val; - - ret = abx500_get_register_interruptible(di->dev, - AB5500_BANK_USB, AB5500_USB_LINE_STATUS, &val); - if (ret < 0) { - dev_err(di->dev, "%s ab5500 read failed\n", __func__); - return ret; - } - /* get the USB type */ - val = (val & AB5500_USB_LINK_STATUS) >> 3; - ret = ab5500_charger_max_usb_curr(di, - (enum ab5500_charger_link_status) val); - - return ret; -} - static int ab5500_charger_voltage_map[] = { 3500 , 3525 , @@ -1075,13 +1049,6 @@ void ab5500_charger_detect_usb_type_work(struct work_struct *work) power_supply_changed(&di->usb_chg.psy); } else { di->vbus_detected = 1; - - ret = ab5500_charger_detect_usb_type(di); - if (!ret) { - di->usb.charger_connected = 1; - power_supply_changed(&di->usb_chg.psy); - } - } } @@ -1713,7 +1680,7 @@ static int __devinit ab5500_charger_probe(struct platform_device *pdev) di->vbus_detected = true; di->vbus_detected_start = true; queue_work(di->charger_wq, - &di->detect_usb_type_work); + &di->usb_link_status_work); } /* Register interrupts */ -- cgit v1.2.3 From 2022cb1b951a54820916f8c60ba2b537f5c4f475 Mon Sep 17 00:00:00 2001 From: Johan Palsson Date: Thu, 7 Jul 2011 11:45:03 +0200 Subject: power: ab8500_bm: Measure battery temperature when not charging Battery temperature is now measured even when no charger is connected. ST-Ericsson ID: 342846 ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ia861437635bcdcdc32b259aff05d889b449ee682 Signed-off-by: Johan Palsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/26608 Reviewed-by: QATEST Reviewed-by: Mattias WALLIN --- drivers/power/ab8500_btemp.c | 33 +++++++++++++++++---------------- include/linux/mfd/ab8500/bm.h | 4 ++++ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c index 7d204b19742..b8053d577ab 100644 --- a/drivers/power/ab8500_btemp.c +++ b/drivers/power/ab8500_btemp.c @@ -510,6 +510,7 @@ static int ab8500_btemp_id(struct ab8500_btemp *di) */ static void ab8500_btemp_periodic_work(struct work_struct *work) { + int interval; struct ab8500_btemp *di = container_of(work, struct ab8500_btemp, btemp_periodic_work.work); @@ -520,10 +521,15 @@ static void ab8500_btemp_periodic_work(struct work_struct *work) power_supply_changed(&di->btemp_psy); } + if (di->events.ac_conn || di->events.usb_conn) + interval = di->bat->temp_interval_chg; + else + interval = di->bat->temp_interval_nochg; + /* Schedule a new measurement */ queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, - round_jiffies(20 * HZ)); + round_jiffies(interval * HZ)); } /** @@ -656,11 +662,14 @@ static void ab8500_btemp_periodic(struct ab8500_btemp *di, { dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n", enable); + /* + * Make sure a new measurement is done directly by cancelling + * any pending work + */ + cancel_delayed_work_sync(&di->btemp_periodic_work); if (enable) queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0); - else - cancel_delayed_work_sync(&di->btemp_periodic_work); } /** @@ -795,9 +804,6 @@ static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data) /* AC disconnected */ if (!ret.intval && di->events.ac_conn) { di->events.ac_conn = false; - if (!di->events.usb_conn) - ab8500_btemp_periodic(di, - false); } /* AC connected */ else if (ret.intval && !di->events.ac_conn) { @@ -810,9 +816,6 @@ static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data) /* USB disconnected */ if (!ret.intval && di->events.usb_conn) { di->events.usb_conn = false; - if (!di->events.ac_conn) - ab8500_btemp_periodic(di, - false); } /* USB connected */ else if (ret.intval && !di->events.usb_conn) { @@ -863,8 +866,7 @@ static int ab8500_btemp_resume(struct platform_device *pdev) { struct ab8500_btemp *di = platform_get_drvdata(pdev); - if (di->events.ac_conn || di->events.usb_conn) - ab8500_btemp_periodic(di, true); + ab8500_btemp_periodic(di, true); return 0; } @@ -874,8 +876,7 @@ static int ab8500_btemp_suspend(struct platform_device *pdev, { struct ab8500_btemp *di = platform_get_drvdata(pdev); - if (di->events.ac_conn || di->events.usb_conn) - ab8500_btemp_periodic(di, false); + ab8500_btemp_periodic(di, false); return 0; } @@ -1004,9 +1005,6 @@ static int __devinit ab8500_btemp_probe(struct platform_device *pdev) break; } - /* Measure temperature once initially */ - di->bat_temp = ab8500_btemp_measure_temp(di); - /* Register BTEMP power supply class */ ret = power_supply_register(di->dev, &di->btemp_psy); if (ret) { @@ -1032,6 +1030,9 @@ static int __devinit ab8500_btemp_probe(struct platform_device *pdev) platform_set_drvdata(pdev, di); + /* Kick off periodic temperature measurements */ + ab8500_btemp_periodic(di, true); + return ret; free_irq: diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h index 2ca4d8779cb..acde23b2a19 100644 --- a/include/linux/mfd/ab8500/bm.h +++ b/include/linux/mfd/ab8500/bm.h @@ -394,6 +394,8 @@ struct ab8500_bm_charger_parameters { * @temp_low between this temp and temp_under charging is reduced * @temp_high between this temp and temp_over charging is reduced * @temp_over over this temp, charging is stopped + * @temp_interval_chg temperature measurement interval in s when charging + * @temp_interval_nochg temperature measurement interval in s when not charging * @main_safety_tmr_h safety timer for main charger * @usb_safety_tmr_h safety timer for usb charger * @bkup_bat_v voltage which we charge the backup battery with @@ -419,6 +421,8 @@ struct ab8500_bm_data { int temp_low; int temp_high; int temp_over; + int temp_interval_chg; + int temp_interval_nochg; int main_safety_tmr_h; int usb_safety_tmr_h; int bkup_bat_v; -- cgit v1.2.3 From d471610b0b29536311448f14c8681289389cc41b Mon Sep 17 00:00:00 2001 From: Johan Palsson Date: Thu, 25 Aug 2011 12:27:06 +0530 Subject: hwmon: ab8500: Add support to monitor battery temp ST-Ericsson ID: CR339643 ST-Ericsson Linux next: ER282986 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ib6ad58e7a702fe976a1d66fef0ce9d1782a1f8d2 Signed-off-by: Huan DUAN Signed-off-by: Johan Palsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/25108 Reviewed-by: QATEST Reviewed-by: Jonas ABERG --- drivers/hwmon/ab8500.c | 107 +++++++++++++++++++++++++++++++----------- drivers/power/ab8500_btemp.c | 28 +++++++++++ include/linux/mfd/ab8500/bm.h | 17 ++++++- 3 files changed, 123 insertions(+), 29 deletions(-) diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c index 1cfab4948e4..7469ffaa6cb 100644 --- a/drivers/hwmon/ab8500.c +++ b/drivers/hwmon/ab8500.c @@ -38,6 +38,7 @@ #include #include #include +#include #include /* @@ -50,13 +51,17 @@ #define NUM_SENSORS 5 -/* The driver monitors GPADC - ADC_AUX1 and ADC_AUX2 */ -#define NUM_MONITORED_SENSORS 2 +/* + * The driver monitors GPADC - ADC_AUX1, ADC_AUX2, BTEMP_BALL + * and BAT_CTRL. + */ +#define NUM_MONITORED_SENSORS 4 struct ab8500_temp { struct platform_device *pdev; struct device *hwmon_dev; struct ab8500_gpadc *gpadc; + struct ab8500_btemp *btemp; u8 gpadc_addr[NUM_SENSORS]; unsigned long min[NUM_SENSORS]; unsigned long max[NUM_SENSORS]; @@ -131,10 +136,21 @@ static void gpadc_monitor(struct work_struct *work) && data->min[i] == 0) continue; - val = ab8500_gpadc_convert(data->gpadc, data->gpadc_addr[i]); - if (val < 0) { - dev_err(&data->pdev->dev, "GPADC read failed\n"); - continue; + /* + * Special treatment for the BAT_CTRL node, since this + * temperature measurement is more complex than just + * an ADC readout + */ + if (data->gpadc_addr[i] == BAT_CTRL) { + val = ab8500_btemp_get_batctrl_temp(data->btemp); + } else { + val = ab8500_gpadc_convert(data->gpadc, + data->gpadc_addr[i]); + if (val < 0) { + dev_err(&data->pdev->dev, + "GPADC read failed\n"); + continue; + } } mutex_lock(&data->lock); @@ -322,11 +338,11 @@ static ssize_t show_label(struct device *dev, name = "bat_temp"; break; case 4: - name = "ab8500"; - break; - case 5: name = "bat_ctrl"; break; + case 5: + name = "ab8500"; + break; default: return -EINVAL; } @@ -342,9 +358,18 @@ static ssize_t show_input(struct device *dev, /* hwmon attr index starts at 1, thus "attr->index-1" below */ u8 gpadc_addr = data->gpadc_addr[attr->index - 1]; - val = ab8500_gpadc_convert(data->gpadc, gpadc_addr); - if (val < 0) - dev_err(&data->pdev->dev, "GPADC read failed\n"); + /* + * Special treatment for the BAT_CTRL node, since this + * temperature measurement is more complex than just + * an ADC readout + */ + if (gpadc_addr == BAT_CTRL) { + val = ab8500_btemp_get_batctrl_temp(data->btemp); + } else { + val = ab8500_gpadc_convert(data->gpadc, gpadc_addr); + if (val < 0) + dev_err(&data->pdev->dev, "GPADC read failed\n"); + } return sprintf(buf, "%d\n", val); } @@ -546,15 +571,31 @@ static SENSOR_DEVICE_ATTR(temp2_max_hyst_alarm, S_IRUGO, /* GPADC - BTEMP_BALL */ static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 3); static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min, set_min, 3); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max, set_max, 3); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 3); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_min_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_max_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 3); -/* AB8500 */ +/* GPADC - BAT_CTRL */ static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 4); -static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, - show_crit_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_input, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_min, set_min, 4); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_max, set_max, 4); +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 4); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_min_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_max_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 4); -/* GPADC - BAT_CTRL */ +/* AB8500 */ static SENSOR_DEVICE_ATTR(temp5_label, S_IRUGO, show_label, NULL, 5); -static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_input, NULL, 5); +static SENSOR_DEVICE_ATTR(temp5_crit_alarm, S_IRUGO, + show_crit_alarm, NULL, 5); static struct attribute *ab8500_temp_attributes[] = { &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, @@ -581,12 +622,24 @@ static struct attribute *ab8500_temp_attributes[] = { /* GPADC - BTEMP_BALL */ &sensor_dev_attr_temp3_label.dev_attr.attr, &sensor_dev_attr_temp3_input.dev_attr.attr, - /* AB8500 */ - &sensor_dev_attr_temp4_label.dev_attr.attr, - &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst_alarm.dev_attr.attr, /* GPADC - BAT_CTRL */ + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst_alarm.dev_attr.attr, + /* AB8500 */ &sensor_dev_attr_temp5_label.dev_attr.attr, - &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, NULL }; @@ -605,9 +658,9 @@ static irqreturn_t ab8500_temp_irq_handler(int irq, void *irq_data) * used for AB8500 thermal warning from HW. */ mutex_lock(&data->lock); - data->crit_alarm[3] = 1; + data->crit_alarm[4] = 1; mutex_unlock(&data->lock); - sysfs_notify(&pdev->dev.kobj, NULL, "temp4_crit_alarm"); + sysfs_notify(&pdev->dev.kobj, NULL, "temp5_crit_alarm"); dev_info(&pdev->dev, "AB8500 thermal warning, power off in %lu s\n", data->power_off_delay); delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); @@ -645,6 +698,7 @@ static int __devinit ab8500_temp_probe(struct platform_device *pdev) goto exit; data->gpadc = ab8500_gpadc_get(); + data->btemp = ab8500_btemp_get(); data->hwmon_dev = hwmon_device_register(&pdev->dev); if (IS_ERR(data->hwmon_dev)) { @@ -665,21 +719,18 @@ static int __devinit ab8500_temp_probe(struct platform_device *pdev) * GPADC - ADC_AUX2, connected to NTC R2150 near DB8500 on HREF * Hence, temp#_min/max/max_hyst refer to millivolts and not * millidegrees + * This is not the case for BAT_CTRL where millidegrees is used * * HREF HW does not support reading AB8500 temperature. BUT an * AB8500 IRQ will be launched if die crit temp limit is reached. * - * Also: - * Battery temperature (BatTemp and BatCtrl) thresholds will - * not be exposed via hwmon. - * * Make sure indexes correspond to the attribute indexes * used when calling SENSOR_DEVICE_ATRR */ data->gpadc_addr[0] = ADC_AUX1; data->gpadc_addr[1] = ADC_AUX2; data->gpadc_addr[2] = BTEMP_BALL; - data->gpadc_addr[4] = BAT_CTRL; + data->gpadc_addr[3] = BAT_CTRL; mutex_init(&data->lock); data->pdev = pdev; data->power_off_delay = DEFAULT_POWER_OFF_DELAY; diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c index b8053d577ab..336384c882f 100644 --- a/drivers/power/ab8500_btemp.c +++ b/drivers/power/ab8500_btemp.c @@ -67,6 +67,7 @@ struct ab8500_btemp_ranges { /** * struct ab8500_btemp - ab8500 BTEMP device information * @dev: Pointer to the structure device + * @node: List of AB8500 BTEMPs, hence prepared for reentrance * @chip_id: Chip-Id of the AB8500 * @curr_source: What current source we use, in uA * @bat_temp: Battery temperature in degree Celcius @@ -83,6 +84,7 @@ struct ab8500_btemp_ranges { */ struct ab8500_btemp { struct device *dev; + struct list_head node; u8 chip_id; int curr_source; int bat_temp; @@ -106,6 +108,20 @@ static enum power_supply_property ab8500_btemp_props[] = { POWER_SUPPLY_PROP_TEMP, }; +static LIST_HEAD(ab8500_btemp_list); + +/** + * ab8500_btemp_get() - returns a reference to the primary AB8500 BTEMP + * (i.e. the first BTEMP in the instance list) + */ +struct ab8500_btemp *ab8500_btemp_get(void) +{ + struct ab8500_btemp *btemp; + btemp = list_first_entry(&ab8500_btemp_list, struct ab8500_btemp, node); + + return btemp; +} + /** * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance * @di: pointer to the ab8500_btemp structure @@ -722,6 +738,17 @@ static int ab8500_btemp_get_temp(struct ab8500_btemp *di) return temp; } +/** + * ab8500_btemp_get_batctrl_temp() - get the temperature + * @btemp: pointer to the btemp structure + * + * Returns the batctrl temperature in millidegrees + */ +int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) +{ + return btemp->bat_temp * 1000; +} + /** * ab8500_btemp_get_property() - get the btemp properties * @psy: pointer to the power_supply structure @@ -1032,6 +1059,7 @@ static int __devinit ab8500_btemp_probe(struct platform_device *pdev) /* Kick off periodic temperature measurements */ ab8500_btemp_periodic(di, true); + list_add_tail(&di->node, &ab8500_btemp_list); return ret; diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h index acde23b2a19..a830e4d20e4 100644 --- a/include/linux/mfd/ab8500/bm.h +++ b/include/linux/mfd/ab8500/bm.h @@ -462,5 +462,20 @@ struct ab8500_fg_platform_data { struct ab8500_chargalg_platform_data { char **supplied_to; size_t num_supplicants; -}; +} + +struct ab8500_btemp; +#ifdef CONFIG_AB8500_BM +struct ab8500_btemp *ab8500_btemp_get(void); +int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp); +#else +static struct ab8500_btemp *ab8500_btemp_get(void) +{ + return 0; +} +static int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) +{ + return 0; +} +#endif #endif /* _AB8500_BM_H */ -- cgit v1.2.3 From 3538ae70032767de43afe7b66a3e69c921598090 Mon Sep 17 00:00:00 2001 From: Rajagopala V Date: Thu, 25 Aug 2011 13:20:04 +0530 Subject: abx500: hwmon: generalize AB8500 temperature monitor driver Generalize ab8500 hwmon driver to support other abx500 variants. Source code is split into generic and variant specific files. ST-Ericsson Linux next: NA ST-Ericsson ID: WP257616 ST-Ericsson FOSS-OUT ID: NA Change-Id: Ia39feaac455160149d49a5982374da3729ea5301 Signed-off-by: Rajagopala V Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28138 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/Kconfig | 2 +- drivers/hwmon/Makefile | 2 +- drivers/hwmon/ab8500.c | 714 +++------------------------------------------- drivers/hwmon/abx500.c | 667 +++++++++++++++++++++++++++++++++++++++++++ drivers/hwmon/abx500.h | 84 ++++++ drivers/mfd/ab8500-core.c | 4 +- 6 files changed, 801 insertions(+), 672 deletions(-) create mode 100644 drivers/hwmon/abx500.c create mode 100644 drivers/hwmon/abx500.h diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 8ae4cc3b94e..7dadc125e92 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -41,7 +41,7 @@ comment "Native drivers" config SENSORS_AB8500 tristate "AB8500 thermal monitoring" - depends on AB8500_CORE + depends on AB8500_GPADC default n help If you say yes here you get support for the thermal sensor part diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index b191234b3f2..1a085a872ca 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -19,7 +19,7 @@ obj-$(CONFIG_SENSORS_W83795) += w83795.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o -obj-$(CONFIG_SENSORS_AB8500) += ab8500.o +obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o obj-$(CONFIG_SENSORS_DB8500) += db8500.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c index 7469ffaa6cb..cb6cf4cf4d4 100644 --- a/drivers/hwmon/ab8500.c +++ b/drivers/hwmon/ab8500.c @@ -6,16 +6,6 @@ * * Note: * - * AB8500 does not provide auto ADC, so to monitor the required - * temperatures, a periodic work is used. It is more important - * to not wake up the CPU than to perform this job, hence the use - * of a deferred delay. - * - * A deferred delay for thermal monitor is considered safe because: - * If the chip gets too hot during a sleep state it's most likely - * due to external factors, such as the surrounding temperature. - * I.e. no SW decisions will make any difference. - * * If/when the AB8500 thermal warning temperature is reached (threshold * cannot be changed by SW), an interrupt is set and the driver * notifies user space via a sysfs event. If a shut down is not @@ -26,30 +16,17 @@ * shutdown of the AB8500 will occur. */ -#include #include +#include #include #include #include -#include #include -#include -#include -#include -#include #include #include -#include +#include "abx500.h" -/* - * If AB8500 warm interrupt is set, user space will be notified. - * If user space doesn't shut down the platform within this time - * frame, this driver will. Time unit is ms. - */ #define DEFAULT_POWER_OFF_DELAY 10000 -#define DEFAULT_MONITOR_DELAY 1000 - -#define NUM_SENSORS 5 /* * The driver monitors GPADC - ADC_AUX1, ADC_AUX2, BTEMP_BALL @@ -57,265 +34,46 @@ */ #define NUM_MONITORED_SENSORS 4 -struct ab8500_temp { - struct platform_device *pdev; - struct device *hwmon_dev; - struct ab8500_gpadc *gpadc; - struct ab8500_btemp *btemp; - u8 gpadc_addr[NUM_SENSORS]; - unsigned long min[NUM_SENSORS]; - unsigned long max[NUM_SENSORS]; - unsigned long max_hyst[NUM_SENSORS]; - unsigned long crit[NUM_SENSORS]; - unsigned long min_alarm[NUM_SENSORS]; - unsigned long max_alarm[NUM_SENSORS]; - unsigned long max_hyst_alarm[NUM_SENSORS]; - unsigned long crit_alarm[NUM_SENSORS]; - struct delayed_work work; - struct delayed_work power_off_work; - struct mutex lock; - /* Delay (ms) between temperature readings */ - unsigned long gpadc_monitor_delay; - /* Delay (ms) before power off */ - unsigned long power_off_delay; -}; - -/* - * Thresholds are considered inactive if set to 0. - * To avoid confusion for user space applications, - * the temp monitor delay is set to 0 if all thresholds - * are 0. - */ -static bool find_active_thresholds(struct ab8500_temp *data) +static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor) { - int i; - for (i = 0; i < NUM_MONITORED_SENSORS; i++) - if (data->max[i] != 0 || data->max_hyst[i] != 0 - || data->min[i] != 0) - return true; - - dev_dbg(&data->pdev->dev, "No active thresholds," - "cancel deferred job (if it exists)" - "and reset temp monitor delay\n"); - cancel_delayed_work_sync(&data->work); - return false; -} - + int val; + /* + * Special treatment for the BAT_CTRL node, since this + * temperature measurement is more complex than just + * an ADC readout + * + * DIE_TEMP input temperature reading is not supported + * in AB8500 + */ + if (sensor == DIE_TEMP) + val = 0; + else if (sensor == BAT_CTRL) + val = ab8500_btemp_get_batctrl_temp(data->ab8500_btemp); + else + val = ab8500_gpadc_convert(data->ab8500_gpadc, sensor); -static inline void schedule_monitor(struct ab8500_temp *data) { - unsigned long delay_in_jiffies; - delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); - schedule_delayed_work(&data->work, delay_in_jiffies); + return val; } -static void thermal_power_off(struct work_struct *work) +static void ab8500_thermal_power_off(struct work_struct *work) { - struct ab8500_temp *data = container_of(work, struct ab8500_temp, + struct abx500_temp *data = container_of(work, struct abx500_temp, power_off_work.work); dev_warn(&data->pdev->dev, "Power off due to AB8500 thermal warning\n"); pm_power_off(); } -static void gpadc_monitor(struct work_struct *work) -{ - unsigned long delay_in_jiffies; - int val, i, ret; - /* Container for alarm node name */ - char alarm_node[30]; - - bool updated_min_alarm = false; - bool updated_max_alarm = false; - bool updated_max_hyst_alarm = false; - struct ab8500_temp *data = container_of(work, struct ab8500_temp, - work.work); - - for (i = 0; i < NUM_MONITORED_SENSORS; i++) { - /* Thresholds are considered inactive if set to 0 */ - if (data->max[i] == 0 && data->max_hyst[i] == 0 - && data->min[i] == 0) - continue; - - /* - * Special treatment for the BAT_CTRL node, since this - * temperature measurement is more complex than just - * an ADC readout - */ - if (data->gpadc_addr[i] == BAT_CTRL) { - val = ab8500_btemp_get_batctrl_temp(data->btemp); - } else { - val = ab8500_gpadc_convert(data->gpadc, - data->gpadc_addr[i]); - if (val < 0) { - dev_err(&data->pdev->dev, - "GPADC read failed\n"); - continue; - } - } - - mutex_lock(&data->lock); - if (data->min[i] != 0) { - if (val < data->min[i]) { - if (data->min_alarm[i] == 0) { - data->min_alarm[i] = 1; - updated_min_alarm = true; - } - } else { - if (data->min_alarm[i] == 1) { - data->min_alarm[i] = 0; - updated_min_alarm = true; - } - } - - } - if (data->max[i] != 0) { - if (val > data->max[i]) { - if (data->max_alarm[i] == 0) { - data->max_alarm[i] = 1; - updated_max_alarm = true; - } - } else { - if (data->max_alarm[i] == 1) { - data->max_alarm[i] = 0; - updated_max_alarm = true; - } - } - - } - if (data->max_hyst[i] != 0) { - if (val > data->max_hyst[i]) { - if (data->max_hyst_alarm[i] == 0) { - data->max_hyst_alarm[i] = 1; - updated_max_hyst_alarm = true; - } - } else { - if (data->max_hyst_alarm[i] == 1) { - data->max_hyst_alarm[i] = 0; - updated_max_hyst_alarm = true; - } - } - } - mutex_unlock(&data->lock); - - /* hwmon attr index starts at 1, thus "i+1" below */ - if (updated_min_alarm) { - ret = snprintf(alarm_node, 16, "temp%d_min_alarm", - (i + 1)); - if (ret < 0) { - dev_err(&data->pdev->dev, - "Unable to update alarm node (%d)", - ret); - break; - } - sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); - } - if (updated_max_alarm) { - ret = snprintf(alarm_node, 16, "temp%d_max_alarm", - (i + 1)); - if (ret < 0) { - dev_err(&data->pdev->dev, - "Unable to update alarm node (%d)", - ret); - break; - } - sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); - } - if (updated_max_hyst_alarm) { - ret = snprintf(alarm_node, 21, "temp%d_max_hyst_alarm", - (i + 1)); - if (ret < 0) { - dev_err(&data->pdev->dev, - "Unable to update alarm node (%d)", - ret); - break; - } - sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); - } - } - delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); - schedule_delayed_work(&data->work, delay_in_jiffies); -} - -static inline void gpadc_monitor_exit(struct platform_device *pdev) -{ - struct ab8500_temp *data = platform_get_drvdata(pdev); - cancel_delayed_work_sync(&data->work); -} - -static ssize_t set_temp_monitor_delay(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - int res; - unsigned long delay_in_s; - struct ab8500_temp *data = dev_get_drvdata(dev); - - res = strict_strtoul(buf, 10, &delay_in_s); - if (res < 0) - return res; - - mutex_lock(&data->lock); - data->gpadc_monitor_delay = delay_in_s * 1000; - - if (find_active_thresholds(data)) { - schedule_monitor(data); - } - - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t set_temp_power_off_delay(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - int res; - unsigned long delay_in_s; - struct ab8500_temp *data = dev_get_drvdata(dev); - - res = strict_strtoul(buf, 10, &delay_in_s); - if (res < 0) - return res; - - mutex_lock(&data->lock); - data->power_off_delay = delay_in_s * 1000; - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_temp_monitor_delay(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct ab8500_temp *data = dev_get_drvdata(dev); - /* return time in s, not ms */ - return sprintf(buf, "%lu\n", (data->gpadc_monitor_delay) / 1000); -} - -static ssize_t show_temp_power_off_delay(struct device *dev, - struct device_attribute *devattr, - char *buf) +static ssize_t ab8500_show_name(struct device *dev, + struct device_attribute *devattr, + char *buf) { - struct ab8500_temp *data = dev_get_drvdata(dev); - /* return time in s, not ms */ - return sprintf(buf, "%lu\n", (data->power_off_delay) / 1000); -} - -/* HWMON sysfs interface */ -static ssize_t show_name(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - /* - * To avoid confusion between sensor label and chip name, the function - * "show_label" is not used to return the chip name. - */ return sprintf(buf, "ab8500\n"); } -static ssize_t show_label(struct device *dev, - struct device_attribute *devattr, char *buf) +static ssize_t ab8500_show_label(struct device *dev, + struct device_attribute *devattr, + char *buf) { char *name; struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); @@ -349,310 +107,9 @@ static ssize_t show_label(struct device *dev, return sprintf(buf, "%s\n", name); } -static ssize_t show_input(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - int val; - struct ab8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - u8 gpadc_addr = data->gpadc_addr[attr->index - 1]; - - /* - * Special treatment for the BAT_CTRL node, since this - * temperature measurement is more complex than just - * an ADC readout - */ - if (gpadc_addr == BAT_CTRL) { - val = ab8500_btemp_get_batctrl_temp(data->btemp); - } else { - val = ab8500_gpadc_convert(data->gpadc, gpadc_addr); - if (val < 0) - dev_err(&data->pdev->dev, "GPADC read failed\n"); - } - - return sprintf(buf, "%d\n", val); -} - -/* set functions (RW nodes) */ -static ssize_t set_min(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct ab8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int res = strict_strtoul(buf, 10, &val); - if (res < 0) - return res; - - mutex_lock(&data->lock); - /* - * Threshold is considered inactive if set to 0 - * hwmon attr index starts at 1, thus "attr->index-1" below - */ - if (val == 0) - data->min_alarm[attr->index - 1] = 0; - - data->min[attr->index - 1] = val; - - if (val == 0) - (void) find_active_thresholds(data); - else - schedule_monitor(data); - - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t set_max(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct ab8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int res = strict_strtoul(buf, 10, &val); - if (res < 0) - return res; - - mutex_lock(&data->lock); - /* - * Threshold is considered inactive if set to 0 - * hwmon attr index starts at 1, thus "attr->index-1" below - */ - if (val == 0) - data->max_alarm[attr->index - 1] = 0; - - data->max[attr->index - 1] = val; - - if (val == 0) - (void) find_active_thresholds(data); - else - schedule_monitor(data); - - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t set_max_hyst(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct ab8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int res = strict_strtoul(buf, 10, &val); - if (res < 0) - return res; - - mutex_lock(&data->lock); - /* - * Threshold is considered inactive if set to 0 - * hwmon attr index starts at 1, thus "attr->index-1" below - */ - if (val == 0) - data->max_hyst_alarm[attr->index - 1] = 0; - - data->max_hyst[attr->index - 1] = val; - - if (val == 0) - (void) find_active_thresholds(data); - else - schedule_monitor(data); - - mutex_unlock(&data->lock); - - return count; -} - -/* - * show functions (RO nodes) - * Notice that min/max/max_hyst refer to millivolts and not millidegrees - */ -static ssize_t show_min(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct ab8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%ld\n", data->min[attr->index - 1]); -} - -static ssize_t show_max(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct ab8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%ld\n", data->max[attr->index - 1]); -} - -static ssize_t show_max_hyst(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct ab8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%ld\n", data->max_hyst[attr->index - 1]); -} - -/* Alarms */ -static ssize_t show_min_alarm(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct ab8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%ld\n", data->min_alarm[attr->index - 1]); -} - -static ssize_t show_max_alarm(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct ab8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%ld\n", data->max_alarm[attr->index - 1]); -} - -static ssize_t show_max_hyst_alarm(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct ab8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%ld\n", data->max_hyst_alarm[attr->index - 1]); -} - -static ssize_t show_crit_alarm(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct ab8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%ld\n", data->crit_alarm[attr->index - 1]); -} - -/*These node are not included in the kernel hwmon sysfs interface */ -static SENSOR_DEVICE_ATTR(temp_monitor_delay, S_IRUGO | S_IWUSR, - show_temp_monitor_delay, set_temp_monitor_delay, 0); -static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, - show_temp_power_off_delay, - set_temp_power_off_delay, 0); - -/* Chip name, required by hwmon*/ -static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); - -/* GPADC - ADC_AUX1 */ -static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 1); -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 1); -static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); -static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); -static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, - show_max_hyst, set_max_hyst, 1); -static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); -static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); -static SENSOR_DEVICE_ATTR(temp1_max_hyst_alarm, S_IRUGO, - show_max_hyst_alarm, NULL, 1); - -/* GPADC - ADC_AUX2 */ -static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 2); -static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 2); -static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min, 2); -static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max, 2); -static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IWUSR | S_IRUGO, - show_max_hyst, set_max_hyst, 2); -static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_min_alarm, NULL, 2); -static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_max_alarm, NULL, 2); -static SENSOR_DEVICE_ATTR(temp2_max_hyst_alarm, S_IRUGO, - show_max_hyst_alarm, NULL, 2); - -/* GPADC - BTEMP_BALL */ -static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 3); -static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 3); -static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min, set_min, 3); -static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max, set_max, 3); -static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IWUSR | S_IRUGO, - show_max_hyst, set_max_hyst, 3); -static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_min_alarm, NULL, 3); -static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_max_alarm, NULL, 3); -static SENSOR_DEVICE_ATTR(temp3_max_hyst_alarm, S_IRUGO, - show_max_hyst_alarm, NULL, 3); - -/* GPADC - BAT_CTRL */ -static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 4); -static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_input, NULL, 4); -static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_min, set_min, 4); -static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_max, set_max, 4); -static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IWUSR | S_IRUGO, - show_max_hyst, set_max_hyst, 4); -static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_min_alarm, NULL, 4); -static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_max_alarm, NULL, 4); -static SENSOR_DEVICE_ATTR(temp4_max_hyst_alarm, S_IRUGO, - show_max_hyst_alarm, NULL, 4); - -/* AB8500 */ -static SENSOR_DEVICE_ATTR(temp5_label, S_IRUGO, show_label, NULL, 5); -static SENSOR_DEVICE_ATTR(temp5_crit_alarm, S_IRUGO, - show_crit_alarm, NULL, 5); - -static struct attribute *ab8500_temp_attributes[] = { - &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, - &sensor_dev_attr_temp_monitor_delay.dev_attr.attr, - &sensor_dev_attr_name.dev_attr.attr, - /* GPADC - ADC_AUX1 */ - &sensor_dev_attr_temp1_label.dev_attr.attr, - &sensor_dev_attr_temp1_input.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_max_hyst_alarm.dev_attr.attr, - /* GPADC - ADC_AUX2 */ - &sensor_dev_attr_temp2_label.dev_attr.attr, - &sensor_dev_attr_temp2_input.dev_attr.attr, - &sensor_dev_attr_temp2_min.dev_attr.attr, - &sensor_dev_attr_temp2_max.dev_attr.attr, - &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp2_max_hyst_alarm.dev_attr.attr, - /* GPADC - BTEMP_BALL */ - &sensor_dev_attr_temp3_label.dev_attr.attr, - &sensor_dev_attr_temp3_input.dev_attr.attr, - &sensor_dev_attr_temp3_min.dev_attr.attr, - &sensor_dev_attr_temp3_max.dev_attr.attr, - &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp3_max_hyst_alarm.dev_attr.attr, - /* GPADC - BAT_CTRL */ - &sensor_dev_attr_temp4_label.dev_attr.attr, - &sensor_dev_attr_temp4_input.dev_attr.attr, - &sensor_dev_attr_temp4_min.dev_attr.attr, - &sensor_dev_attr_temp4_max.dev_attr.attr, - &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, - &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, - &sensor_dev_attr_temp4_max_hyst_alarm.dev_attr.attr, - /* AB8500 */ - &sensor_dev_attr_temp5_label.dev_attr.attr, - &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, - NULL -}; - -static const struct attribute_group ab8500_temp_group = { - .attrs = ab8500_temp_attributes, -}; - -static irqreturn_t ab8500_temp_irq_handler(int irq, void *irq_data) +static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) { unsigned long delay_in_jiffies; - struct platform_device *pdev = irq_data; - struct ab8500_temp *data = platform_get_drvdata(pdev); - /* * Make sure the magic numbers below corresponds to the node * used for AB8500 thermal warning from HW. @@ -660,55 +117,25 @@ static irqreturn_t ab8500_temp_irq_handler(int irq, void *irq_data) mutex_lock(&data->lock); data->crit_alarm[4] = 1; mutex_unlock(&data->lock); - sysfs_notify(&pdev->dev.kobj, NULL, "temp5_crit_alarm"); - dev_info(&pdev->dev, "AB8500 thermal warning, power off in %lu s\n", - data->power_off_delay); + sysfs_notify(&data->pdev->dev.kobj, NULL, "temp5_crit_alarm"); + dev_info(&data->pdev->dev, "AB8500 thermal warning," + " power off in %lu s\n", data->power_off_delay); delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); schedule_delayed_work(&data->power_off_work, delay_in_jiffies); - return IRQ_HANDLED; -} - -static int setup_irqs(struct platform_device *pdev) -{ - int ret; - int irq = platform_get_irq_byname(pdev, "AB8500_TEMP_WARM"); - - if (irq < 0) - dev_err(&pdev->dev, "Get irq by name failed\n"); - - ret = request_threaded_irq(irq, NULL, ab8500_temp_irq_handler, - IRQF_NO_SUSPEND, "ab8500-temp", pdev); - if (ret < 0) - dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); - - return ret; + return 0; } -static int __devinit ab8500_temp_probe(struct platform_device *pdev) +int __init abx500_hwmon_init(struct abx500_temp *data) { - struct ab8500_temp *data; - int err; - - data = kzalloc(sizeof(struct ab8500_temp), GFP_KERNEL); - if (!data) - return -ENOMEM; + data->ab8500_gpadc = ab8500_gpadc_get(); + if (IS_ERR(data->ab8500_gpadc)) + return PTR_ERR(data->ab8500_gpadc); - err = setup_irqs(pdev); - if (err < 0) - goto exit; - - data->gpadc = ab8500_gpadc_get(); - data->btemp = ab8500_btemp_get(); - - data->hwmon_dev = hwmon_device_register(&pdev->dev); - if (IS_ERR(data->hwmon_dev)) { - err = PTR_ERR(data->hwmon_dev); - dev_err(&pdev->dev, "Class registration failed (%d)\n", err); - goto exit; - } + data->ab8500_btemp = ab8500_btemp_get(); + if (IS_ERR(data->ab8500_btemp)) + return PTR_ERR(data->ab8500_btemp); - INIT_DELAYED_WORK_DEFERRABLE(&data->work, gpadc_monitor); - INIT_DELAYED_WORK(&data->power_off_work, thermal_power_off); + INIT_DELAYED_WORK(&data->power_off_work, ab8500_thermal_power_off); /* * Setup HW defined data. @@ -731,63 +158,14 @@ static int __devinit ab8500_temp_probe(struct platform_device *pdev) data->gpadc_addr[1] = ADC_AUX2; data->gpadc_addr[2] = BTEMP_BALL; data->gpadc_addr[3] = BAT_CTRL; - mutex_init(&data->lock); - data->pdev = pdev; + data->gpadc_addr[4] = DIE_TEMP; data->power_off_delay = DEFAULT_POWER_OFF_DELAY; - data->gpadc_monitor_delay = DEFAULT_MONITOR_DELAY; + data->monitored_sensors = NUM_MONITORED_SENSORS; - platform_set_drvdata(pdev, data); - - err = sysfs_create_group(&pdev->dev.kobj, &ab8500_temp_group); - if (err < 0) { - dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); - goto exit_platform_data; - } + data->ops.read_sensor = ab8500_read_sensor; + data->ops.irq_handler = ab8500_temp_irq_handler; + data->ops.show_name = ab8500_show_name; + data->ops.show_label = ab8500_show_label; return 0; - -exit_platform_data: - platform_set_drvdata(pdev, NULL); -exit: - kfree(data); - return err; } - -static int __devexit ab8500_temp_remove(struct platform_device *pdev) -{ - struct ab8500_temp *data = platform_get_drvdata(pdev); - - gpadc_monitor_exit(pdev); - hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&pdev->dev.kobj, &ab8500_temp_group); - platform_set_drvdata(pdev, NULL); - kfree(data); - return 0; -} - -/* No action required in suspend/resume, thus the lack of functions */ -static struct platform_driver ab8500_temp_driver = { - .driver = { - .owner = THIS_MODULE, - .name = "ab8500-temp", - }, - .probe = ab8500_temp_probe, - .remove = __devexit_p(ab8500_temp_remove), -}; - -static int __init ab8500_temp_init(void) -{ - return platform_driver_register(&ab8500_temp_driver); -} - -static void __exit ab8500_temp_exit(void) -{ - platform_driver_unregister(&ab8500_temp_driver); -} - -MODULE_AUTHOR("Martin Persson "); -MODULE_DESCRIPTION("AB8500 temperature driver"); -MODULE_LICENSE("GPL"); - -module_init(ab8500_temp_init) -module_exit(ab8500_temp_exit) diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c new file mode 100644 index 00000000000..9d522ef6f62 --- /dev/null +++ b/drivers/hwmon/abx500.c @@ -0,0 +1,667 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Martin Persson for + * ST-Ericsson. + * License terms: GNU Gereral Public License (GPL) version 2 + * + * Note: + * + * ABX500 does not provide auto ADC, so to monitor the required + * temperatures, a periodic work is used. It is more important + * to not wake up the CPU than to perform this job, hence the use + * of a deferred delay. + * + * A deferred delay for thermal monitor is considered safe because: + * If the chip gets too hot during a sleep state it's most likely + * due to external factors, such as the surrounding temperature. + * I.e. no SW decisions will make any difference. + * + * If/when the ABX500 thermal warning temperature is reached (threshold + * cannot be changed by SW), an interrupt is set and the driver + * notifies user space via a sysfs event. + * + * If/when ABX500 thermal shutdown temperature is reached a hardware + * shutdown of the ABX500 will occur. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "abx500.h" + +#define DEFAULT_MONITOR_DELAY 1000 + +/* + * Thresholds are considered inactive if set to 0. + * To avoid confusion for user space applications, + * the temp monitor delay is set to 0 if all thresholds + * are 0. + */ +static bool find_active_thresholds(struct abx500_temp *data) +{ + int i; + for (i = 0; i < data->monitored_sensors; i++) + if (data->max[i] != 0 || data->max_hyst[i] != 0 + || data->min[i] != 0) + return true; + + dev_dbg(&data->pdev->dev, "No active thresholds," + "cancel deferred job (if it exists)" + "and reset temp monitor delay\n"); + cancel_delayed_work_sync(&data->work); + return false; +} + +static inline void schedule_monitor(struct abx500_temp *data) +{ + unsigned long delay_in_jiffies; + delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); + schedule_delayed_work(&data->work, delay_in_jiffies); +} + +static inline void gpadc_monitor_exit(struct abx500_temp *data) +{ + cancel_delayed_work_sync(&data->work); +} + +static void gpadc_monitor(struct work_struct *work) +{ + unsigned long delay_in_jiffies; + int val, i, ret; + /* Container for alarm node name */ + char alarm_node[30]; + + bool updated_min_alarm = false; + bool updated_max_alarm = false; + bool updated_max_hyst_alarm = false; + struct abx500_temp *data = container_of(work, struct abx500_temp, + work.work); + + for (i = 0; i < data->monitored_sensors; i++) { + /* Thresholds are considered inactive if set to 0 */ + if (data->max[i] == 0 && data->max_hyst[i] == 0 + && data->min[i] == 0) + continue; + + val = data->ops.read_sensor(data, data->gpadc_addr[i]); + if (val < 0) { + dev_err(&data->pdev->dev, "GPADC read failed\n"); + continue; + } + + mutex_lock(&data->lock); + if (data->min[i] != 0) { + if (val < data->min[i]) { + if (data->min_alarm[i] == 0) { + data->min_alarm[i] = 1; + updated_min_alarm = true; + } + } else { + if (data->min_alarm[i] == 1) { + data->min_alarm[i] = 0; + updated_min_alarm = true; + } + } + + } + if (data->max[i] != 0) { + if (val > data->max[i]) { + if (data->max_alarm[i] == 0) { + data->max_alarm[i] = 1; + updated_max_alarm = true; + } + } else { + if (data->max_alarm[i] == 1) { + data->max_alarm[i] = 0; + updated_max_alarm = true; + } + } + + } + if (data->max_hyst[i] != 0) { + if (val > data->max_hyst[i]) { + if (data->max_hyst_alarm[i] == 0) { + data->max_hyst_alarm[i] = 1; + updated_max_hyst_alarm = true; + } + } else { + if (data->max_hyst_alarm[i] == 1) { + data->max_hyst_alarm[i] = 0; + updated_max_hyst_alarm = true; + } + } + } + mutex_unlock(&data->lock); + + /* hwmon attr index starts at 1, thus "i+1" below */ + if (updated_min_alarm) { + ret = snprintf(alarm_node, 16, "temp%d_min_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + if (updated_max_alarm) { + ret = snprintf(alarm_node, 16, "temp%d_max_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + if (updated_max_hyst_alarm) { + ret = snprintf(alarm_node, 21, "temp%d_max_hyst_alarm", + (i + 1)); + if (ret < 0) { + dev_err(&data->pdev->dev, + "Unable to update alarm node (%d)", + ret); + break; + } + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + } + delay_in_jiffies = msecs_to_jiffies(data->gpadc_monitor_delay); + schedule_delayed_work(&data->work, delay_in_jiffies); +} + +static ssize_t set_temp_monitor_delay(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int res; + unsigned long delay_in_s; + struct abx500_temp *data = dev_get_drvdata(dev); + + res = strict_strtoul(buf, 10, &delay_in_s); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->gpadc_monitor_delay = delay_in_s * 1000; + + if (find_active_thresholds(data)) + schedule_monitor(data); + + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int res; + unsigned long delay_in_s; + struct abx500_temp *data = dev_get_drvdata(dev); + + res = strict_strtoul(buf, 10, &delay_in_s); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->power_off_delay = delay_in_s * 1000; + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_temp_monitor_delay(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + /* return time in s, not ms */ + return sprintf(buf, "%lu\n", (data->gpadc_monitor_delay) / 1000); +} + +static ssize_t show_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + /* return time in s, not ms */ + return sprintf(buf, "%lu\n", (data->power_off_delay) / 1000); +} + +/* HWMON sysfs interface */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + /* + * To avoid confusion between sensor label and chip name, the function + * "show_label" is not used to return the chip name. + */ + struct abx500_temp *data = dev_get_drvdata(dev); + return data->ops.show_name(dev, devattr, buf); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + return data->ops.show_label(dev, devattr, buf); +} + +static ssize_t show_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + u8 gpadc_addr = data->gpadc_addr[attr->index - 1]; + + val = data->ops.read_sensor(data, gpadc_addr); + if (val < 0) + dev_err(&data->pdev->dev, "GPADC read failed\n"); + + return sprintf(buf, "%d\n", val); +} + +/* set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->min_alarm[attr->index - 1] = 0; + + data->min[attr->index - 1] = val; + + if (val == 0) + (void) find_active_thresholds(data); + else + schedule_monitor(data); + + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->max_alarm[attr->index - 1] = 0; + + data->max[attr->index - 1] = val; + + if (val == 0) + (void) find_active_thresholds(data); + else + schedule_monitor(data); + + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_max_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + /* + * Threshold is considered inactive if set to 0 + * hwmon attr index starts at 1, thus "attr->index-1" below + */ + if (val == 0) + data->max_hyst_alarm[attr->index - 1] = 0; + + data->max_hyst[attr->index - 1] = val; + + if (val == 0) + (void) find_active_thresholds(data); + else + schedule_monitor(data); + + mutex_unlock(&data->lock); + + return count; +} + +/* + * show functions (RO nodes) + */ +static ssize_t show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->min[attr->index - 1]); +} + +static ssize_t show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max[attr->index - 1]); +} + +static ssize_t show_max_hyst(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_hyst[attr->index - 1]); +} + +/* Alarms */ +static ssize_t show_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->min_alarm[attr->index - 1]); +} + +static ssize_t show_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_alarm[attr->index - 1]); +} + +static ssize_t show_max_hyst_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->max_hyst_alarm[attr->index - 1]); +} + +static ssize_t show_crit_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%ld\n", data->crit_alarm[attr->index - 1]); +} + +static SENSOR_DEVICE_ATTR(temp_monitor_delay, S_IRUGO | S_IWUSR, + show_temp_monitor_delay, set_temp_monitor_delay, 0); +static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, + show_temp_power_off_delay, + set_temp_power_off_delay, 0); + +/* Chip name, required by hwmon*/ +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); + +/* GPADC - SENSOR1 */ +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 1); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 1); + +/* GPADC - SENSOR2 */ +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min, 2); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max, 2); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 2); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_min_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_max_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 2); + +/* GPADC - SENSOR3 */ +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min, set_min, 3); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max, set_max, 3); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 3); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_min_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_max_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 3); + +/* GPADC - SENSOR4 */ +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_input, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_min, set_min, 4); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_max, set_max, 4); +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 4); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_min_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_max_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 4); + +/* GPADC - SENSOR4 */ +static SENSOR_DEVICE_ATTR(temp5_label, S_IRUGO, show_label, NULL, 5); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_input, NULL, 5); +static SENSOR_DEVICE_ATTR(temp5_crit_alarm, S_IRUGO, + show_crit_alarm, NULL, 5); + +struct attribute *abx500_temp_attributes[] = { + &sensor_dev_attr_name.dev_attr.attr, + &sensor_dev_attr_temp_monitor_delay.dev_attr.attr, + &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, + /* GPADC SENSOR1 */ + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst_alarm.dev_attr.attr, + /* GPADC SENSOR2 */ + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst_alarm.dev_attr.attr, + /* GPADC SENSOR3 */ + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst_alarm.dev_attr.attr, + /* GPADC SENSOR4 */ + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst_alarm.dev_attr.attr, + /* GPADC SENSOR5*/ + &sensor_dev_attr_temp5_label.dev_attr.attr, + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group abx500_temp_group = { + .attrs = abx500_temp_attributes, +}; + +static irqreturn_t abx500_temp_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + struct abx500_temp *data = platform_get_drvdata(pdev); + data->ops.irq_handler(irq, data); + return IRQ_HANDLED; +} + +static int setup_irqs(struct platform_device *pdev) +{ + int ret; + int irq = platform_get_irq_byname(pdev, "ABX500_TEMP_WARM"); + + if (irq < 0) + dev_err(&pdev->dev, "Get irq by name failed\n"); + + ret = request_threaded_irq(irq, NULL, abx500_temp_irq_handler, + IRQF_NO_SUSPEND, "abx500-temp", pdev); + if (ret < 0) + dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); + + return ret; +} + +static int __devinit abx500_temp_probe(struct platform_device *pdev) +{ + struct abx500_temp *data; + int err; + + data = kzalloc(sizeof(struct abx500_temp), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->pdev = pdev; + mutex_init(&data->lock); + + /* Chip specific initialization */ + err = abx500_hwmon_init(data); + if (err < 0) { + dev_err(&pdev->dev, "abx500 init failed"); + goto exit; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit; + } + + INIT_DELAYED_WORK_DEFERRABLE(&data->work, gpadc_monitor); + data->gpadc_monitor_delay = DEFAULT_MONITOR_DELAY; + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &abx500_temp_group); + if (err < 0) { + dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); + goto exit_platform_data; + } + + err = setup_irqs(pdev); + if (err < 0) { + dev_err(&pdev->dev, "irq setup failed (%d)\n", err); + goto exit_sysfs_group; + } + return 0; + +exit_sysfs_group: + sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); +exit_platform_data: + hwmon_device_unregister(data->hwmon_dev); + platform_set_drvdata(pdev, NULL); +exit: + kfree(data); + return err; +} + +static int __devexit abx500_temp_remove(struct platform_device *pdev) +{ + struct abx500_temp *data = platform_get_drvdata(pdev); + + gpadc_monitor_exit(data); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); + platform_set_drvdata(pdev, NULL); + kfree(data); + return 0; +} + +/* No action required in suspend/resume, thus the lack of functions */ +static struct platform_driver abx500_temp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "abx500-temp", + }, + .probe = abx500_temp_probe, + .remove = __devexit_p(abx500_temp_remove), +}; + +static int __init abx500_temp_init(void) +{ + return platform_driver_register(&abx500_temp_driver); +} + +static void __exit abx500_temp_exit(void) +{ + platform_driver_unregister(&abx500_temp_driver); +} + +MODULE_AUTHOR("Martin Persson "); +MODULE_DESCRIPTION("ABX500 temperature driver"); +MODULE_LICENSE("GPL"); + +module_init(abx500_temp_init) +module_exit(abx500_temp_exit) diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h new file mode 100644 index 00000000000..a089a63939d --- /dev/null +++ b/drivers/hwmon/abx500.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * License terms: GNU General Public License v2 + * Author: Martin Persson + */ + +#ifndef _ABX500_H +#define _ABX500_H + +#define NUM_SENSORS 5 + +struct ab8500_gpadc; +struct ab8500_btemp; +struct abx500_temp; + +/** + * struct abx500_temp_ops - abx500 chip specific ops + * @read_sensor: reads gpadc output + * @irq_handler: irq handler + * @show_name: hwmon device name + * @show_label: hwmon attribute label + */ +struct abx500_temp_ops { + int (*read_sensor)(struct abx500_temp *, u8); + int (*irq_handler)(int, struct abx500_temp *); + ssize_t (*show_name)(struct device *, + struct device_attribute *, char *); + ssize_t (*show_label) (struct device *, + struct device_attribute *, char *); +}; + +/** + * struct abx500_temp - representation of temp mon device + * @pdev: platform device + * @hwmon_dev: hwmon device + * @ab8500_gpadc: gpadc interface for ab8500 + * @btemp: battery temperature interface for ab8500 + * @gpadc_addr: gpadc channel address + * @temp: sensor temperature input value + * @min: sensor temperature min value + * @max: sensor temperature max value + * @max_hyst: sensor temperature hysteresis value for max limit + * @crit: sensor temperature critical value + * @min_alarm: sensor temperature min alarm + * @max_alarm: sensor temperature max alarm + * @max_hyst_alarm: sensor temperature hysteresis alarm + * @crit_alarm: sensor temperature critical value alarm + * @work: delayed work scheduled to monitor temperature periodically + * @power_off_work: delayed work scheduled to power off the system + when critical temperature is reached + * @lock: mutex + * @gpadc_monitor_delay: delay between temperature readings in ms + * @power_off_delay: delay before power off in ms + * @monitored_sensors: number of monitored sensors + */ +struct abx500_temp { + struct platform_device *pdev; + struct device *hwmon_dev; + struct ab8500_gpadc *ab8500_gpadc; + struct ab8500_btemp *ab8500_btemp; + struct abx500_temp_ops ops; + u8 gpadc_addr[NUM_SENSORS]; + unsigned long temp[NUM_SENSORS]; + unsigned long min[NUM_SENSORS]; + unsigned long max[NUM_SENSORS]; + unsigned long max_hyst[NUM_SENSORS]; + unsigned long crit[NUM_SENSORS]; + unsigned long min_alarm[NUM_SENSORS]; + unsigned long max_alarm[NUM_SENSORS]; + unsigned long max_hyst_alarm[NUM_SENSORS]; + unsigned long crit_alarm[NUM_SENSORS]; + struct delayed_work work; + struct delayed_work power_off_work; + struct mutex lock; + /* Delay (ms) between temperature readings */ + unsigned long gpadc_monitor_delay; + /* Delay (ms) before power off */ + unsigned long power_off_delay; + int monitored_sensors; +}; + +int abx500_hwmon_init(struct abx500_temp *data) __init; + +#endif /* _ABX500_H */ diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index 4b1a40fe2fe..c352221925a 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -680,7 +680,7 @@ static struct resource __devinitdata ab8500_usb_resources[] = { static struct resource __devinitdata ab8500_temp_resources[] = { { - .name = "AB8500_TEMP_WARM", + .name = "ABX500_TEMP_WARM", .start = AB8500_INT_TEMP_WARM, .end = AB8500_INT_TEMP_WARM, .flags = IORESOURCE_IRQ, @@ -774,7 +774,7 @@ static struct mfd_cell __devinitdata ab8500_devs[] = { .name = "ab8500-denc", }, { - .name = "ab8500-temp", + .name = "abx500-temp", .num_resources = ARRAY_SIZE(ab8500_temp_resources), .resources = ab8500_temp_resources, }, -- cgit v1.2.3 From 405e2f354d0c23f017b16986530cc4e4ff8a7b5d Mon Sep 17 00:00:00 2001 From: Rajagopala V Date: Thu, 25 Aug 2011 13:27:42 +0530 Subject: abx500: hwmon: temperature monitor support for AB5500 Includes support for xtal temp, pcb temp and die temp monitoring ST-Ericsson Linux next: NA ST-Ericsson ID: WP257616 & WP351655 ST-Ericsson FOSS-OUT ID: NA Change-Id: I7668fb36df957b66929bfd13a4b6ad0386036aa1 Signed-off-by: Rajagopala V Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28139 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/Kconfig | 14 +++- drivers/hwmon/Makefile | 1 + drivers/hwmon/ab5500.c | 190 +++++++++++++++++++++++++++++++++++++++++++++ drivers/hwmon/abx500.c | 2 + drivers/hwmon/abx500.h | 6 ++ drivers/mfd/ab5500-core.c | 13 ++++ include/linux/mfd/abx500.h | 1 + 7 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 drivers/hwmon/ab5500.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 7dadc125e92..0bf9eac1633 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -50,7 +50,19 @@ config SENSORS_AB8500 used to access sensors outside the AB8500 chip. This driver can also be built as a module. If so, the module - will be called ab8500-temp. + will be called abx500-temp. + +config SENSORS_AB5500 + tristate "AB5500 thermal monitoring" + depends on AB5500_GPADC + default n + help + If you say yes here you get support for the thermal sensor part + of the AB5500 chip. The driver includes thermal management for + AB5500 die, pcb and RF XTAL temperature. + + This driver can also be built as a module. If so, the module + will be called abx500-temp. config SENSORS_DB8500 tristate "DB8500 thermal monitoring" diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 1a085a872ca..7ce440cdd6c 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o +obj-$(CONFIG_SENSORS_AB5500) += abx500.o ab5500.o obj-$(CONFIG_SENSORS_DB8500) += db8500.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o diff --git a/drivers/hwmon/ab5500.c b/drivers/hwmon/ab5500.c new file mode 100644 index 00000000000..f229d103ec1 --- /dev/null +++ b/drivers/hwmon/ab5500.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Author: Martin Persson for + * ST-Ericsson. + * License terms: GNU Gereral Public License (GPL) version 2 + * + * Note: + * + * If/when the AB5500 thermal warning temperature is reached (threshold + * 125C cannot be changed by SW), an interrupt is set and the driver + * notifies user space via a sysfs event. If a shut down is not + * triggered by user space and temperature reaches beyond critical + * limit(130C) pm_power off is called. + * + * If/when AB5500 thermal shutdown temperature is reached a hardware + * shutdown of the AB5500 will occur. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "abx500.h" + +/* AB5500 driver monitors GPADC - XTAL_TEMP, PCB_TEMP, + * BTEMP_BALL, BAT_CTRL and DIE_TEMP + */ +#define NUM_MONITORED_SENSORS 5 + +#define SHUTDOWN_AUTO_MIN_LIMIT -25 +#define SHUTDOWN_AUTO_MAX_LIMIT 130 + +static int ab5500_output_convert(int val, u8 sensor) +{ + int res = val; + /* GPADC returns die temperature in Celsius + * convert it to millidegree celsius + */ + if (sensor == DIE_TEMP) + res = val * 1000; + + return res; +} + +static int ab5500_read_sensor(struct abx500_temp *data, u8 sensor) +{ + /* + * TODO: Add support for BAT_CTRL node, since this + * temperature measurement is more complex than just + * an ADC readout + */ + int val = ab5500_gpadc_convert(data->ab5500_gpadc, sensor); + if (val < 0) + return val; + else + return ab5500_output_convert(val, sensor); +} + +static ssize_t ab5500_show_name(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "ab5500\n"); +} + +static ssize_t ab5500_show_label(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + char *name; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int index = attr->index; + + /* + * Make sure these labels correspond to the attribute indexes + * used when calling SENSOR_DEVICE_ATRR. + * Temperature sensors outside ab8500 (read via GPADC) are marked + * with prefix ext_ + */ + switch (index) { + case 1: + name = "xtal_temp"; + break; + case 2: + name = "pcb_temp"; + break; + case 3: + name = "bat_temp"; + break; + case 4: + name = "bat_ctrl"; + break; + case 5: + name = "ab5500"; + break; + default: + return -EINVAL; + } + return sprintf(buf, "%s\n", name); +} + +static int temp_shutdown_trig(int mux) +{ + pm_power_off(); + return 0; +} + +static int ab5500_temp_shutdown_auto(struct abx500_temp *data) +{ + int ret; + struct adc_auto_input *auto_ip; + + auto_ip = kzalloc(sizeof(struct adc_auto_input), GFP_KERNEL); + if (!auto_ip) { + dev_err(&data->pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + auto_ip->mux = DIE_TEMP; + auto_ip->freq = MS500; + auto_ip->min = SHUTDOWN_AUTO_MIN_LIMIT; + auto_ip->max = SHUTDOWN_AUTO_MAX_LIMIT; + auto_ip->auto_adc_callback = temp_shutdown_trig; + data->gpadc_auto = auto_ip; + ret = ab5500_gpadc_convert_auto(data->ab5500_gpadc, + data->gpadc_auto); + if (ret < 0) + kfree(auto_ip); + + return ret; +} + +static int ab5500_temp_irq_handler(int irq, struct abx500_temp *data) +{ + /* + * Make sure the magic numbers below corresponds to the node + * used for AB5500 thermal warning from HW. + */ + mutex_lock(&data->lock); + data->crit_alarm[4] = 1; + mutex_unlock(&data->lock); + sysfs_notify(&data->pdev->dev.kobj, NULL, "temp5_crit_alarm"); + dev_info(&data->pdev->dev, "ABX500 thermal warning," + " power off system now!\n"); + return 0; +} + +int __init abx500_hwmon_init(struct abx500_temp *data) +{ + int err; + + data->ab5500_gpadc = ab5500_gpadc_get("ab5500-adc.0"); + if (IS_ERR(data->ab5500_gpadc)) + return PTR_ERR(data->ab5500_gpadc); + + err = ab5500_temp_shutdown_auto(data); + if (err < 0) { + dev_err(&data->pdev->dev, "Failed to register" + " auto trigger(%d)\n", err); + return err; + } + + /* + * Setup HW defined data. + * + * Reference hardware (HREF): + * + * XTAL_TEMP, PCB_TEMP, BTEMP_BALL refer to millivolts and + * BAT_CTRL and DIE_TEMP refer to millidegrees + * + * Make sure indexes correspond to the attribute indexes + * used when calling SENSOR_DEVICE_ATRR + */ + data->gpadc_addr[0] = XTAL_TEMP; + data->gpadc_addr[1] = PCB_TEMP; + data->gpadc_addr[2] = BTEMP_BALL; + data->gpadc_addr[3] = BAT_CTRL; + data->gpadc_addr[4] = DIE_TEMP; + data->monitored_sensors = NUM_MONITORED_SENSORS; + + data->ops.read_sensor = ab5500_read_sensor; + data->ops.irq_handler = ab5500_temp_irq_handler; + data->ops.show_name = ab5500_show_name; + data->ops.show_label = ab5500_show_label; + + return 0; +} diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c index 9d522ef6f62..6666dc27065 100644 --- a/drivers/hwmon/abx500.c +++ b/drivers/hwmon/abx500.c @@ -623,6 +623,7 @@ exit_platform_data: hwmon_device_unregister(data->hwmon_dev); platform_set_drvdata(pdev, NULL); exit: + kfree(data->gpadc_auto); kfree(data); return err; } @@ -635,6 +636,7 @@ static int __devexit abx500_temp_remove(struct platform_device *pdev) hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); platform_set_drvdata(pdev, NULL); + kfree(data->gpadc_auto); kfree(data); return 0; } diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h index a089a63939d..1d41aa191fe 100644 --- a/drivers/hwmon/abx500.h +++ b/drivers/hwmon/abx500.h @@ -10,7 +10,9 @@ #define NUM_SENSORS 5 struct ab8500_gpadc; +struct ab5500_gpadc; struct ab8500_btemp; +struct adc_auto_input; struct abx500_temp; /** @@ -34,7 +36,9 @@ struct abx500_temp_ops { * @pdev: platform device * @hwmon_dev: hwmon device * @ab8500_gpadc: gpadc interface for ab8500 + * @ab5500_gpadc: gpadc interface for ab5500 * @btemp: battery temperature interface for ab8500 + * @adc_auto_input: gpadc auto trigger * @gpadc_addr: gpadc channel address * @temp: sensor temperature input value * @min: sensor temperature min value @@ -57,7 +61,9 @@ struct abx500_temp { struct platform_device *pdev; struct device *hwmon_dev; struct ab8500_gpadc *ab8500_gpadc; + struct ab5500_gpadc *ab5500_gpadc; struct ab8500_btemp *ab8500_btemp; + struct adc_auto_input *gpadc_auto; struct abx500_temp_ops ops; u8 gpadc_addr[NUM_SENSORS]; unsigned long temp[NUM_SENSORS]; diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 189f4b66bfa..9c47f075962 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -1389,6 +1389,19 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, }, }, + [AB5500_DEVID_TEMPMON] = { + .name = "abx500-temp", + .id = AB5500_DEVID_TEMPMON, + .num_resources = 1, + .resources = (struct resource[]) { + { + .name = "ABX500_TEMP_WARM", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 2), + .end = AB5500_IRQ(2, 2), + }, + }, + }, }; /* diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 7a1d3179f6a..e99a3d177ea 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -230,6 +230,7 @@ enum ab5500_devid { AB5500_DEVID_ONSWA, AB5500_DEVID_CHARGALG, AB5500_DEVID_BTEMP, + AB5500_DEVID_TEMPMON, AB5500_NUM_DEVICES, }; -- cgit v1.2.3 From 9109da2bb89b0bd557ad15aca06417c8dec66155 Mon Sep 17 00:00:00 2001 From: Rajagopala V Date: Thu, 25 Aug 2011 13:31:50 +0530 Subject: hwmon: ab5500: support for battery temp monitor add support for battery temperature monitor ST-Ericsson Linux next: NA ST-Ericsson ID: WP257616 ST-Ericsson FOSS-OUT ID: NA Change-Id: I344947ed41b4dd0a49abcb28e57b7b3c697dc57d Signed-off-by: Rajagopala V Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28233 Reviewed-by: Arun MURTHY Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/ab5500.c | 14 ++++++++++++-- drivers/hwmon/abx500.h | 2 ++ drivers/power/ab5500_btemp.c | 11 +++++++++++ include/linux/mfd/abx500/ab5500-bm.h | 10 ++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/ab5500.c b/drivers/hwmon/ab5500.c index f229d103ec1..79540c9a79b 100644 --- a/drivers/hwmon/ab5500.c +++ b/drivers/hwmon/ab5500.c @@ -23,6 +23,7 @@ #include #include #include +#include #include "abx500.h" /* AB5500 driver monitors GPADC - XTAL_TEMP, PCB_TEMP, @@ -47,12 +48,17 @@ static int ab5500_output_convert(int val, u8 sensor) static int ab5500_read_sensor(struct abx500_temp *data, u8 sensor) { + int val; /* - * TODO: Add support for BAT_CTRL node, since this + * Special treatment for BAT_CTRL node, since this * temperature measurement is more complex than just * an ADC readout */ - int val = ab5500_gpadc_convert(data->ab5500_gpadc, sensor); + if (sensor == BAT_CTRL) + val = ab5500_btemp_get_batctrl_temp(data->ab5500_btemp); + else + val = ab5500_gpadc_convert(data->ab5500_gpadc, sensor); + if (val < 0) return val; else @@ -156,6 +162,10 @@ int __init abx500_hwmon_init(struct abx500_temp *data) if (IS_ERR(data->ab5500_gpadc)) return PTR_ERR(data->ab5500_gpadc); + data->ab5500_btemp = ab5500_btemp_get(); + if (IS_ERR(data->ab5500_btemp)) + return PTR_ERR(data->ab5500_btemp); + err = ab5500_temp_shutdown_auto(data); if (err < 0) { dev_err(&data->pdev->dev, "Failed to register" diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h index 1d41aa191fe..77ae7cbd5c1 100644 --- a/drivers/hwmon/abx500.h +++ b/drivers/hwmon/abx500.h @@ -12,6 +12,7 @@ struct ab8500_gpadc; struct ab5500_gpadc; struct ab8500_btemp; +struct ab5500_btemp; struct adc_auto_input; struct abx500_temp; @@ -63,6 +64,7 @@ struct abx500_temp { struct ab8500_gpadc *ab8500_gpadc; struct ab5500_gpadc *ab5500_gpadc; struct ab8500_btemp *ab8500_btemp; + struct ab5500_btemp *ab5500_btemp; struct adc_auto_input *gpadc_auto; struct abx500_temp_ops ops; u8 gpadc_addr[NUM_SENSORS]; diff --git a/drivers/power/ab5500_btemp.c b/drivers/power/ab5500_btemp.c index 04ad6bbb7ac..2e2a5054a58 100644 --- a/drivers/power/ab5500_btemp.c +++ b/drivers/power/ab5500_btemp.c @@ -109,6 +109,17 @@ struct ab5500_btemp *ab5500_btemp_get(void) return di; } +/** + * ab5500_btemp_get_batctrl_temp() - get the temperature + * @di: pointer to the ab5500_btemp structure + * + * Returns the batctrl temperature in millidegrees + */ +int ab5500_btemp_get_batctrl_temp(struct ab5500_btemp *di) +{ + return di->bat_temp * 1000; +} + /** * ab5500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance * @di: pointer to the ab5500_btemp structure diff --git a/include/linux/mfd/abx500/ab5500-bm.h b/include/linux/mfd/abx500/ab5500-bm.h index b9800cd8c19..1bb22614b27 100644 --- a/include/linux/mfd/abx500/ab5500-bm.h +++ b/include/linux/mfd/abx500/ab5500-bm.h @@ -100,9 +100,19 @@ #ifdef CONFIG_AB5500_BM void ab5500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); +struct ab5500_btemp *ab5500_btemp_get(void); +int ab5500_btemp_get_batctrl_temp(struct ab5500_btemp *btemp); #else static void ab5500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) { } +inline struct ab5500_btemp *ab5500_btemp_get(void) +{ + return 0; +} +inline int ab5500_btemp_get_batctrl_temp(struct ab5500_btemp *btemp) +{ + return 0; +} #endif #endif /* _AB5500_BM_H */ -- cgit v1.2.3 From fd465381982e50fe022f5a5933dc50addc9c7acc Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Tue, 16 Aug 2011 11:35:23 +0530 Subject: power: ab5500-fg: Proper registering of power supply During boot if the battery voltage exceed the max voltage, then an interrupt is triggered to the GPADC for auto control. This in turn will call the fg registered callback handler. But this happens before the power supply class of fg is registered. Thereby calling power_supply_changed() will lead to BUG since it used the wake lock which has not yet been initializes. Hence register power supply class for fg first followed by initializing the hardware registers, which includes registering for a gpadc auto convert for battery voltage. ST-Ericsson Linux next: NA ST-Ericsson ID: 355236 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Iaa291c5ebbd8c2ba3fcee579de3605805a306ebd Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28777 Reviewed-by: QATEST Reviewed-by: Rabin VINCENT --- drivers/power/ab5500_fg.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/power/ab5500_fg.c b/drivers/power/ab5500_fg.c index 95f31cb84cb..bb2bfb9e840 100644 --- a/drivers/power/ab5500_fg.c +++ b/drivers/power/ab5500_fg.c @@ -1751,13 +1751,6 @@ static int __devinit ab5500_fg_probe(struct platform_device *pdev) list_add_tail(&di->node, &ab5500_fg_list); - /* Initialize OVV, and other registers */ - ret = ab5500_fg_init_hw_registers(di); - if (ret) { - dev_err(di->dev, "failed to initialize registers\n"); - goto free_fg_wq; - } - /* Consider battery unknown until we're informed otherwise */ di->flags.batt_unknown = true; @@ -1768,6 +1761,13 @@ static int __devinit ab5500_fg_probe(struct platform_device *pdev) goto free_fg_wq; } + /* Initialize OVV, and other registers */ + ret = ab5500_fg_init_hw_registers(di); + if (ret) { + dev_err(di->dev, "failed to initialize registers\n"); + goto pow_unreg; + } + di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); ab5500_fg_coulomb_counter(di, true); @@ -1793,6 +1793,8 @@ static int __devinit ab5500_fg_probe(struct platform_device *pdev) dev_info(di->dev, "probe success\n"); return ret; +pow_unreg: + power_supply_unregister(&di->fg_psy); free_fg_wq: destroy_workqueue(di->fg_wq); free_device_info: -- cgit v1.2.3 From c35a9eb5e673a161cd2fdeadd8cdce9a27b3bed4 Mon Sep 17 00:00:00 2001 From: Andrew Lynn Date: Thu, 25 Aug 2011 13:37:48 +0530 Subject: ab8500-bm: Move current measurements to workqueue Move 'instant' current measurement, which is actually averaged over 250ms, to its own workqueue. Provide interfaces for blocking and non-blocking clients. ST-Ericsson ID: 350661 ST-Ericsson FOSS-OUT ID: Trivial ST-Ericsson Linux next: NA Change-Id: Id7daddbc4dcea4562bd76a986b4efa769c5a03c6 Signed-off-by: Andrew Lynn Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/27868 Reviewed-by: QATOOLS Reviewed-by: QABUILD Reviewed-by: QATEST Reviewed-by: Johan PALSSON Reviewed-by: Karl KOMIEROWSKI --- drivers/power/ab8500_fg.c | 227 ++++++++++++++++++++++++++++++++++++++---- include/linux/mfd/ab8500/bm.h | 33 +++++- 2 files changed, 235 insertions(+), 25 deletions(-) diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index b7462d493e0..62b4adae0a6 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -123,9 +123,15 @@ struct ab8500_fg_flags { bool calibrate; }; +struct inst_curr_result_list { + struct list_head list; + int *result; +}; + /** * struct ab8500_fg - ab8500 FG device information * @dev: Pointer to the structure device + * @node: a list of AB8500 FGs, hence prepared for reentrance * @vbat: Battery voltage in mV * @vbat_nom: Nominal battery voltage in mV * @inst_curr: Instantenous battery current in mA @@ -138,6 +144,23 @@ struct ab8500_fg_flags { * @recovery_needed: Indicate if recovery is needed * @high_curr_mode: Indicate if we're in high current mode * @init_capacity: Indicate if initial capacity measuring should be done + * @inst_curr_mip: Indicate if 'instant' current measurement is in progress + * AB8500 does not support real instant current + * readings. The best we can do is sample over + * 250ms. + * @inst_curr_lock: Control access to inst curr wait queues and result lists + * @inst_curr_wq: Work queue for running 'instant' current measurements + * @fg_inst_curr_work: Work to measure 'instant' current + * @result_wq: Wait queue for blocking 'instant' current clients + * @cpw_a_wq: Physical wait queue A for concurrent inst curr clients + * @cpw_b_wq: Physical wait queue B for concurrent inst curr clients + * @cpw_next_wq: Logical next wait queue for concurrent inst curr clients + * @cpw_this_wq: Logical this wait queue for concurrent inst curr clients + * @inst_curr_result: Result register for blocking instant current clients + * @result_a_list: Physical result list A for concurrent inst curr clients + * @result_b_list: Physical result list B for concurrent inst curr clients + * @next_result_list: Logical next results for concurrent inst curr clients + * @this_result_list: Logical this results for concurrent inst curr clients * @calib_state State during offset calibration * @discharge_state: Current discharge state * @charge_state: Current charge state @@ -159,6 +182,7 @@ struct ab8500_fg_flags { */ struct ab8500_fg { struct device *dev; + struct list_head node; int vbat; int vbat_nom; int inst_curr; @@ -171,6 +195,20 @@ struct ab8500_fg { bool recovery_needed; bool high_curr_mode; bool init_capacity; + bool inst_curr_mip; + spinlock_t inst_curr_lock; + struct workqueue_struct *inst_curr_wq; + struct delayed_work fg_inst_curr_work; + wait_queue_head_t result_wq; + wait_queue_head_t cpw_a_wq; + wait_queue_head_t cpw_b_wq; + wait_queue_head_t *cpw_next_wq; + wait_queue_head_t *cpw_this_wq; + int inst_curr_result; + struct list_head result_a_list; + struct list_head result_b_list; + struct list_head *next_result_list; + struct list_head *this_result_list; enum ab8500_fg_calibration_state calib_state; enum ab8500_fg_discharge_state discharge_state; enum ab8500_fg_charge_state charge_state; @@ -190,6 +228,22 @@ struct ab8500_fg { struct mutex cc_lock; struct kobject fg_kobject; }; +static LIST_HEAD(ab8500_fg_list); + +/** + * ab8500_fg_get() - returns a reference to the primary AB8500 fuel gauge + * (i.e. the first fuel gauge in the instance list) + */ +struct ab8500_fg *ab8500_fg_get(void) +{ + struct ab8500_fg *fg; + + if (list_empty(&ab8500_fg_list)) + return NULL; + + fg = list_first_entry(&ab8500_fg_list, struct ab8500_fg, node); + return fg; +} /* Main battery properties */ static enum power_supply_property ab8500_fg_props[] = { @@ -439,24 +493,101 @@ cc_err: } /** - * ab8500_fg_inst_curr() - battery instantaneous current + * ab8500_fg_inst_curr_nonblocking() - battery instantaneous current + * @di: pointer to the ab8500_fg structure + * @local_result: pointer to result location, updated after measurement is + * completed ~250ms after this function returns + * + * Returns error code + */ +int ab8500_fg_inst_curr_nonblocking(struct ab8500_fg *di, int *local_result) +{ + DEFINE_WAIT(wait); + + /* + * caller needs to do some other work at + * the same time as current measurement + */ + wait_queue_head_t *cpw_my_wq; + struct inst_curr_result_list *new = + kmalloc(sizeof(struct inst_curr_result_list), GFP_KERNEL); + if (!new) + return -ENOMEM; + new->result = local_result; + *local_result = INVALID_CURRENT; + INIT_LIST_HEAD(&new->list); + spin_lock(&di->inst_curr_lock); + list_add(&new->list, di->next_result_list); + cpw_my_wq = di->cpw_next_wq; + add_wait_queue(cpw_my_wq, &wait); + /* queue_work may schedule */ + queue_delayed_work(di->inst_curr_wq, &di->fg_inst_curr_work, 1); + set_current_state(TASK_UNINTERRUPTIBLE); + spin_unlock(&di->inst_curr_lock); + schedule(); + finish_wait(cpw_my_wq, &wait); + return 0; +} + +/** + * ab8500_fg_inst_curr_blocking() - battery instantaneous current * @di: pointer to the ab8500_fg structure * * Returns battery instantenous current(on success) else error code */ -static int ab8500_fg_inst_curr(struct ab8500_fg *di) +int ab8500_fg_inst_curr_blocking(struct ab8500_fg *di) +{ + DEFINE_WAIT(wait); + + /* caller will wait for the next available result */ + spin_lock(&di->inst_curr_lock); + add_wait_queue(&di->result_wq, &wait); + if (!di->inst_curr_mip) + /* queue_work may schedule */ + queue_delayed_work(di->inst_curr_wq, &di->fg_inst_curr_work, 1); + set_current_state(TASK_UNINTERRUPTIBLE); + spin_unlock(&di->inst_curr_lock); + schedule(); + finish_wait(&di->result_wq, &wait); + return di->inst_curr_result; +} + +/** + * ab8500_fg_inst_curr_work() - take an 'instant' battery current reading + * @work: pointer to the work_struct structure + * + * AB8500 does not provide instant current readings, the best we can do is + * average over 250ms. + */ +static void ab8500_fg_inst_curr_work(struct work_struct *work) { + struct list_head *temp_result_list; + wait_queue_head_t *cpw_temp_wq; u8 low, high, reg_val; static int val; int ret = 0; bool fg_off = false; + struct ab8500_fg *di = container_of(work, + struct ab8500_fg, fg_inst_curr_work.work); + + spin_lock(&di->inst_curr_lock); + temp_result_list = di->next_result_list; + cpw_temp_wq = di->cpw_next_wq; + di->next_result_list = di->this_result_list; + di->cpw_next_wq = di->cpw_this_wq; + di->this_result_list = temp_result_list; + di->cpw_this_wq = cpw_temp_wq; + + di->inst_curr_mip = true; + spin_unlock(&di->inst_curr_lock); + mutex_lock(&di->cc_lock); ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, AB8500_RTC_CC_CONF_REG, ®_val); if (ret < 0) - goto inst_curr_err; + goto inst_curr_err1; if (!(reg_val & CC_PWR_UP_ENA)) { dev_dbg(di->dev, "%s Enable FG\n", __func__); @@ -467,21 +598,23 @@ static int ab8500_fg_inst_curr(struct ab8500_fg *di) AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU, SEC_TO_SAMPLE(10)); if (ret) - goto inst_curr_err; + goto inst_curr_err1; /* Start the CC */ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, AB8500_RTC_CC_CONF_REG, (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA)); if (ret) - goto inst_curr_err; + goto inst_curr_err1; } /* Reset counter and Read request */ ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, (RESET_ACCU | READ_REQ)); if (ret) - goto inst_curr_err; + goto inst_curr_err1; + + wake_up(di->cpw_this_wq); /* * Since there is no interrupt for this, just wait for 250ms @@ -493,12 +626,12 @@ static int ab8500_fg_inst_curr(struct ab8500_fg *di) ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, AB8500_GASG_CC_SMPL_CNVL_REG, &low); if (ret < 0) - goto inst_curr_err; + goto inst_curr_err2; ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE, AB8500_GASG_CC_SMPL_CNVH_REG, &high); if (ret < 0) - goto inst_curr_err; + goto inst_curr_err2; /* * negative value for Discharging @@ -524,23 +657,42 @@ static int ab8500_fg_inst_curr(struct ab8500_fg *di) ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0); if (ret) - goto inst_curr_err; + goto inst_curr_err3; /* Stop the CC */ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, AB8500_RTC_CC_CONF_REG, 0); if (ret) - goto inst_curr_err; + goto inst_curr_err3; } +finished: mutex_unlock(&di->cc_lock); - return val; + spin_lock(&di->inst_curr_lock); + di->inst_curr_result = val; + + while (!list_empty(di->this_result_list)) { + struct inst_curr_result_list *this = list_first_entry( + di->this_result_list, + struct inst_curr_result_list, + list); + *(this->result) = val; + list_del(&this->list); + kfree(this); + } + di->inst_curr_mip = false; + wake_up(&di->result_wq); + spin_unlock(&di->inst_curr_lock); + return; -inst_curr_err: +inst_curr_err1: + wake_up(di->cpw_this_wq); +inst_curr_err2: + val = 0; +inst_curr_err3: dev_err(di->dev, "%s Get instanst current failed\n", __func__); - mutex_unlock(&di->cc_lock); - return ret; + goto finished; } /** @@ -690,7 +842,7 @@ static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di) { int vbat_comp; - di->inst_curr = ab8500_fg_inst_curr(di); + di->inst_curr = ab8500_fg_inst_curr_blocking(di); di->vbat = ab8500_fg_bat_voltage(di); /* Use Ohms law to get the load compensated voltage */ @@ -788,7 +940,7 @@ static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di) /* We need to update battery voltage and inst current when charging */ di->vbat = ab8500_fg_bat_voltage(di); - di->inst_curr = ab8500_fg_inst_curr(di); + di->inst_curr = ab8500_fg_inst_curr_blocking(di); return di->bat_cap.mah; } @@ -1138,7 +1290,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) * RECOVERY_SLEEP if time left. * If high, go to READOUT */ - di->inst_curr = ab8500_fg_inst_curr(di); + di->inst_curr = ab8500_fg_inst_curr_blocking(di); if (ab8500_fg_is_low_curr(di, di->inst_curr)) { if (di->recovery_cnt > @@ -1166,7 +1318,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) break; case AB8500_FG_DISCHARGE_READOUT: - di->inst_curr = ab8500_fg_inst_curr(di); + di->inst_curr = ab8500_fg_inst_curr_blocking(di); if (ab8500_fg_is_low_curr(di, di->inst_curr)) { /* Detect mode change */ @@ -1221,7 +1373,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) case AB8500_FG_DISCHARGE_WAKEUP: ab8500_fg_coulomb_counter(di, true); - di->inst_curr = ab8500_fg_inst_curr(di); + di->inst_curr = ab8500_fg_inst_curr_blocking(di); ab8500_fg_calc_cap_discharge_voltage(di, true); @@ -1340,7 +1492,7 @@ static void ab8500_fg_periodic_work(struct work_struct *work) if (di->init_capacity) { /* A dummy read that will return 0 */ - di->inst_curr = ab8500_fg_inst_curr(di); + di->inst_curr = ab8500_fg_inst_curr_blocking(di); /* Get an initial capacity calculation */ ab8500_fg_calc_cap_discharge_voltage(di, true); ab8500_fg_check_capacity_limits(di, true); @@ -1862,11 +2014,15 @@ static int __devexit ab8500_fg_remove(struct platform_device *pdev) int ret = 0; struct ab8500_fg *di = platform_get_drvdata(pdev); + list_del(&di->node); + /* Disable coulomb counter */ ret = ab8500_fg_coulomb_counter(di, false); if (ret) dev_err(di->dev, "failed to disable coulomb counter\n"); + flush_workqueue(di->inst_curr_wq); + destroy_workqueue(di->inst_curr_wq); destroy_workqueue(di->fg_wq); ab8500_fg_sysfs_exit(di); @@ -1939,6 +2095,19 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev) di->init_capacity = true; + di->inst_curr_mip = false; + spin_lock_init(&di->inst_curr_lock); + init_waitqueue_head(&di->result_wq); + init_waitqueue_head(&di->cpw_a_wq); + init_waitqueue_head(&di->cpw_b_wq); + di->cpw_next_wq = &di->cpw_a_wq; + di->cpw_this_wq = &di->cpw_b_wq; + di->inst_curr_result = 0; + INIT_LIST_HEAD(&di->result_a_list); + INIT_LIST_HEAD(&di->result_b_list); + di->next_result_list = &di->result_a_list; + di->this_result_list = &di->result_b_list; + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); @@ -1949,6 +2118,16 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev) goto free_device_info; } + di->inst_curr_wq = create_singlethread_workqueue("ab8500_inst_curr_wq"); + if (di->inst_curr_wq == NULL) { + dev_err(di->dev, "failed to create work queue\n"); + goto free_fg_wq; + } + + /* Init work for running the instant current measurment */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_inst_curr_work, + ab8500_fg_inst_curr_work); + /* Init work for running the fg algorithm instantly */ INIT_WORK(&di->fg_work, ab8500_fg_instant_work); @@ -1967,7 +2146,7 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev) ret = ab8500_fg_init_hw_registers(di); if (ret) { dev_err(di->dev, "failed to initialize registers\n"); - goto free_fg_wq; + goto free_inst_curr_wq; } /* Consider battery unknown until we're informed otherwise */ @@ -1977,7 +2156,7 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev) ret = power_supply_register(di->dev, &di->fg_psy); if (ret) { dev_err(di->dev, "failed to register FG psy\n"); - goto free_fg_wq; + goto free_inst_curr_wq; } di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer); @@ -2013,6 +2192,8 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev) /* Run the FG algorithm */ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + list_add_tail(&di->node, &ab8500_fg_list); + return ret; free_irq: @@ -2023,6 +2204,8 @@ free_irq: irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); free_irq(irq, di); } +free_inst_curr_wq: + destroy_workqueue(di->inst_curr_wq); free_fg_wq: destroy_workqueue(di->fg_wq); free_device_info: diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h index a830e4d20e4..cbd66dfd9ec 100644 --- a/include/linux/mfd/ab8500/bm.h +++ b/include/linux/mfd/ab8500/bm.h @@ -8,6 +8,8 @@ #ifndef _AB8500_BM_H #define _AB8500_BM_H +#include + /* * System control 2 register offsets. * bank = 0x02 @@ -226,6 +228,9 @@ /* Battery type */ #define BATTERY_UNKNOWN 00 +/* Concurrent instant current i/f */ +#define INVALID_CURRENT INT_MAX + /* * ADC for the battery thermistor. * When using the ADC_THERM_BATCTRL the battery ID resistor is combined with @@ -462,20 +467,42 @@ struct ab8500_fg_platform_data { struct ab8500_chargalg_platform_data { char **supplied_to; size_t num_supplicants; -} - +}; struct ab8500_btemp; +struct ab8500_gpadc; +struct ab8500_fg; #ifdef CONFIG_AB8500_BM +void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); struct ab8500_btemp *ab8500_btemp_get(void); int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp); +struct ab8500_fg *ab8500_fg_get(void); +int ab8500_fg_inst_curr_blocking(struct ab8500_fg *dev); +int ab8500_fg_inst_curr_nonblocking(struct ab8500_fg *dev, int *local_result); #else +static void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) +{ +} static struct ab8500_btemp *ab8500_btemp_get(void) { - return 0; + return NULL; } static int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) { return 0; } +struct ab8500_fg *ab8500_fg_get(void) +{ + return NULL; +} +static int ab8500_fg_inst_curr_blocking(struct ab8500_fg *dev) +{ + return -ENODEV; +} +static int ab8500_fg_inst_curr_nonblocking( + struct ab8500_fg *dev, + int *local_result) +{ + return -ENODEV; +} #endif #endif /* _AB8500_BM_H */ -- cgit v1.2.3 From 38ef1b0703e96a73e913ab7b221fbb7c75fdf802 Mon Sep 17 00:00:00 2001 From: Andrew Lynn Date: Thu, 18 Aug 2011 15:08:21 +0100 Subject: ab8500-bm: Ground lift compensation for Tbat Measure Ibat and remove the resulting voltage between Vbat- and Gnd from Vntc readings. ST-Ericsson ID: 350661 ST-Ericsson FOSS-OUT ID: Trivial ST-Ericsson Linux next: NA Change-Id: I232b9fc7da3ea69e2062feda98776ded33d6253b Signed-off-by: Andrew Lynn Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/27869 Reviewed-by: QATOOLS Reviewed-by: QABUILD Reviewed-by: QATEST Reviewed-by: Johan PALSSON Reviewed-by: Jonas ABERG Reviewed-by: Karl KOMIEROWSKI --- drivers/power/ab8500_btemp.c | 31 ++++++++++++++++++++++++------- include/linux/mfd/ab8500/bm.h | 2 ++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c index 336384c882f..70e3a4688c9 100644 --- a/drivers/power/ab8500_btemp.c +++ b/drivers/power/ab8500_btemp.c @@ -74,6 +74,7 @@ struct ab8500_btemp_ranges { * @prev_bat_temp Last dispatched battery temperature * @parent: Pointer to the struct ab8500 * @gpadc: Pointer to the struct gpadc + * @fg: Pointer to the struct fg * @pdata: Pointer to the ab8500_btemp platform data * @bat: Pointer to the ab8500_bm platform data * @btemp_psy: Structure for BTEMP specific battery properties @@ -91,6 +92,7 @@ struct ab8500_btemp { int prev_bat_temp; struct ab8500 *parent; struct ab8500_gpadc *gpadc; + struct ab8500_fg *fg; struct ab8500_btemp_platform_data *pdata; struct ab8500_bm_data *bat; struct power_supply btemp_psy; @@ -126,13 +128,14 @@ struct ab8500_btemp *ab8500_btemp_get(void) * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance * @di: pointer to the ab8500_btemp structure * @v_batctrl: measured batctrl voltage + * @inst_curr: measured instant current * * This function returns the battery resistance that is * derived from the BATCTRL voltage. * Returns value in Ohms. */ static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, - int v_batctrl) + int v_batctrl, int inst_curr) { int rbs; @@ -151,7 +154,9 @@ static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, * If the battery has internal NTC, we use the current * source to calculate the resistance, 7uA or 20uA */ - rbs = v_batctrl * 1000 / di->curr_source; + rbs = (v_batctrl * 1000 + - di->bat->gnd_lift_resistance * inst_curr) + / di->curr_source; } else { /* * BAT_CTRL is internally @@ -342,8 +347,10 @@ disable_force_comp: static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) { int ret; - int batctrl; + int batctrl = 0; int res; + int inst_curr; + int i = 0; /* * BATCTRL current sources are included on AB8500 cut2.0 @@ -355,8 +362,18 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) return ret; } - batctrl = ab8500_btemp_read_batctrl_voltage(di); - res = ab8500_btemp_batctrl_volt_to_res(di, batctrl); + if (!di->fg) + di->fg = ab8500_fg_get(); + if (!di->fg || ab8500_fg_inst_curr_nonblocking(di->fg, &inst_curr)) + inst_curr = 0; + do { + batctrl += ab8500_btemp_read_batctrl_voltage(di); + i++; + msleep(1); + barrier(); + } while (inst_curr == INVALID_CURRENT); + batctrl /= i; + res = ab8500_btemp_batctrl_volt_to_res(di, batctrl, inst_curr); ret = ab8500_btemp_curr_source_enable(di, false); if (ret) { @@ -364,8 +381,8 @@ static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di) return ret; } - dev_dbg(di->dev, "%s batctrl: %d res: %d ", - __func__, batctrl, res); + dev_dbg(di->dev, "%s batctrl: %d res: %d inst_curr: %d\n", + __func__, batctrl, res, inst_curr); return res; } diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h index cbd66dfd9ec..0bd8703b2bd 100644 --- a/include/linux/mfd/ab8500/bm.h +++ b/include/linux/mfd/ab8500/bm.h @@ -415,6 +415,7 @@ struct ab8500_bm_charger_parameters { * @interval_charging charge alg cycle period time when charging (sec) * @interval_not_charging charge alg cycle period time when not charging (sec) * @temp_hysteresis temperature hysteresis + * @gnd_lift_resistance Battery ground to phone ground resistance (mOhm) * @maxi: maximization parameters * @cap_levels capacity in percent for the different capacity levels * @bat_type table of supported battery types @@ -442,6 +443,7 @@ struct ab8500_bm_data { int interval_charging; int interval_not_charging; int temp_hysteresis; + int gnd_lift_resistance; const struct ab8500_maxim_parameters *maxi; const struct ab8500_bm_capacity_levels *cap_levels; const struct battery_type *bat_type; -- cgit v1.2.3 From 0534baf8ae1484fcdf5646bdd74d499356416ef1 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Fri, 15 Jul 2011 12:57:40 +0530 Subject: power: ab5500-charger: update the watchdog rekick seq In AB5500 there exist no charger watchdog, instead all the charging registers that are written during enabling charging are to be re-written which acts as a watchdog re-kick. Even though its usb charging DCIOCURRENT register has to be written in order to kick charging, else charging is not started. ST-Ericsson Linux next: Not Tested ST-Ericsson ID: WP257120 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Iad283349e21027908df8aec560c9f44f2861f0ee Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/27220 Reviewed-by: Johan PALSSON Reviewed-by: Srinidhi KASAGAR --- drivers/power/ab5500_charger.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/power/ab5500_charger.c b/drivers/power/ab5500_charger.c index 69b808f06d7..b30f00a57f9 100644 --- a/drivers/power/ab5500_charger.c +++ b/drivers/power/ab5500_charger.c @@ -825,6 +825,21 @@ static int ab5500_charger_usb_en(struct ux500_charger *charger, __func__, __LINE__); return ret; } + + /* + * Register DCIOCURRENT is one among the charging watchdog + * rekick sequence, hence irrespective of usb charging this + * register will have to be written. + */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_DCIOCURRENT, + RESET); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", + __func__, __LINE__); + return ret; + } + di->usb.charger_online = 1; } else { /* ChVoltLevel: max voltage upto which battery can be charged */ @@ -946,6 +961,19 @@ static int ab5500_charger_watchdog_kick(struct ux500_charger *charger) dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); return ret; } + /* + * Register DCIOCURRENT is one among the charging watchdog + * rekick sequence, hence irrespective of usb charging this + * register will have to be written. + */ + ret = abx500_set_register_interruptible(di->dev, + AB5500_BANK_CHG, AB5500_DCIOCURRENT, + RESET); + if (ret) { + dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__); + return ret; + } + return ret; } -- cgit v1.2.3 From ca5e903a2478a0e53395c1ea17427b0582ab7ca8 Mon Sep 17 00:00:00 2001 From: Andrew Lynn Date: Tue, 5 Jul 2011 15:39:08 +0100 Subject: u8500: rtc: Set can_wake flag Set can_wake flag so wakealarm property is visible in sysfs. ST-Ericsson ID: 338529 ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ief6dfc50bce6334cd2de95a20833dea7c8ed4eba Signed-off-by: Andrew Lynn Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/26588 Reviewed-by: QATEST Reviewed-by: Jonas ABERG --- drivers/rtc/rtc-ab8500.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/rtc/rtc-ab8500.c b/drivers/rtc/rtc-ab8500.c index 89e282292d3..8e017f2cf79 100644 --- a/drivers/rtc/rtc-ab8500.c +++ b/drivers/rtc/rtc-ab8500.c @@ -319,6 +319,8 @@ static int __devinit ab8500_rtc_probe(struct platform_device *pdev) return -ENODEV; } + device_init_wakeup(&pdev->dev, true); + rtc = rtc_device_register("ab8500-rtc", &pdev->dev, &ab8500_rtc_ops, THIS_MODULE); if (IS_ERR(rtc)) { -- cgit v1.2.3 From 710433acef73cc1fbed7139f6d83704b959561d1 Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Thu, 23 Jun 2011 17:36:41 +0530 Subject: ab5500: identify AB5500v2 ST-Ericsson Linux next: - ST-Ericsson ID: 349062 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I0df95338d14b45167c5be811d8b74d6d48bd78e2 Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/25781 Reviewed-by: Bibek BASU Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/mfd/ab5500-core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 9c47f075962..c8b4952974f 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -2613,6 +2613,10 @@ static const struct ab_family_id ids[] __initdata = { .id = AB5500_1_1, .name = "1.1" }, + { + .id = AB5500_2_0, + .name = "2.0" + }, /* Terminator */ { .id = 0x00, -- cgit v1.2.3 From 5f980130f69a88264e9e1ce3e42cbc598d04e8f7 Mon Sep 17 00:00:00 2001 From: Bibek Basu Date: Thu, 25 Aug 2011 15:52:41 +0530 Subject: u5500 : Improper handling of device name in ab5500 core Following changes done: 1> Remove the protection on Read/Write access for any ab5500 register by any driver 2> Correct the pdev id for the mfd devices being registered by ab5500 core driver 3> Correct device name in sound driver ST-Ericsson Linux next: 336280 ST-Ericsson ID: 349133 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I659621b0d5453652a9368580631d85c7c852026d Signed-off-by: Bibek Basu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/25871 Reviewed-by: QATEST Tested-by: Amaresh MULAGE Reviewed-by: Srinidhi KASAGAR --- drivers/mfd/ab5500-core.c | 730 ++-------------------------------------------- 1 file changed, 23 insertions(+), 707 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index c8b4952974f..2ac4d9b0aea 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -82,601 +82,6 @@ static const struct ab5500_bank bankinfo[AB5500_NUM_BANKS] = { AB5500_ADDR_AUDIO_HEADSETUSB, "AUDIO_HEADSETUSB"}, }; - -/** - * struct ab5500_reg_range - * @first: the first address of the range - * @last: the last address of the range - * @perm: access permissions for the range - */ -struct ab5500_reg_range { - u8 first; - u8 last; - u8 perm; -}; - -/** - * struct ab5500_i2c_ranges - * @count: the number of ranges in the list - * @range: the list of register ranges - */ -struct ab5500_i2c_ranges { - u8 nranges; - u8 bankid; - const struct ab5500_reg_range *range; -}; - -/** - * struct ab5500_i2c_banks - * @count: the number of ranges in the list - * @range: the list of register ranges - */ -struct ab5500_i2c_banks { - u8 nbanks; - const struct ab5500_i2c_ranges *bank; -}; - -/* - * Permissible register ranges for reading and writing per device and bank. - * - * The ranges must be listed in increasing address order, and no overlaps are - * allowed. It is assumed that write permission implies read permission - * (i.e. only RO and RW permissions should be used). Ranges with write - * permission must not be split up. - */ - -#define NO_RANGE {.count = 0, .range = NULL,} -static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { - [AB5500_DEVID_USB] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_USB, - .nranges = 12, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x01, - .last = 0x01, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x80, - .last = 0x83, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x87, - .last = 0x8A, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x8B, - .last = 0x8B, - .perm = AB5500_PERM_RO, - }, - { - .first = 0x91, - .last = 0x92, - .perm = AB5500_PERM_RO, - }, - { - .first = 0x93, - .last = 0x93, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x94, - .last = 0x94, - .perm = AB5500_PERM_RO, - }, - { - .first = 0xA8, - .last = 0xB0, - .perm = AB5500_PERM_RO, - }, - { - .first = 0xB2, - .last = 0xB2, - .perm = AB5500_PERM_RO, - }, - { - .first = 0xB4, - .last = 0xBC, - .perm = AB5500_PERM_RO, - }, - { - .first = 0xBF, - .last = 0xBF, - .perm = AB5500_PERM_RO, - }, - { - .first = 0xC1, - .last = 0xC5, - .perm = AB5500_PERM_RO, - }, - }, - }, - }, - }, - [AB5500_DEVID_ADC] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_ADC, - .nranges = 6, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x1F, - .last = 0x22, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x23, - .last = 0x24, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x26, - .last = 0x2D, - .perm = AB5500_PERM_RO, - }, - { - .first = 0x2F, - .last = 0x34, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x37, - .last = 0x57, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x58, - .last = 0x58, - .perm = AB5500_PERM_RO, - }, - }, - }, - }, - }, - [AB5500_DEVID_LEDS] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_LED, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x0C, - .perm = AB5500_PERM_RW, - }, - }, - }, - }, - }, - [AB5500_DEVID_VIDEO] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_VDENC, - .nranges = 12, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x08, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x09, - .last = 0x09, - .perm = AB5500_PERM_RO, - }, - { - .first = 0x0A, - .last = 0x12, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x15, - .last = 0x19, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x1B, - .last = 0x21, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x27, - .last = 0x2C, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x41, - .last = 0x41, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x45, - .last = 0x5B, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x5D, - .last = 0x5D, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x69, - .last = 0x69, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x6C, - .last = 0x6D, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x80, - .last = 0x81, - .perm = AB5500_PERM_RW, - }, - }, - }, - }, - }, - [AB5500_DEVID_REGULATORS] = { - .nbanks = 2, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_STARTUP, - .nranges = 12, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x01, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x1F, - .last = 0x1F, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x2E, - .last = 0x2E, - .perm = AB5500_PERM_RO, - }, - { - .first = 0x2F, - .last = 0x30, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x50, - .last = 0x51, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x60, - .last = 0x61, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x66, - .last = 0x8A, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x8C, - .last = 0x96, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xAA, - .last = 0xB4, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xB7, - .last = 0xBF, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xC1, - .last = 0xCA, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xD3, - .last = 0xE0, - .perm = AB5500_PERM_RW, - }, - }, - }, - { - .bankid = AB5500_BANK_SIM_USBSIM, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x13, - .last = 0x19, - .perm = AB5500_PERM_RW, - }, - }, - }, - }, - }, - [AB5500_DEVID_SIM] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_SIM_USBSIM, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x13, - .last = 0x19, - .perm = AB5500_PERM_RW, - }, - }, - }, - }, - }, - [AB5500_DEVID_RTC] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_RTC, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x04, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x06, - .last = 0x0C, - .perm = AB5500_PERM_RW, - }, - }, - }, - }, - }, - [AB5500_DEVID_CHARGER] = { - .nbanks = 4, - .bank = (struct ab5500_i2c_ranges[]) { - { - .bankid = AB5500_BANK_CHG, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x11, - .last = 0x12, - .perm = AB5500_PERM_RO, - }, - { - .first = 0x12, - .last = 0x1B, - .perm = AB5500_PERM_RW, - }, - }, - }, - { - .bankid = AB5500_BANK_USB, - .nranges = 13, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x01, - .last = 0x01, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x80, - .last = 0x83, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x87, - .last = 0x8B, - .perm = AB5500_PERM_RW, - }, - { - .first = 0x91, - .last = 0x94, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xA8, - .last = 0xB0, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xB2, - .last = 0xB2, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xB4, - .last = 0xBC, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xBF, - .last = 0xBF, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xC1, - .last = 0xC6, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xCD, - .last = 0xCD, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xD6, - .last = 0xDA, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xDC, - .last = 0xDC, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xE0, - .last = 0xE4, - .perm = AB5500_PERM_RW, - }, - }, - }, - { - .bankid = AB5500_BANK_IT, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x02, - .perm = AB5500_PERM_RO, - }, - { - .first = 0x20, - .last = 0x36, - .perm = AB5500_PERM_RO, - }, - }, - }, - { - .bankid = AB5500_BANK_STARTUP, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x2F, - .last = 0x30, - .perm = AB5500_PERM_RW, - }, - }, - }, - }, - }, - [AB5500_DEVID_BTEMP] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_FG_BATTCOM_ACC, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x0B, - .perm = AB5500_PERM_RO, - }, - { - .first = 0x0C, - .last = 0x24, - .perm = AB5500_PERM_RW, - }, - }, - }, - }, - }, - [AB5500_DEVID_FG] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_FG_BATTCOM_ACC, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x0B, - .perm = AB5500_PERM_RO, - }, - { - .first = 0x0C, - .last = 0x24, - .perm = AB5500_PERM_RW, - }, - }, - }, - }, - }, - [AB5500_DEVID_VIBRATOR] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_VIBRA, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x10, - .last = 0x13, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xFE, - .last = 0xFE, - .perm = AB5500_PERM_RW, - }, - }, - }, - }, - }, - [AB5500_DEVID_CODEC] = { - .nbanks = 1, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_AUDIO_HEADSETUSB, - .nranges = 2, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x00, - .last = 0x48, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xEB, - .last = 0xFB, - .perm = AB5500_PERM_RW, - }, - }, - }, - }, - }, - [AB5500_DEVID_POWER] = { - .nbanks = 2, - .bank = (struct ab5500_i2c_ranges []) { - { - .bankid = AB5500_BANK_STARTUP, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x30, - .last = 0x30, - .perm = AB5500_PERM_RW, - }, - }, - }, - { - .bankid = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, - .nranges = 1, - .range = (struct ab5500_reg_range[]) { - { - .first = 0x01, - .last = 0x01, - .perm = AB5500_PERM_RW, - }, - }, - }, - }, - }, -}; - #define AB5500_IRQ(bank, bit) ((bank) * 8 + (bit)) /* I appologize for the resource names beeing a mix of upper case @@ -684,19 +89,15 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { [AB5500_DEVID_LEDS] = { .name = "ab5500-leds", - .id = AB5500_DEVID_LEDS, }, [AB5500_DEVID_POWER] = { .name = "ab5500-power", - .id = AB5500_DEVID_POWER, }, [AB5500_DEVID_REGULATORS] = { .name = "ab5500-regulator", - .id = AB5500_DEVID_REGULATORS, }, [AB5500_DEVID_SIM] = { .name = "ab5500-sim", - .id = AB5500_DEVID_SIM, .num_resources = 1, .resources = (struct resource[]) { { @@ -709,7 +110,6 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, [AB5500_DEVID_RTC] = { .name = "ab5500-rtc", - .id = AB5500_DEVID_RTC, .num_resources = 1, .resources = (struct resource[]) { { @@ -722,7 +122,6 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, [AB5500_DEVID_CHARGER] = { .name = "ab5500-charger", - .id = AB5500_DEVID_CHARGER, .num_resources = 29, .resources = (struct resource[]) { { @@ -903,11 +302,9 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, [AB5500_DEVID_CHARGALG] = { .name = "abx500-chargalg", - .id = AB5500_DEVID_CHARGALG, }, [AB5500_DEVID_BTEMP] = { .name = "ab5500-btemp", - .id = AB5500_DEVID_BTEMP, .num_resources = 2, .resources = (struct resource[]) { { @@ -926,7 +323,6 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, [AB5500_DEVID_ADC] = { .name = "ab5500-adc", - .id = AB5500_DEVID_ADC, .num_resources = 10, .resources = (struct resource[]) { { @@ -993,7 +389,6 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, [AB5500_DEVID_FG] = { .name = "ab5500-fg", - .id = AB5500_DEVID_FG, .num_resources = 6, .resources = (struct resource[]) { { @@ -1036,11 +431,9 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, [AB5500_DEVID_VIBRATOR] = { .name = "ab5500-vibrator", - .id = AB5500_DEVID_VIBRATOR, }, [AB5500_DEVID_CODEC] = { .name = "ab5500-codec", - .id = AB5500_DEVID_CODEC, .num_resources = 3, .resources = (struct resource[]) { { @@ -1065,7 +458,6 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, [AB5500_DEVID_USB] = { .name = "ab5500-usb", - .id = AB5500_DEVID_USB, .num_resources = 36, .resources = (struct resource[]) { { @@ -1288,11 +680,9 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, [AB5500_DEVID_OTP] = { .name = "ab5500-otp", - .id = AB5500_DEVID_OTP, }, [AB5500_DEVID_VIDEO] = { .name = "ab5500-video", - .id = AB5500_DEVID_VIDEO, .num_resources = 1, .resources = (struct resource[]) { { @@ -1305,7 +695,6 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, [AB5500_DEVID_DBIECI] = { .name = "ab5500-dbieci", - .id = AB5500_DEVID_DBIECI, .num_resources = 10, .resources = (struct resource[]) { { @@ -1372,7 +761,6 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, [AB5500_DEVID_ONSWA] = { .name = "ab5500-onswa", - .id = AB5500_DEVID_ONSWA, .num_resources = 2, .resources = (struct resource[]) { { @@ -1495,85 +883,6 @@ set_register_interruptible(struct ab5500 *ab, u8 bank, u8 reg, u8 value) return mask_and_set_register_interruptible(ab, bank, reg, 0xff, value); } -/* - * Read/write permission checking functions. - */ -static const struct ab5500_i2c_ranges *get_bankref(u8 devid, u8 bank) -{ - u8 i; - - if (devid < AB5500_NUM_DEVICES) { - for (i = 0; i < ab5500_bank_ranges[devid].nbanks; i++) { - if (ab5500_bank_ranges[devid].bank[i].bankid == bank) - return &ab5500_bank_ranges[devid].bank[i]; - } - } - return NULL; -} - -static bool page_write_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg) -{ - u8 i; /* range loop index */ - const struct ab5500_i2c_ranges *bankref; - - bankref = get_bankref(devid, bank); - if (bankref == NULL || last_reg < first_reg) - return false; - - for (i = 0; i < bankref->nranges; i++) { - if (first_reg < bankref->range[i].first) - break; - if ((last_reg <= bankref->range[i].last) && - (bankref->range[i].perm & AB5500_PERM_WR)) - return true; - } - return false; -} - -static bool reg_write_allowed(u8 devid, u8 bank, u8 reg) -{ - return page_write_allowed(devid, bank, reg, reg); -} - -static bool page_read_allowed(u8 devid, u8 bank, u8 first_reg, u8 last_reg) -{ - u8 i; - const struct ab5500_i2c_ranges *bankref; - - bankref = get_bankref(devid, bank); - if (bankref == NULL || last_reg < first_reg) - return false; - - - /* Find the range (if it exists in the list) that includes first_reg. */ - for (i = 0; i < bankref->nranges; i++) { - if (first_reg < bankref->range[i].first) - return false; - if (first_reg <= bankref->range[i].last) - break; - } - /* Make sure that the entire range up to and including last_reg is - * readable. This may span several of the ranges in the list. - */ - while ((i < bankref->nranges) && - (bankref->range[i].perm & AB5500_PERM_RD)) { - if (last_reg <= bankref->range[i].last) - return true; - if ((++i >= bankref->nranges) || - (bankref->range[i].first != - (bankref->range[i - 1].last + 1))) { - break; - } - } - return false; -} - -static bool reg_read_allowed(u8 devid, u8 bank, u8 reg) -{ - return page_read_allowed(devid, bank, reg, reg); -} - - /* * The exported register access functionality. */ @@ -1588,11 +897,6 @@ static int ab5500_mask_and_set_register_interruptible(struct device *dev, u8 bank, u8 reg, u8 bitmask, u8 bitvalues) { struct ab5500 *ab; - struct platform_device *pdev = to_platform_device(dev); - - if ((AB5500_NUM_BANKS <= bank) || - !reg_write_allowed(pdev->id, bank, reg)) - return -EINVAL; ab = dev_get_drvdata(dev->parent); return mask_and_set_register_interruptible(ab, bank, reg, @@ -1610,11 +914,6 @@ static int ab5500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, u8 *value) { struct ab5500 *ab; - struct platform_device *pdev = to_platform_device(dev); - - if ((AB5500_NUM_BANKS <= bank) || - !reg_read_allowed(pdev->id, bank, reg)) - return -EINVAL; ab = dev_get_drvdata(dev->parent); return get_register_interruptible(ab, bank, reg, value); @@ -1624,12 +923,6 @@ static int ab5500_get_register_page_interruptible(struct device *dev, u8 bank, u8 first_reg, u8 *regvals, u8 numregs) { struct ab5500 *ab; - struct platform_device *pdev = to_platform_device(dev); - - if ((AB5500_NUM_BANKS <= bank) || - !page_read_allowed(pdev->id, bank, - first_reg, (first_reg + numregs - 1))) - return -EINVAL; ab = dev_get_drvdata(dev->parent); return get_register_page_interruptible(ab, bank, first_reg, regvals, @@ -1708,6 +1001,29 @@ error_irq: } #ifdef CONFIG_DEBUG_FS +/** + * struct ab5500_reg_range + * @first: the first address of the range + * @last: the last address of the range + * @perm: access permissions for the range + */ +struct ab5500_reg_range { + u8 first; + u8 last; + u8 perm; +}; + +/** + * struct ab5500_i2c_ranges + * @count: the number of ranges in the list + * @range: the list of register ranges + */ +struct ab5500_i2c_ranges { + u8 nranges; + u8 bankid; + const struct ab5500_reg_range *range; +}; + static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { [AB5500_BANK_LED] = { .bankid = AB5500_BANK_LED, -- cgit v1.2.3 From aa603c09e469ade3b2279343b8208ea15f5e2273 Mon Sep 17 00:00:00 2001 From: Bibek Basu Date: Thu, 25 Aug 2011 15:58:35 +0530 Subject: U5500 : coverity fix in ab5500 core and mailbox Following fix in mailbox logical driver and ab5500 core driver Used after free Unused value Bad Free ST-Ericsson Linux next: 336280 ST-Ericsson ID: 348573 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I57e11eebc94d97a73612b4cef5e403b8e72f38e8 Signed-off-by: Bibek Basu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/25866 Reviewed-by: Srinidhi KASAGAR --------- Partially applied, coverity fixes in mbox seems to be already present. Signed-off-by: Arun Murthy --- drivers/mfd/ab5500-core.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 2ac4d9b0aea..cf0e14e4c84 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -2107,7 +2107,6 @@ exit_no_detect: static int __exit ab5500_remove(struct platform_device *pdev) { struct ab5500 *ab = platform_get_drvdata(pdev); - struct resource *res; /* * At this point, all subscribers should have unregistered @@ -2119,7 +2118,6 @@ static int __exit ab5500_remove(struct platform_device *pdev) free_irq(ab->ab5500_irq, ab); ab5500_irq_remove(ab); } - res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); kfree(ab); return 0; } -- cgit v1.2.3 From 9e882ad487db52010146885cfc5b95c0894a6cd4 Mon Sep 17 00:00:00 2001 From: Bibek Basu Date: Mon, 27 Jun 2011 10:59:13 +0530 Subject: U5500 : Debugfs register print fails sometimes Number of registers in Fuel Guage and Battery Communication Bank 0x0C is not correct.Also update on register banks based on latest datasheet ST-Ericsson Linux next: 336280 ST-Ericsson ID: 342280 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ic57123a59851315cb7ab29943116103edd42535c Signed-off-by: Bibek Basu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/25870 Reviewed-by: Srinidhi KASAGAR --- drivers/mfd/ab5500-core.c | 77 +++++++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index cf0e14e4c84..7936a85e827 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -1042,11 +1042,11 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { .range = (struct ab5500_reg_range[]) { { .first = 0x1F, - .last = 0x22, - .perm = AB5500_PERM_RW, + .last = 0x21, + .perm = AB5500_PERM_RO, }, { - .first = 0x23, + .first = 0x22, .last = 0x24, .perm = AB5500_PERM_RW, }, @@ -1193,7 +1193,7 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { }, [AB5500_BANK_FG_BATTCOM_ACC] = { .bankid = AB5500_BANK_FG_BATTCOM_ACC, - .nranges = 2, + .nranges = 5, .range = (struct ab5500_reg_range[]) { { .first = 0x00, @@ -1202,14 +1202,30 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { }, { .first = 0x0C, + .last = 0x10, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x1A, + .last = 0x1D, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x20, + .last = 0x21, + .perm = AB5500_PERM_RW, + }, + { + .first = 0x23, .last = 0x24, .perm = AB5500_PERM_RW, }, + }, }, [AB5500_BANK_USB] = { .bankid = AB5500_BANK_USB, - .nranges = 12, + .nranges = 13, .range = (struct ab5500_reg_range[]) { { .first = 0x01, @@ -1218,6 +1234,11 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { }, { .first = 0x80, + .last = 0x80, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x81, .last = 0x83, .perm = AB5500_PERM_RW, }, @@ -1342,7 +1363,7 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { }, [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { .bankid = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, - .nranges = 13, + .nranges = 14, .range = (struct ab5500_reg_range[]) { { .first = 0x01, @@ -1356,7 +1377,12 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { }, { .first = 0x0D, - .last = 0x0F, + .last = 0x0D, + .perm = AB5500_PERM_RO, + }, + { + .first = 0x0E, + .last = 0x0E, .perm = AB5500_PERM_RW, }, { @@ -1374,14 +1400,9 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { .last = 0x21, .perm = AB5500_PERM_RW, }, - { - .first = 0x25, - .last = 0x25, - .perm = AB5500_PERM_RW, - }, { .first = 0x28, - .last = 0x2A, + .last = 0x28, .perm = AB5500_PERM_RW, }, { @@ -1409,49 +1430,49 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { .last = 0x73, .perm = AB5500_PERM_RW, }, + { + .first = 0xB1, + .last = 0xB1, + .perm = AB5500_PERM_RW, + }, }, }, [AB5500_BANK_VIBRA] = { .bankid = AB5500_BANK_VIBRA, - .nranges = 2, + .nranges = 1, .range = (struct ab5500_reg_range[]) { { .first = 0x10, .last = 0x13, .perm = AB5500_PERM_RW, }, - { - .first = 0xFE, - .last = 0xFE, - .perm = AB5500_PERM_RW, - }, }, }, [AB5500_BANK_AUDIO_HEADSETUSB] = { .bankid = AB5500_BANK_AUDIO_HEADSETUSB, - .nranges = 2, + .nranges = 1, .range = (struct ab5500_reg_range[]) { { .first = 0x00, - .last = 0x48, - .perm = AB5500_PERM_RW, - }, - { - .first = 0xEB, - .last = 0xFB, + .last = 0x47, .perm = AB5500_PERM_RW, }, }, }, [AB5500_BANK_SIM_USBSIM] = { .bankid = AB5500_BANK_SIM_USBSIM, - .nranges = 1, + .nranges = 2, .range = (struct ab5500_reg_range[]) { { .first = 0x13, .last = 0x19, .perm = AB5500_PERM_RW, }, + { + .first = 0x20, + .last = 0x20, + .perm = AB5500_PERM_RW, + }, }, }, [AB5500_BANK_VDENC] = { @@ -1553,8 +1574,6 @@ static int ab5500_registers_print(struct seq_file *s, void *p) err = seq_printf(s, "[%d/0x%02X]: 0x%02X\n", bank, reg, value); if (err < 0) { - dev_err(ab->dev, - "seq_printf overflow\n"); /* * Error is not returned here since * the output is wanted in any case -- cgit v1.2.3 From 81142e40b1f165c32e46db61ce24af0c32328c65 Mon Sep 17 00:00:00 2001 From: Bibek Basu Date: Thu, 25 Aug 2011 16:04:14 +0530 Subject: u5500 : ab5500 core driver update for cut 2.0 Following changes made: 1. support for new interrupts. 2. debugfs support for new set of registers in each banks ST-Ericsson Linux next: 336280 ST-Ericsson ID: 257120 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I6630d3d8d660959e2d7946fc6d7b31506507a222 Signed-off-by: Bibek Basu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/27213 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR Signed-off-by: Robert Marklund --- drivers/mfd/ab5500-core.c | 575 ++++++++++++++++++++++++++++++++++++--------- include/linux/mfd/abx500.h | 2 + 2 files changed, 467 insertions(+), 110 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 7936a85e827..cf7de4d7443 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -33,7 +33,7 @@ #define AB5500_NAME_STRING "ab5500" #define AB5500_ID_FORMAT_STRING "AB5500 %s" -#define AB5500_NUM_EVENT_REG 23 +#define AB5500_NUM_EVENT_V1_REG 23 #define AB5500_IT_LATCH0_REG 0x40 #define AB5500_IT_MASK0_REG 0x60 /* These are the only registers inside AB5500 used in this main file */ @@ -49,7 +49,8 @@ #define AB5500_MASK_BASE (0x60) #define AB5500_MASK_END (0x79) #define AB5500_CHIP_ID (0x20) -#define AB5500_INTERRUPTS 0x007FFFFF +#define AB5500_INTERRUPTS 0x01FFFFFF + /** * struct ab5500_bank * @slave_addr: I2C slave_addr found in AB5500 specification @@ -214,12 +215,6 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { .start = AB5500_IRQ(14, 5), .end = AB5500_IRQ(14, 5), }, - { - .name = "BSI_INDICATOR", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(14, 4), - .end = AB5500_IRQ(14, 4), - }, { .name = "USB_CH_TH_PROTECTION", .flags = IORESOURCE_IRQ, @@ -376,14 +371,14 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { { .name = "TRIGGER-VBAT", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 8), - .end = AB5500_IRQ(0, 8), + .start = AB5500_IRQ(1, 0), + .end = AB5500_IRQ(1, 0), }, { .name = "TRIGGER-VBAT-TXON", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(0, 9), - .end = AB5500_IRQ(0, 9), + .start = AB5500_IRQ(1, 1), + .end = AB5500_IRQ(1, 1), }, }, }, @@ -523,8 +518,8 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { { .name = "Vbus_Imeasmax_change", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(9, 5), - .end = AB5500_IRQ(9, 6), + .start = AB5500_IRQ(9, 7), + .end = AB5500_IRQ(9, 7), }, { .name = "OVV", @@ -638,7 +633,7 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { .name = "CHGstate_11_SafeModeVBUS", .flags = IORESOURCE_IRQ, .start = AB5500_IRQ(21, 1), - .end = AB5500_IRQ(21, 2), + .end = AB5500_IRQ(21, 1), }, { .name = "CHGstate_12_comletedVBUS", @@ -683,14 +678,21 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, [AB5500_DEVID_VIDEO] = { .name = "ab5500-video", - .num_resources = 1, + .num_resources = 2, .resources = (struct resource[]) { { .name = "plugTVdet", .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(22, 2), - .end = AB5500_IRQ(22, 2), + .start = AB5500_IRQ(13, 7), + .end = AB5500_IRQ(13, 7), + }, + { + .name = "plugTVdet_removal", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(23, 2), + .end = AB5500_IRQ(23, 2), }, + }, }, [AB5500_DEVID_DBIECI] = { @@ -938,7 +940,7 @@ ab5500_event_registers_startup_state_get(struct device *dev, u8 *event) if (!ab->startup_events_read) return -EAGAIN; /* Try again later */ - memcpy(event, ab->startup_events, AB5500_NUM_EVENT_REG); + memcpy(event, ab->startup_events, ab->num_event_reg); return 0; } @@ -978,7 +980,7 @@ static irqreturn_t ab5500_irq(int irq, void *data) dev_err(ab->dev, "PRCMU not enabled!!!\n"); goto error_irq; } - for (i = 0; i < AB5500_NUM_EVENT_REG; i++) { + for (i = 0; i < ab->num_event_reg; i++) { value = readb(pvalue); if (value == 0) { pvalue++; @@ -1005,12 +1007,10 @@ error_irq: * struct ab5500_reg_range * @first: the first address of the range * @last: the last address of the range - * @perm: access permissions for the range */ struct ab5500_reg_range { u8 first; u8 last; - u8 perm; }; /** @@ -1024,7 +1024,7 @@ struct ab5500_i2c_ranges { const struct ab5500_reg_range *range; }; -static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { +static struct ab5500_i2c_ranges ab5500v1_reg_ranges[AB5500_NUM_BANKS] = { [AB5500_BANK_LED] = { .bankid = AB5500_BANK_LED, .nranges = 1, @@ -1032,7 +1032,6 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x00, .last = 0x0C, - .perm = AB5500_PERM_RW, }, }, }, @@ -1043,32 +1042,26 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x1F, .last = 0x21, - .perm = AB5500_PERM_RO, }, { .first = 0x22, .last = 0x24, - .perm = AB5500_PERM_RW, }, { .first = 0x26, .last = 0x2D, - .perm = AB5500_PERM_RO, }, { .first = 0x2F, .last = 0x34, - .perm = AB5500_PERM_RW, }, { .first = 0x37, .last = 0x57, - .perm = AB5500_PERM_RW, }, { .first = 0x58, .last = 0x58, - .perm = AB5500_PERM_RO, }, }, }, @@ -1079,12 +1072,10 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x00, .last = 0x04, - .perm = AB5500_PERM_RW, }, { .first = 0x06, .last = 0x0C, - .perm = AB5500_PERM_RW, }, }, }, @@ -1095,62 +1086,50 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x00, .last = 0x01, - .perm = AB5500_PERM_RW, }, { .first = 0x1F, .last = 0x1F, - .perm = AB5500_PERM_RW, }, { .first = 0x2E, .last = 0x2E, - .perm = AB5500_PERM_RO, }, { .first = 0x2F, .last = 0x30, - .perm = AB5500_PERM_RW, }, { .first = 0x50, .last = 0x51, - .perm = AB5500_PERM_RW, }, { .first = 0x60, .last = 0x61, - .perm = AB5500_PERM_RW, }, { .first = 0x66, .last = 0x8A, - .perm = AB5500_PERM_RW, }, { .first = 0x8C, .last = 0x96, - .perm = AB5500_PERM_RW, }, { .first = 0xAA, .last = 0xB4, - .perm = AB5500_PERM_RW, }, { .first = 0xB7, .last = 0xBF, - .perm = AB5500_PERM_RW, }, { .first = 0xC1, .last = 0xCA, - .perm = AB5500_PERM_RW, }, { .first = 0xD3, .last = 0xE0, - .perm = AB5500_PERM_RW, }, }, }, @@ -1161,17 +1140,14 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x00, .last = 0x07, - .perm = AB5500_PERM_RW, }, { .first = 0x10, .last = 0x10, - .perm = AB5500_PERM_RW, }, { .first = 0x13, .last = 0x13, - .perm = AB5500_PERM_RW, }, }, }, @@ -1182,12 +1158,10 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x11, .last = 0x11, - .perm = AB5500_PERM_RO, }, { .first = 0x12, .last = 0x1B, - .perm = AB5500_PERM_RW, }, }, }, @@ -1198,27 +1172,22 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x00, .last = 0x0B, - .perm = AB5500_PERM_RO, }, { .first = 0x0C, .last = 0x10, - .perm = AB5500_PERM_RW, }, { .first = 0x1A, .last = 0x1D, - .perm = AB5500_PERM_RW, }, { .first = 0x20, .last = 0x21, - .perm = AB5500_PERM_RW, }, { .first = 0x23, .last = 0x24, - .perm = AB5500_PERM_RW, }, }, @@ -1230,67 +1199,54 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x01, .last = 0x01, - .perm = AB5500_PERM_RW, }, { .first = 0x80, .last = 0x80, - .perm = AB5500_PERM_RO, }, { .first = 0x81, .last = 0x83, - .perm = AB5500_PERM_RW, }, { .first = 0x87, .last = 0x8A, - .perm = AB5500_PERM_RW, }, { .first = 0x8B, .last = 0x8B, - .perm = AB5500_PERM_RO, }, { .first = 0x91, .last = 0x92, - .perm = AB5500_PERM_RO, }, { .first = 0x93, .last = 0x93, - .perm = AB5500_PERM_RW, }, { .first = 0x94, .last = 0x94, - .perm = AB5500_PERM_RO, }, { .first = 0xA8, .last = 0xB0, - .perm = AB5500_PERM_RO, }, { .first = 0xB2, .last = 0xB2, - .perm = AB5500_PERM_RO, }, { .first = 0xB4, .last = 0xBC, - .perm = AB5500_PERM_RO, }, { .first = 0xBF, .last = 0xBF, - .perm = AB5500_PERM_RO, }, { .first = 0xC1, .last = 0xC5, - .perm = AB5500_PERM_RO, }, }, }, @@ -1301,22 +1257,18 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x00, .last = 0x02, - .perm = AB5500_PERM_RO, }, { .first = 0x20, .last = 0x36, - .perm = AB5500_PERM_RO, }, { .first = 0x40, .last = 0x56, - .perm = AB5500_PERM_RO, }, { .first = 0x60, .last = 0x76, - .perm = AB5500_PERM_RO, }, }, }, @@ -1327,37 +1279,30 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x02, .last = 0x02, - .perm = AB5500_PERM_RW, }, { .first = 0x12, .last = 0x12, - .perm = AB5500_PERM_RW, }, { .first = 0x30, .last = 0x34, - .perm = AB5500_PERM_RW, }, { .first = 0x40, .last = 0x44, - .perm = AB5500_PERM_RW, }, { .first = 0x50, .last = 0x54, - .perm = AB5500_PERM_RW, }, { .first = 0x60, .last = 0x64, - .perm = AB5500_PERM_RW, }, { .first = 0x70, .last = 0x74, - .perm = AB5500_PERM_RW, }, }, }, @@ -1368,72 +1313,58 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x01, .last = 0x01, - .perm = AB5500_PERM_RW, }, { .first = 0x02, .last = 0x02, - .perm = AB5500_PERM_RO, }, { .first = 0x0D, .last = 0x0D, - .perm = AB5500_PERM_RO, }, { .first = 0x0E, .last = 0x0E, - .perm = AB5500_PERM_RW, }, { .first = 0x1C, .last = 0x1C, - .perm = AB5500_PERM_RW, }, { .first = 0x1E, .last = 0x1E, - .perm = AB5500_PERM_RW, }, { .first = 0x20, .last = 0x21, - .perm = AB5500_PERM_RW, }, { .first = 0x28, .last = 0x28, - .perm = AB5500_PERM_RW, }, { .first = 0x30, .last = 0x33, - .perm = AB5500_PERM_RW, }, { .first = 0x40, .last = 0x43, - .perm = AB5500_PERM_RW, }, { .first = 0x50, .last = 0x53, - .perm = AB5500_PERM_RW, }, { .first = 0x60, .last = 0x63, - .perm = AB5500_PERM_RW, }, { .first = 0x70, .last = 0x73, - .perm = AB5500_PERM_RW, }, { .first = 0xB1, .last = 0xB1, - .perm = AB5500_PERM_RW, }, }, }, @@ -1444,7 +1375,6 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x10, .last = 0x13, - .perm = AB5500_PERM_RW, }, }, }, @@ -1455,7 +1385,6 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x00, .last = 0x47, - .perm = AB5500_PERM_RW, }, }, }, @@ -1466,12 +1395,10 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x13, .last = 0x19, - .perm = AB5500_PERM_RW, }, { .first = 0x20, .last = 0x20, - .perm = AB5500_PERM_RW, }, }, }, @@ -1482,62 +1409,481 @@ static struct ab5500_i2c_ranges ab5500_reg_ranges[AB5500_NUM_BANKS] = { { .first = 0x00, .last = 0x08, - .perm = AB5500_PERM_RW, }, { .first = 0x09, .last = 0x09, - .perm = AB5500_PERM_RO, }, { .first = 0x0A, .last = 0x12, - .perm = AB5500_PERM_RW, }, { .first = 0x15, .last = 0x19, - .perm = AB5500_PERM_RW, }, { .first = 0x1B, .last = 0x21, - .perm = AB5500_PERM_RW, }, { .first = 0x27, .last = 0x2C, - .perm = AB5500_PERM_RW, }, { .first = 0x41, .last = 0x41, - .perm = AB5500_PERM_RW, }, { .first = 0x45, .last = 0x5B, - .perm = AB5500_PERM_RW, }, { .first = 0x5D, .last = 0x5D, - .perm = AB5500_PERM_RW, }, { .first = 0x69, .last = 0x69, - .perm = AB5500_PERM_RW, }, { .first = 0x6C, .last = 0x6D, - .perm = AB5500_PERM_RW, }, { .first = 0x80, .last = 0x81, - .perm = AB5500_PERM_RW, + }, + }, + }, +}; + +static struct ab5500_i2c_ranges ab5500v2_reg_ranges[AB5500_NUM_BANKS] = { + [AB5500_BANK_LED] = { + .bankid = AB5500_BANK_LED, + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x02, + }, + { + .first = 0x04, + .last = 0x0D, + }, + { + .first = 0x10, + .last = 0x1B, + }, + }, + }, + [AB5500_BANK_ADC] = { + .bankid = AB5500_BANK_ADC, + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x1F, + .last = 0x24, + }, + { + .first = 0x26, + .last = 0x35, + }, + { + .first = 0x37, + .last = 0x57, + }, + { + .first = 0xA0, + .last = 0xA5, + }, + }, + }, + [AB5500_BANK_RTC] = { + .bankid = AB5500_BANK_RTC, + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x06, + .last = 0x0B, + }, + { + .first = 0x20, + .last = 0x21, + }, + }, + }, + [AB5500_BANK_STARTUP] = { + .bankid = AB5500_BANK_STARTUP, + .nranges = 14, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + }, + { + .first = 0x1F, + .last = 0x1F, + }, + { + .first = 0x2E, + .last = 0x31, + }, + { + .first = 0x50, + .last = 0x51, + }, + { + .first = 0x60, + .last = 0x61, + }, + { + .first = 0x66, + .last = 0x8A, + }, + { + .first = 0x8C, + .last = 0x96, + }, + { + .first = 0xAA, + .last = 0xB5, + }, + { + .first = 0xB7, + .last = 0xBF, + }, + { + .first = 0xC1, + .last = 0xCA, + }, + { + .first = 0xD3, + .last = 0xE0, + }, + { + .first = 0xEA, + .last = 0xEA, + }, + { + .first = 0xF0, + .last = 0xF0, + }, + { + .first = 0xF6, + .last = 0xF6, + }, + }, + }, + [AB5500_BANK_DBI_ECI] = { + .bankid = AB5500_BANK_DBI_ECI, + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + }, + { + .first = 0x10, + .last = 0x10, + }, + { + .first = 0x13, + .last = 0x13, + }, + }, + }, + [AB5500_BANK_CHG] = { + .bankid = AB5500_BANK_CHG, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x11, + .last = 0x1D, + }, + }, + }, + [AB5500_BANK_FG_BATTCOM_ACC] = { + .bankid = AB5500_BANK_FG_BATTCOM_ACC, + .nranges = 5, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x10, + }, + { + .first = 0x1A, + .last = 0x1D, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0x24, + .last = 0x24, + }, + { + .first = 0x30, + .last = 0x37, + }, + + }, + }, + [AB5500_BANK_USB] = { + .bankid = AB5500_BANK_USB, + .nranges = 5, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x01, + }, + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8B, + }, + { + .first = 0x91, + .last = 0x94, + }, + { + .first = 0x98, + .last = 0x9A, + }, + }, + }, + [AB5500_BANK_IT] = { + .bankid = AB5500_BANK_IT, + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x03, + }, + { + .first = 0x20, + .last = 0x38, + }, + { + .first = 0x40, + .last = 0x58, + }, + { + .first = 0x60, + .last = 0x78, + }, + }, + }, + [AB5500_BANK_VDDDIG_IO_I2C_CLK_TST] = { + .bankid = AB5500_BANK_VDDDIG_IO_I2C_CLK_TST, + .nranges = 7, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x02, + .last = 0x02, + }, + { + .first = 0x12, + .last = 0x12, + }, + { + .first = 0x30, + .last = 0x34, + }, + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x54, + }, + { + .first = 0x60, + .last = 0x64, + }, + { + .first = 0x70, + .last = 0x74, + }, + }, + }, + [AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP] = { + .bankid = AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP, + .nranges = 17, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x01, + .last = 0x02, + }, + { + .first = 0x0D, + .last = 0x0F, + }, + { + .first = 0x19, + .last = 0x1C, + }, + { + .first = 0x1E, + .last = 0x1E, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0x28, + .last = 0x28, + }, + { + .first = 0x30, + .last = 0x33, + }, + { + .first = 0x35, + .last = 0x35, + }, + { + .first = 0x40, + .last = 0x43, + }, + { + .first = 0x45, + .last = 0x45, + }, + { + .first = 0x50, + .last = 0x53, + }, + { + .first = 0x55, + .last = 0x55, + }, + { + .first = 0x60, + .last = 0x63, + }, + { + .first = 0x65, + .last = 0x65, + }, + { + .first = 0x70, + .last = 0x73, + }, + { + .first = 0x75, + .last = 0x75, + }, + { + .first = 0xB1, + .last = 0xB1, + }, + }, + }, + [AB5500_BANK_VIBRA] = { + .bankid = AB5500_BANK_VIBRA, + .nranges = 1, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x10, + .last = 0x13, + }, + }, + }, + [AB5500_BANK_AUDIO_HEADSETUSB] = { + .bankid = AB5500_BANK_AUDIO_HEADSETUSB, + .nranges = 4, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x14, + }, + { + .first = 0x16, + .last = 0x26, + }, + { + .first = 0x28, + .last = 0x30, + }, + { + .first = 0x35, + .last = 0x47, + }, + + }, + }, + [AB5500_BANK_SIM_USBSIM] = { + .bankid = AB5500_BANK_SIM_USBSIM, + .nranges = 3, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x13, + .last = 0x1A, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0xFE, + .last = 0xFE, + }, + }, + }, + [AB5500_BANK_VDENC] = { + .bankid = AB5500_BANK_VDENC, + .nranges = 10, + .range = (struct ab5500_reg_range[]) { + { + .first = 0x00, + .last = 0x12, + }, + { + .first = 0x15, + .last = 0x19, + }, + { + .first = 0x1B, + .last = 0x21, + }, + { + .first = 0x27, + .last = 0x2C, + }, + { + .first = 0x41, + .last = 0x41, + }, + { + .first = 0x45, + .last = 0x5B, + }, + { + .first = 0x5D, + .last = 0x5D, + }, + { + .first = 0x69, + .last = 0x69, + }, + { + .first = 0x6C, + .last = 0x6D, + }, + { + .first = 0x80, + .last = 0x81, }, }, }, @@ -1547,7 +1893,12 @@ static int ab5500_registers_print(struct seq_file *s, void *p) struct ab5500 *ab = s->private; unsigned int i; u8 bank = (u8)ab->debug_bank; + struct ab5500_i2c_ranges *ab5500_reg_ranges; + if (ab->chip_id == AB5500_2_0) + ab5500_reg_ranges = ab5500v2_reg_ranges; + else + ab5500_reg_ranges = ab5500v1_reg_ranges; seq_printf(s, AB5500_NAME_STRING " register values:\n"); for (bank = 0; bank < AB5500_NUM_BANKS; bank++) { seq_printf(s, " bank %u, %s (0x%x):\n", bank, @@ -1908,7 +2259,7 @@ static void ab5500_irq_sync_unlock(struct irq_data *data) struct ab5500 *ab = irq_data_get_irq_chip_data(data); int i; - for (i = 0; i < AB5500_NUM_IRQ_REGS; i++) { + for (i = 0; i < ab->num_event_reg; i++) { u8 old = ab->oldmask[i]; u8 new = ab->mask[i]; int reg; @@ -2058,8 +2409,12 @@ static int __init ab5500_probe(struct platform_device *pdev) } ab->ab5500_irq = res->start; + if (ab->chip_id == AB5500_2_0) + ab->num_event_reg = AB5500_NUM_IRQ_REGS; + else + ab->num_event_reg = AB5500_NUM_EVENT_V1_REG; /* Clear and mask all interrupts */ - for (i = 0; i < AB5500_NUM_IRQ_REGS; i++) { + for (i = 0; i < ab->num_event_reg; i++) { u8 latchreg = AB5500_IT_LATCH0_REG + i; u8 maskreg = AB5500_IT_MASK0_REG + i; u8 val; diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index e99a3d177ea..cd2e36d0108 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -311,6 +311,7 @@ enum ab5500_banks_addr { * @chip_name: name of this chip variant * @chip_id: 8 bit chip ID for this chip variant * @irq_lock: a lock to protect the mask + * @num_event_reg: number events registered * @abb_events: a local bit mask of the prcmu wakeup events * @event_mask: a local copy of the mask event registers * @last_event_mask: a copy of the last event_mask written to hardware @@ -325,6 +326,7 @@ struct ab5500 { char chip_name[32]; u8 chip_id; struct mutex irq_lock; + u32 num_event_reg; u32 abb_events; u8 mask[AB5500_NUM_IRQ_REGS]; u8 oldmask[AB5500_NUM_IRQ_REGS]; -- cgit v1.2.3 From 61ede2e3f1454b84b52f0bece7f796f11f788b99 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Wed, 27 Jul 2011 20:47:17 +0530 Subject: mfd: ab5500-core: board turn on status Read the interrupt registers during boot before clearing the interrupts and save the reason as to why the board has booted. Expose the same to user space via sysfs. ST-Ericsson Linux next: NA ST-Ericsson ID: 338529 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ie27c8a1d7b89dce1a5348109b12e29a0cdae7817 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/27969 Reviewed-by: QATOOLS Reviewed-by: Bibek BASU Reviewed-by: QATEST Reviewed-by: Andrew LYNN --- drivers/mfd/ab5500-core.c | 100 +++++++++++++++++++++++++++++++++++++++++++-- include/linux/mfd/abx500.h | 9 ++++ 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index cf7de4d7443..91f6d25f6e9 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -51,6 +51,18 @@ #define AB5500_CHIP_ID (0x20) #define AB5500_INTERRUPTS 0x01FFFFFF +/* Turn On Status Event */ +#define RTC_ALARM 0x80 +#define POW_KEY_2_ON 0x20 +#define POW_KEY_1_ON 0x08 +#define POR_ON_VBAT 0x01 +#define VBUS_DET 0x02 +#define VBUS_CH_DROP_R 0x08 +#define USB_CH_DET_DONE 0x02 + +/* Global Variables */ +u8 turn_on_stat = 0x00; + /** * struct ab5500_bank * @slave_addr: I2C slave_addr found in AB5500 specification @@ -794,6 +806,34 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, }; +static ssize_t show_chip_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ab5500 *ab5500; + + ab5500 = dev_get_drvdata(dev); + return sprintf(buf, "%#x\n", ab5500 ? ab5500->chip_id : -EINVAL); +} + +static ssize_t show_turn_on_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%#x\n", turn_on_stat); +} + +static DEVICE_ATTR(chip_id, S_IRUGO, show_chip_id, NULL); +static DEVICE_ATTR(turn_on_status, S_IRUGO, show_turn_on_status, NULL); + +static struct attribute *ab5500_sysfs_entries[] = { + &dev_attr_chip_id.attr, + &dev_attr_turn_on_status.attr, + NULL, +}; + +static struct attribute_group ab5500_attr_group = { + .attrs = ab5500_sysfs_entries, +}; + /* * Functionality for getting/setting register values. */ @@ -2357,6 +2397,7 @@ static int __init ab5500_probe(struct platform_device *pdev) struct resource *res; int err; int i; + u8 val; ab = kzalloc(sizeof(struct ab5500), GFP_KERNEL); if (!ab) { @@ -2413,11 +2454,52 @@ static int __init ab5500_probe(struct platform_device *pdev) ab->num_event_reg = AB5500_NUM_IRQ_REGS; else ab->num_event_reg = AB5500_NUM_EVENT_V1_REG; + + /* Read the latch regs to know the reason for turn on */ + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 1, &val); + if (err) + goto exit_no_detect; + if (val & RTC_ALARM) /* RTCAlarm */ + turn_on_stat = RTC_ALARM_EVENT; + if (val & POW_KEY_2_ON) /* PonKey2dbR */ + turn_on_stat |= P_ON_KEY2_EVENT; + if (val & POW_KEY_1_ON) /* PonKey1dbR */ + turn_on_stat |= P_ON_KEY1_EVENT; + + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 2, &val); + if (err) + goto exit_no_detect; + if (val & POR_ON_VBAT) + /* PORnVbat */ + turn_on_stat |= POR_ON_VBAT_EVENT ; + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 8, &val); + if (err) + goto exit_no_detect; + if (val & VBUS_DET) + /* VbusDet */ + turn_on_stat |= VBUS_DET_EVENT; + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 18, &val); + if (err) + goto exit_no_detect; + if (val & VBUS_CH_DROP_R) + /* VBUSChDrop */ + turn_on_stat |= VBUS_DET_EVENT; + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 9, &val); + if (err) + goto exit_no_detect; + if (val & USB_CH_DET_DONE) + /* VBUSChDrop */ + turn_on_stat |= VBUS_DET_EVENT; + /* Clear and mask all interrupts */ for (i = 0; i < ab->num_event_reg; i++) { u8 latchreg = AB5500_IT_LATCH0_REG + i; u8 maskreg = AB5500_IT_MASK0_REG + i; - u8 val; get_register_interruptible(ab, AB5500_BANK_IT, latchreg, &val); set_register_interruptible(ab, AB5500_BANK_IT, maskreg, 0xff); @@ -2456,17 +2538,26 @@ static int __init ab5500_probe(struct platform_device *pdev) ab5500_plf_data->irq.base); if (err) { dev_err(&pdev->dev, "ab5500_mfd_add_device error\n"); - goto exit_no_irq; + goto exit_no_add_dev; } err = ab5500_setup(ab, ab5500_plf_data->init_settings, ab5500_plf_data->init_settings_sz); if (err) { dev_err(&pdev->dev, "ab5500_setup error\n"); - goto exit_no_irq; + goto exit_no_add_dev; } ab5500_setup_debugfs(ab); - return 0; + err = sysfs_create_group(&ab->dev->kobj, &ab5500_attr_group); + if (err) { + dev_err(&pdev->dev, "error creating sysfs entries\n"); + goto exit_no_debugfs; + } + return 0; +exit_no_debugfs: + ab5500_remove_debugfs(); +exit_no_add_dev: + mfd_remove_devices(&pdev->dev); exit_no_irq: if (ab->irq_base) { free_irq(ab->ab5500_irq, ab); @@ -2486,6 +2577,7 @@ static int __exit ab5500_remove(struct platform_device *pdev) * At this point, all subscribers should have unregistered * their notifiers so deactivate IRQ */ + sysfs_remove_group(&ab->dev->kobj, &ab5500_attr_group); ab5500_remove_debugfs(); mfd_remove_devices(&pdev->dev); if (ab->irq_base) { diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index cd2e36d0108..a0a1386774f 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -99,6 +99,15 @@ */ #define AB3100_NUM_REGULATORS 10 +/* Turn On Events */ +#define POR_ON_VBAT_EVENT (0x01 << 0) +#define P_ON_KEY1_EVENT (0x01 << 1) +#define P_ON_KEY2_EVENT (0x01 << 2) +#define RTC_ALARM_EVENT (0x01 << 3) +#define MAIN_CH_DET_EVENT (0x01 << 4) +#define VBUS_DET_EVENT (0x01 << 5) +#define USB_ID_DET_EVENT (0x01 << 6) + /** * struct ab3100 * @access_mutex: lock out concurrent accesses to the AB3100 registers -- cgit v1.2.3 From 4adc31cb4935fd5bc3d45bfb7b5abaeb5339a02b Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Fri, 5 Aug 2011 11:42:53 +0530 Subject: mfd: ab5500-core: update board turn on status Update the status as usb detect if usb line status change interrupt is triggered. ST-Ericsson Linux next: NA ST-Ericsson ID: 338529 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I5b305dbe099a8dcb49f5da0d29da45d177808540 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28371 Reviewed-by: QATOOLS Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR --- drivers/mfd/ab5500-core.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 91f6d25f6e9..5cafc2b1c5c 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -2454,7 +2454,6 @@ static int __init ab5500_probe(struct platform_device *pdev) ab->num_event_reg = AB5500_NUM_IRQ_REGS; else ab->num_event_reg = AB5500_NUM_EVENT_V1_REG; - /* Read the latch regs to know the reason for turn on */ err = get_register_interruptible(ab, AB5500_BANK_IT, AB5500_IT_LATCH0_REG + 1, &val); @@ -2495,6 +2494,13 @@ static int __init ab5500_probe(struct platform_device *pdev) if (val & USB_CH_DET_DONE) /* VBUSChDrop */ turn_on_stat |= VBUS_DET_EVENT; + err = get_register_interruptible(ab, AB5500_BANK_IT, + AB5500_IT_LATCH0_REG + 22, &val); + if (err) + goto exit_no_detect; + if (val & USB_CH_DET_DONE) + /* USBLineStatus Change */ + turn_on_stat |= VBUS_DET_EVENT; /* Clear and mask all interrupts */ for (i = 0; i < ab->num_event_reg; i++) { -- cgit v1.2.3 From 83a0d436cbe0d090ec1ff4ea545962197d81f7b8 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Fri, 19 Aug 2011 11:26:53 +0530 Subject: mfd: ab5500-core: Error in turn on status Mismatch in bit mask for power on vbat and vbus rising edge detect. ST-Ericsson Linux next: NA ST-Ericsson ID: 338529 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I333164a2c739bdd8c39c63236e528f48aa611635 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/29059 Reviewed-by: QATOOLS Reviewed-by: QABUILD Reviewed-by: QATEST Reviewed-by: Rabin VINCENT --- drivers/mfd/ab5500-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 5cafc2b1c5c..1e8fbc9e091 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -55,8 +55,8 @@ #define RTC_ALARM 0x80 #define POW_KEY_2_ON 0x20 #define POW_KEY_1_ON 0x08 -#define POR_ON_VBAT 0x01 -#define VBUS_DET 0x02 +#define POR_ON_VBAT 0x10 +#define VBUS_DET 0x20 #define VBUS_CH_DROP_R 0x08 #define USB_CH_DET_DONE 0x02 -- cgit v1.2.3 From 58c42bffb9a89ab32b64800c7cb874d6ad3567ff Mon Sep 17 00:00:00 2001 From: Mattias Wallin Date: Thu, 25 Aug 2011 16:23:38 +0530 Subject: mfd: Add support for ab8500 v3.3 This patch removes the early drop version of ab8500 which have the really bad version nr 0x0. It also adds support for ab8500 cut 3, MetalFix 3 or v3.3. ST-Ericsson FOSS-OUT ID: Trivial ST-Ericsson Linux next: Will be done. Change-Id: I3b8ef5dab92069bc6270a380d75ddf305715d16f Signed-off-by: Mattias Wallin Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/26680 Reviewed-by: QATEST Reviewed-by: Jonas ABERG --- drivers/mfd/ab8500-core.c | 2 +- include/linux/mfd/abx500.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index c352221925a..2d28fb7376c 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -847,11 +847,11 @@ int __devinit ab8500_init(struct ab8500 *ab8500) return ret; switch (value) { - case AB8500_CUTEARLY: case AB8500_CUT1P0: case AB8500_CUT1P1: case AB8500_CUT2P0: case AB8500_CUT3P0: + case AB8500_CUT3P3: dev_info(ab8500->dev, "detected chip, revision: %#x\n", value); break; default: diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index a0a1386774f..1d96d4f1bcc 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -43,11 +43,11 @@ #define AB8500_CUT3P0 0x30 /* AB8500 CIDs*/ -#define AB8500_CUTEARLY 0x00 #define AB8500_CUT1P0 0x10 #define AB8500_CUT1P1 0x11 #define AB8500_CUT2P0 0x20 #define AB8500_CUT3P0 0x30 +#define AB8500_CUT3P3 0x33 /* * AB3100, EVENTA1, A2 and A3 event register flags -- cgit v1.2.3 From 4d8f1ebafc6f7d4e4e8be19c6eb4ddf80919501b Mon Sep 17 00:00:00 2001 From: Andrew Lynn Date: Tue, 5 Jul 2011 15:39:44 +0100 Subject: mfd: ab8500_core: Expose TurnOnStatus in sysfs Expose TurnOnStatus (Power key, RTC alarm, Vbus detect, etc) in sysfs. ST-Ericsson ID: 338529 ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I3a29a03cae632fb428fba450d77fe85f4066935d Signed-off-by: Andrew Lynn Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/26589 Reviewed-by: QATEST Reviewed-by: Srinidhi KASAGAR Reviewed-by: Jonas ABERG --- drivers/mfd/ab8500-core.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index 2d28fb7376c..3ce5dfe1378 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -92,6 +92,8 @@ #define AB8500_REV_REG 0x80 #define AB8500_SWITCH_OFF_STATUS 0x00 +#define AB8500_TURN_ON_STATUS 0x00 + /* * Map interrupt numbers to the LATCH and MASK register offsets, Interrupt * numbers are indexed into this array with (num / 8). @@ -815,12 +817,40 @@ static ssize_t show_switch_off_status(struct device *dev, return sprintf(buf, "%#x\n", value); } +/* + * ab8500 has turned on due to (TURN_ON_STATUS): + * 0x01 PORnVbat + * 0x02 PonKey1dbF + * 0x04 PonKey2dbF + * 0x08 RTCAlarm + * 0x10 MainChDet + * 0x20 VbusDet + * 0x40 UsbIDDetect + * 0x80 Reserved + */ +static ssize_t show_turn_on_status(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + u8 value; + struct ab8500 *ab8500; + + ab8500 = dev_get_drvdata(dev); + ret = get_register_interruptible(ab8500, AB8500_SYS_CTRL1_BLOCK, + AB8500_TURN_ON_STATUS, &value); + if (ret < 0) + return ret; + return sprintf(buf, "%#x\n", value); +} + static DEVICE_ATTR(chip_id, S_IRUGO, show_chip_id, NULL); static DEVICE_ATTR(switch_off_status, S_IRUGO, show_switch_off_status, NULL); +static DEVICE_ATTR(turn_on_status, S_IRUGO, show_turn_on_status, NULL); static struct attribute *ab8500_sysfs_entries[] = { &dev_attr_chip_id.attr, &dev_attr_switch_off_status.attr, + &dev_attr_turn_on_status.attr, NULL, }; -- cgit v1.2.3 From 606bda5f9b97cd36b6198ca3d1abb79dbd33155d Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Wed, 17 Aug 2011 13:20:21 +0200 Subject: MFD: AB8500: If a charger is present, reboot instead If a changer is present on power off, reboot the system instead of powering it off. ST-Ericsson ID: 354056 ST-Ericsson Linux next: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I03a2a91936219db040f55fa7cd833fdbe5a05657 Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28933 Reviewed-by: Karl KOMIEROWSKI --- drivers/mfd/ab8500-sysctrl.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/mfd/ab8500-sysctrl.c b/drivers/mfd/ab8500-sysctrl.c index e6de6a279a5..99ddb353984 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -18,6 +20,31 @@ void ab8500_power_off(void) { sigset_t old; sigset_t all; + static char *pss[] = {"ab8500_ac", "ab8500_usb"}; + int i; + + /* + * If we have a charger connected and we're powering off, + * reboot into charge-only mode. + */ + + for (i = 0; i < ARRAY_SIZE(pss); i++) { + union power_supply_propval val; + struct power_supply *psy; + int ret; + + psy = power_supply_get_by_name(pss[i]); + if (!psy) + continue; + ret = psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &val); + + if (!ret && val.intval) { + printk(KERN_INFO + "Charger \"%s\" is connected. Rebooting.\n", + pss[i]); + machine_restart(NULL); + } + } sigfillset(&all); -- cgit v1.2.3 From 3239856c3d92d7bfaabcd08ec328fd2f4ca3edca Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Wed, 17 Aug 2011 15:58:52 +0200 Subject: MFD: AB8500: Only reboot into charging if battery type is known Only reboot into charging mode if the battery is of known type. ST-Ericsson ID: 354056 ST-Ericsson Linux next: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I8aa0d6f56940fc3b82858534324ab3027c592b21 Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28945 Reviewed-by: QATEST Reviewed-by: Karl KOMIEROWSKI --- drivers/mfd/ab8500-sysctrl.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/drivers/mfd/ab8500-sysctrl.c b/drivers/mfd/ab8500-sysctrl.c index 99ddb353984..6f4382304c2 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -22,6 +22,10 @@ void ab8500_power_off(void) sigset_t all; static char *pss[] = {"ab8500_ac", "ab8500_usb"}; int i; + bool charger_present = false; + union power_supply_propval val; + struct power_supply *psy; + int ret; /* * If we have a charger connected and we're powering off, @@ -29,23 +33,36 @@ void ab8500_power_off(void) */ for (i = 0; i < ARRAY_SIZE(pss); i++) { - union power_supply_propval val; - struct power_supply *psy; - int ret; - psy = power_supply_get_by_name(pss[i]); if (!psy) continue; + ret = psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &val); if (!ret && val.intval) { + charger_present = true; + break; + } + } + + if (!charger_present) + goto shutdown; + + /* Check if battery is known */ + psy = power_supply_get_by_name("ab8500_btemp"); + if (psy) { + ret = psy->get_property(psy, POWER_SUPPLY_PROP_TECHNOLOGY, + &val); + if (!ret && val.intval != POWER_SUPPLY_TECHNOLOGY_UNKNOWN) { printk(KERN_INFO - "Charger \"%s\" is connected. Rebooting.\n", + "Charger \"%s\" is connected with known battery." + " Rebooting.\n", pss[i]); machine_restart(NULL); } } +shutdown: sigfillset(&all); if (!sigprocmask(SIG_BLOCK, &all, &old)) { -- cgit v1.2.3 From c3057fa915e2f1981c3a40d8237488e4e309c373 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Thu, 18 Aug 2011 10:14:38 +0200 Subject: MFD: AB8500: Provide charging as reset reason Provide charging as reset reason when proper ST-Ericsson ID: 354056 ST-Ericsson Linux next: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I5c6dd0e0a4bce63dbf43a138b132f27e2e42b1b1 Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28982 Reviewed-by: QATEST Reviewed-by: Karl KOMIEROWSKI --- drivers/mfd/ab8500-sysctrl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/ab8500-sysctrl.c b/drivers/mfd/ab8500-sysctrl.c index 6f4382304c2..d24c41fed7a 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -58,7 +58,7 @@ void ab8500_power_off(void) "Charger \"%s\" is connected with known battery." " Rebooting.\n", pss[i]); - machine_restart(NULL); + machine_restart("charging"); } } -- cgit v1.2.3 From f0103fe9d2dde02bf3f629b843d43f8af56a8b9f Mon Sep 17 00:00:00 2001 From: Rajagopala V Date: Thu, 25 Aug 2011 16:57:56 +0530 Subject: u5500: ponkey: shutdown time configuration support for compile time configuration of poweron key shutdown time. Default is 10sec and can be set to 5sec or disabled ST-Ericsson Linux next: NA ST-Ericsson ID: WP257125 ST-Ericsson FOSS-OUT ID: NA Change-Id: I7fbf57d21ff218370dba24182716222c354f4dc5 Signed-off-by: Rajagopala V Tested-by: Rajagopala VENKATARAVANAPPA X Reviewed-by: Srinidhi KASAGAR Signed-off-by: Robert Marklund --- include/linux/mfd/abx500.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 1d96d4f1bcc..79b3e9eb4f3 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -369,6 +369,9 @@ struct ab5500_platform_data { struct ab5500_regulator_platform_data *regulator; }; +struct ab5500_ponkey_platform_data { + u8 shutdown_secs; +}; int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, u8 value); -- cgit v1.2.3 From 52bfa255bcd63f0a15363832981ca811ccf53652 Mon Sep 17 00:00:00 2001 From: David Paris Date: Tue, 2 Aug 2011 11:55:16 +0530 Subject: MFD: tc3589x: Optimize power save settings for suspend Compared to HSI GPIO V3.1, register 0x82 is set with a different value 0x82 = 0xE (HSI GPIO V3.1) 0x82 = 0xF (on android) In case of android, it seems that GPIO reset should be applied to reach 13uA of consumption on TC3589x when AP in DeepSleep Change-Id: I64eaa4304fcd9f71546cadba0aa703c91b6075df Signed-off-by: David Paris Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/17640 --- drivers/mfd/tc3589x.c | 126 +++++++++++++++++++++++++++++++++++++++++--- include/linux/mfd/tc3589x.h | 61 ++++++++++++--------- 2 files changed, 154 insertions(+), 33 deletions(-) diff --git a/drivers/mfd/tc3589x.c b/drivers/mfd/tc3589x.c index c27e515b072..8f172ce41bc 100644 --- a/drivers/mfd/tc3589x.c +++ b/drivers/mfd/tc3589x.c @@ -357,16 +357,111 @@ static int __devexit tc3589x_remove(struct i2c_client *client) return 0; } +static u32 sleep_regs[] = { + TC3589x_IOPC0_L, + TC3589x_IOPC0_H, + TC3589x_IOPC1_L, + TC3589x_IOPC1_H, + TC3589x_IOPC2_L, + TC3589x_IOPC2_H, + TC3589x_DRIVE0_L, + TC3589x_DRIVE0_H, + TC3589x_DRIVE1_L, + TC3589x_DRIVE1_H, + TC3589x_DRIVE2_L, + TC3589x_DRIVE2_H, + TC3589x_DRIVE3, + TC3589x_GPIODATA0, + TC3589x_GPIOMASK0, + TC3589x_GPIODATA1, + TC3589x_GPIOMASK1, + TC3589x_GPIODATA2, + TC3589x_GPIOMASK2, + TC3589x_GPIODIR0, + TC3589x_GPIODIR1, + TC3589x_GPIODIR2, + TC3589x_GPIOIE0, + TC3589x_GPIOIE1, + TC3589x_GPIOIE2, + TC3589x_RSTCTRL, + TC3589x_CLKCFG, +}; + +static u8 sleep_regs_val[] = { + 0x00, /* TC3589x_IOPC0_L */ + 0x00, /* TC3589x_IOPC0_H */ + 0x00, /* TC3589x_IOPC1_L */ + 0x00, /* TC3589x_IOPC1_H */ + 0x00, /* TC3589x_IOPC2_L */ + 0x00, /* TC3589x_IOPC2_H */ + 0xff, /* TC3589x_DRIVE0_L */ + 0xff, /* TC3589x_DRIVE0_H */ + 0xff, /* TC3589x_DRIVE1_L */ + 0xff, /* TC3589x_DRIVE1_H */ + 0xff, /* TC3589x_DRIVE2_L */ + 0xff, /* TC3589x_DRIVE2_H */ + 0x0f, /* TC3589x_DRIVE3 */ + 0x80, /* TC3589x_GPIODATA0 */ + 0x80, /* TC3589x_GPIOMASK0 */ + 0x80, /* TC3589x_GPIODATA1 */ + 0x80, /* TC3589x_GPIOMASK1 */ + 0x06, /* TC3589x_GPIODATA2 */ + 0x06, /* TC3589x_GPIOMASK2 */ + 0xf0, /* TC3589x_GPIODIR0 */ + 0xe0, /* TC3589x_GPIODIR1 */ + 0xee, /* TC3589x_GPIODIR2 */ + 0x0f, /* TC3589x_GPIOIE0 */ + 0x1f, /* TC3589x_GPIOIE1 */ + 0x11, /* TC3589x_GPIOIE2 */ + 0x0f, /* TC3589x_RSTCTRL */ + 0xb0 /* TC3589x_CLKCFG */ + +}; + +static u8 sleep_regs_backup[ARRAY_SIZE(sleep_regs)]; + static int tc3589x_suspend(struct device *dev) { struct tc3589x *tc3589x = dev_get_drvdata(dev); struct i2c_client *client = tc3589x->i2c; int ret = 0; + int i, j; + int val; + + /* Put the system to sleep mode */ + if (!device_may_wakeup(&client->dev)) { + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + val = tc3589x_reg_read(tc3589x, + sleep_regs[i]); + if (val < 0) + goto out; + + sleep_regs_backup[i] = (u8) (val & 0xff); + } - /* put the system to sleep mode */ - if (!device_may_wakeup(&client->dev)) - ret = tc3589x_reg_write(tc3589x, TC3589x_CLKMODE, - TC3589x_CLKMODE_MODCTL_SLEEP); + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + ret = tc3589x_reg_write(tc3589x, + sleep_regs[i], + sleep_regs_val[i]); + if (ret < 0) + goto fail; + + } + + ret = tc3589x_reg_write(tc3589x, + TC3589x_CLKMODE, + TC3589x_CLKMODE_MODCTL_SLEEP); + } +out: + return ret; +fail: + for (j = 0; j <= i; j++) { + ret = tc3589x_reg_write(tc3589x, + sleep_regs[i], + sleep_regs_backup[i]); + if (ret < 0) + break; + } return ret; } @@ -376,12 +471,27 @@ static int tc3589x_resume(struct device *dev) struct tc3589x *tc3589x = dev_get_drvdata(dev); struct i2c_client *client = tc3589x->i2c; int ret = 0; + int i; - /* enable the system into operation */ + /* Enable the system into operation */ if (!device_may_wakeup(&client->dev)) - ret = tc3589x_reg_write(tc3589x, TC3589x_CLKMODE, - TC3589x_CLKMODE_MODCTL_OPERATION); - + { + ret = tc3589x_reg_write(tc3589x, + TC3589x_CLKMODE, + TC3589x_CLKMODE_MODCTL_OPERATION); + if (ret < 0) + goto out; + + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + ret = tc3589x_reg_write(tc3589x, + sleep_regs[i], + sleep_regs_backup[i]); + /* Not much to do here if we fail */ + if (ret < 0) + break; + } + } +out: return ret; } diff --git a/include/linux/mfd/tc3589x.h b/include/linux/mfd/tc3589x.h index 16c76e124f9..b8c6a941071 100644 --- a/include/linux/mfd/tc3589x.h +++ b/include/linux/mfd/tc3589x.h @@ -31,20 +31,43 @@ enum tx3589x_block { #define TC3589x_EVTCODE_FIFO 0x10 #define TC3589x_KBDMFS 0x8F -#define TC3589x_IRQST 0x91 - -#define TC3589x_MANFCODE_MAGIC 0x03 #define TC3589x_MANFCODE 0x80 +#define TC3589x_MANFCODE_MAGIC 0x03 #define TC3589x_VERSION 0x81 -#define TC3589x_IOCFG 0xA7 +#define TC3589x_RSTCTRL 0x82 +#define TC3589x_EXTRSTN 0x83 +#define TC3589x_RSTINTCLR 0x84 #define TC3589x_CLKMODE 0x88 #define TC3589x_CLKCFG 0x89 #define TC3589x_CLKEN 0x8A +#define TC3589x_IRQST 0x91 -#define TC3589x_RSTCTRL 0x82 -#define TC3589x_EXTRSTN 0x83 -#define TC3589x_RSTINTCLR 0x84 +#define TC3589x_DRIVE0_L 0xA0 +#define TC3589x_DRIVE0_H 0xA1 +#define TC3589x_DRIVE1_L 0xA2 +#define TC3589x_DRIVE1_H 0xA3 +#define TC3589x_DRIVE2_L 0xA4 +#define TC3589x_DRIVE2_H 0XA5 +#define TC3589x_DRIVE3 0xA6 +#define TC3589x_IOCFG 0xA7 + +#define TC3589x_IOPC0_L 0xAA +#define TC3589x_IOPC0_H 0xAB +#define TC3589x_IOPC1_L 0xAC +#define TC3589x_IOPC1_H 0xAD +#define TC3589x_IOPC2_L 0xAE +#define TC3589x_IOPC2_H 0xAF + +#define TC3589x_GPIODATA0 0xC0 +#define TC3589x_GPIOMASK0 0xC1 +#define TC3589x_GPIODATA1 0xC2 +#define TC3589x_GPIOMASK1 0xC3 +#define TC3589x_GPIODATA2 0xC4 +#define TC3589x_GPIOMASK2 0xC5 +#define TC3589x_GPIODIR0 0xC6 +#define TC3589x_GPIODIR1 0xC7 +#define TC3589x_GPIODIR2 0xC8 /* Pull up/down configuration registers */ #define TC3589x_IOCFG 0xA7 @@ -75,17 +98,12 @@ enum tx3589x_block { #define TC3589x_GPIOIC0 0xDC #define TC3589x_GPIOIC1 0xDD #define TC3589x_GPIOIC2 0xDE - -#define TC3589x_GPIODATA0 0xC0 -#define TC3589x_GPIOMASK0 0xc1 -#define TC3589x_GPIODATA1 0xC2 -#define TC3589x_GPIOMASK1 0xc3 -#define TC3589x_GPIODATA2 0xC4 -#define TC3589x_GPIOMASK2 0xC5 - -#define TC3589x_GPIODIR0 0xC6 -#define TC3589x_GPIODIR1 0xC7 -#define TC3589x_GPIODIR2 0xC8 +#define TC3589x_GPIOODM0 0xE0 +#define TC3589x_GPIOODE0 0xE1 +#define TC3589x_GPIOODM1 0xE2 +#define TC3589x_GPIOODE1 0xE3 +#define TC3589x_GPIOODM2 0xE4 +#define TC3589x_GPIOODE2 0xE5 #define TC3589x_GPIOSYNC0 0xE6 #define TC3589x_GPIOSYNC1 0xE7 @@ -95,13 +113,6 @@ enum tx3589x_block { #define TC3589x_GPIOWAKE1 0xEA #define TC3589x_GPIOWAKE2 0xEB -#define TC3589x_GPIOODM0 0xE0 -#define TC3589x_GPIOODE0 0xE1 -#define TC3589x_GPIOODM1 0xE2 -#define TC3589x_GPIOODE1 0xE3 -#define TC3589x_GPIOODM2 0xE4 -#define TC3589x_GPIOODE2 0xE5 - #define TC3589x_INT_GPIIRQ 0 #define TC3589x_INT_TI0IRQ 1 #define TC3589x_INT_TI1IRQ 2 -- cgit v1.2.3 From b0f6ff0d8e02a0a7b0270ee563e65b36e11dbbb6 Mon Sep 17 00:00:00 2001 From: Martin Persson Date: Tue, 2 Aug 2011 12:04:49 +0530 Subject: MFD: tc35892: Correct optimized power settings Change-Id: I83fd9379c037fe3c949aa5d01a99423d2fc555b4 Signed-off-by: Martin Persson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/19261 Reviewed-by: Jonas ABERG --- drivers/mfd/tc3589x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/tc3589x.c b/drivers/mfd/tc3589x.c index 8f172ce41bc..05e96d03108 100644 --- a/drivers/mfd/tc3589x.c +++ b/drivers/mfd/tc3589x.c @@ -482,7 +482,7 @@ static int tc3589x_resume(struct device *dev) if (ret < 0) goto out; - for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + for (i = ARRAY_SIZE(sleep_regs) - 1; i >= 0; i--) { ret = tc3589x_reg_write(tc3589x, sleep_regs[i], sleep_regs_backup[i]); -- cgit v1.2.3 From d4d51bffcfa2b299eb1c88b908e078b91ad17c8d Mon Sep 17 00:00:00 2001 From: Virupax Sadashivpetimath Date: Wed, 22 Dec 2010 14:52:44 +0530 Subject: amba-pl022: Clear SSE once the transfer is completed Clear SSE after the xfer completion and remove the not needed default register restoration in suspend. ST-Ericsson ID: ER 316546 Change-Id: I73368e18bd55abdff6b1909239bcf5b64583b452 Signed-off-by: Virupax Sadashivpetimath --- drivers/spi/spi-pl022.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index d1ce32448a9..7ef497a8b25 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -514,7 +514,12 @@ static void giveback(struct pl022 *pl022) msg->state = NULL; if (msg->complete) msg->complete(msg->context); - /* This message is completed, so let's turn off the clocks & power */ + + /* disable the SPI/SSP operation */ + writew((readw(SSP_CR1(pl022->virtbase)) & + (~SSP_CR1_MASK_SSE)), SSP_CR1(pl022->virtbase)); + + /* This message is completed, so let's turn off the clock! */ clk_disable(pl022->clk); amba_pclk_disable(pl022->adev); amba_vcore_disable(pl022->adev); @@ -2311,11 +2316,6 @@ static int pl022_suspend(struct amba_device *adev, pm_message_t state) return status; } - amba_vcore_enable(adev); - amba_pclk_enable(adev); - load_ssp_default_config(pl022); - amba_pclk_disable(adev); - amba_vcore_disable(adev); dev_dbg(&adev->dev, "suspended\n"); return 0; } -- cgit v1.2.3 From 2af838c573e24e69393256669a2f994406d54866 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Mon, 28 Feb 2011 16:42:41 +0100 Subject: SPI: amba-pl022: Fix build warnings ST-Ericsson Linux next: Not tested, ask SSM for ER ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Iaa6b6064e2f55081ffed82d3791ecfbb2e6dd9b4 Signed-off-by: Jonas Aaberg --- drivers/spi/spi-pl022.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 7ef497a8b25..dddf1265d11 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -1908,7 +1908,7 @@ static int pl022_setup(struct spi_device *spi) { struct pl022_config_chip const *chip_info; struct chip_data *chip; - struct ssp_clock_params clk_freq = {0, }; + struct ssp_clock_params clk_freq = { .cpsdvsr = 0, .scr = 0}; int status = 0; struct pl022 *pl022 = spi_master_get_devdata(spi->master); unsigned int bits = spi->bits_per_word; -- cgit v1.2.3 From dc08b2a1509afad124ba10e7213ef3360bcfea92 Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Wed, 17 Aug 2011 14:05:43 +0530 Subject: mfd: Add Toshiba's TC35892 MFD core The TC35892 I/O Expander provides 24 GPIOs, a keypad controller, timers, and a rotator wheel interface. This patch adds the MFD core. Acked-by: Linus Walleij Signed-off-by: Rabin Vincent Signed-off-by: Samuel Ortiz Change-Id: Ib9fe0f306c241284be763e8c65ce1bee44d22d5a Signed-off-by: Virupax Sadashivpetimath --- drivers/mfd/Kconfig | 11 ++ drivers/mfd/Makefile | 1 + drivers/mfd/tc35892.c | 345 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/tc35892.h | 132 +++++++++++++++++ 4 files changed, 489 insertions(+) create mode 100644 drivers/mfd/tc35892.c create mode 100644 include/linux/mfd/tc35892.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 9833f0cd2d5..432a259e444 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -304,6 +304,17 @@ config MFD_TC3589X additional drivers must be enabled in order to use the functionality of the device. +config MFD_TC35892 + bool "Support Toshiba TC35892" + depends on I2C=y && GENERIC_HARDIRQS + select MFD_CORE + help + Support for the Toshiba TC35892 I/O Expander. + + This driver provides common support for accessing the device, + additional drivers must be enabled in order to use the + functionality of the device. + config MFD_TMIO bool default n diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 88c8aa619e0..db48396bc3b 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_MFD_TI_SSP) += ti-ssp.o obj-$(CONFIG_MFD_STMPE) += stmpe.o obj-$(CONFIG_MFD_TC3589X) += tc3589x.o +obj-$(CONFIG_MFD_TC35892) += tc35892.o obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o tmio_core.o obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o tmio_core.o diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c new file mode 100644 index 00000000000..e619e2a5599 --- /dev/null +++ b/drivers/mfd/tc35892.c @@ -0,0 +1,345 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + * Author: Hanumath Prasad for ST-Ericsson + * Author: Rabin Vincent for ST-Ericsson + */ + +#include +#include +#include +#include +#include +#include +#include + +/** + * tc35892_reg_read() - read a single TC35892 register + * @tc35892: Device to read from + * @reg: Register to read + */ +int tc35892_reg_read(struct tc35892 *tc35892, u8 reg) +{ + int ret; + + ret = i2c_smbus_read_byte_data(tc35892->i2c, reg); + if (ret < 0) + dev_err(tc35892->dev, "failed to read reg %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_reg_read); + +/** + * tc35892_reg_read() - write a single TC35892 register + * @tc35892: Device to write to + * @reg: Register to read + * @data: Value to write + */ +int tc35892_reg_write(struct tc35892 *tc35892, u8 reg, u8 data) +{ + int ret; + + ret = i2c_smbus_write_byte_data(tc35892->i2c, reg, data); + if (ret < 0) + dev_err(tc35892->dev, "failed to write reg %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_reg_write); + +/** + * tc35892_block_read() - read multiple TC35892 registers + * @tc35892: Device to read from + * @reg: First register + * @length: Number of registers + * @values: Buffer to write to + */ +int tc35892_block_read(struct tc35892 *tc35892, u8 reg, u8 length, u8 *values) +{ + int ret; + + ret = i2c_smbus_read_i2c_block_data(tc35892->i2c, reg, length, values); + if (ret < 0) + dev_err(tc35892->dev, "failed to read regs %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_block_read); + +/** + * tc35892_block_write() - write multiple TC35892 registers + * @tc35892: Device to write to + * @reg: First register + * @length: Number of registers + * @values: Values to write + */ +int tc35892_block_write(struct tc35892 *tc35892, u8 reg, u8 length, + const u8 *values) +{ + int ret; + + ret = i2c_smbus_write_i2c_block_data(tc35892->i2c, reg, length, + values); + if (ret < 0) + dev_err(tc35892->dev, "failed to write regs %#x: %d\n", + reg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_block_write); + +/** + * tc35892_set_bits() - set the value of a bitfield in a TC35892 register + * @tc35892: Device to write to + * @reg: Register to write + * @mask: Mask of bits to set + * @values: Value to set + */ +int tc35892_set_bits(struct tc35892 *tc35892, u8 reg, u8 mask, u8 val) +{ + int ret; + + mutex_lock(&tc35892->lock); + + ret = tc35892_reg_read(tc35892, reg); + if (ret < 0) + goto out; + + ret &= ~mask; + ret |= val; + + ret = tc35892_reg_write(tc35892, reg, ret); + +out: + mutex_unlock(&tc35892->lock); + return ret; +} +EXPORT_SYMBOL_GPL(tc35892_set_bits); + +static struct resource gpio_resources[] = { + { + .start = TC35892_INT_GPIIRQ, + .end = TC35892_INT_GPIIRQ, + .flags = IORESOURCE_IRQ, + }, +}; + +static struct mfd_cell tc35892_devs[] = { + { + .name = "tc35892-gpio", + .num_resources = ARRAY_SIZE(gpio_resources), + .resources = &gpio_resources[0], + }, +}; + +static irqreturn_t tc35892_irq(int irq, void *data) +{ + struct tc35892 *tc35892 = data; + int status; + + status = tc35892_reg_read(tc35892, TC35892_IRQST); + if (status < 0) + return IRQ_NONE; + + while (status) { + int bit = __ffs(status); + + handle_nested_irq(tc35892->irq_base + bit); + status &= ~(1 << bit); + } + + /* + * A dummy read or write (to any register) appears to be necessary to + * have the last interrupt clear (for example, GPIO IC write) take + * effect. + */ + tc35892_reg_read(tc35892, TC35892_IRQST); + + return IRQ_HANDLED; +} + +static void tc35892_irq_dummy(unsigned int irq) +{ + /* No mask/unmask at this level */ +} + +static struct irq_chip tc35892_irq_chip = { + .name = "tc35892", + .mask = tc35892_irq_dummy, + .unmask = tc35892_irq_dummy, +}; + +static int tc35892_irq_init(struct tc35892 *tc35892) +{ + int base = tc35892->irq_base; + int irq; + + for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) { + set_irq_chip_data(irq, tc35892); + set_irq_chip_and_handler(irq, &tc35892_irq_chip, + handle_edge_irq); + set_irq_nested_thread(irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(irq, IRQF_VALID); +#else + set_irq_noprobe(irq); +#endif + } + + return 0; +} + +static void tc35892_irq_remove(struct tc35892 *tc35892) +{ + int base = tc35892->irq_base; + int irq; + + for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) { +#ifdef CONFIG_ARM + set_irq_flags(irq, 0); +#endif + set_irq_chip_and_handler(irq, NULL, NULL); + set_irq_chip_data(irq, NULL); + } +} + +static int tc35892_chip_init(struct tc35892 *tc35892) +{ + int manf, ver, ret; + + manf = tc35892_reg_read(tc35892, TC35892_MANFCODE); + if (manf < 0) + return manf; + + ver = tc35892_reg_read(tc35892, TC35892_VERSION); + if (ver < 0) + return ver; + + if (manf != TC35892_MANFCODE_MAGIC) { + dev_err(tc35892->dev, "unknown manufacturer: %#x\n", manf); + return -EINVAL; + } + + dev_info(tc35892->dev, "manufacturer: %#x, version: %#x\n", manf, ver); + + /* Put everything except the IRQ module into reset */ + ret = tc35892_reg_write(tc35892, TC35892_RSTCTRL, + TC35892_RSTCTRL_TIMRST + | TC35892_RSTCTRL_ROTRST + | TC35892_RSTCTRL_KBDRST + | TC35892_RSTCTRL_GPIRST); + if (ret < 0) + return ret; + + /* Clear the reset interrupt. */ + return tc35892_reg_write(tc35892, TC35892_RSTINTCLR, 0x1); +} + +static int __devinit tc35892_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct tc35892_platform_data *pdata = i2c->dev.platform_data; + struct tc35892 *tc35892; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_I2C_BLOCK)) + return -EIO; + + tc35892 = kzalloc(sizeof(struct tc35892), GFP_KERNEL); + if (!tc35892) + return -ENOMEM; + + mutex_init(&tc35892->lock); + + tc35892->dev = &i2c->dev; + tc35892->i2c = i2c; + tc35892->pdata = pdata; + tc35892->irq_base = pdata->irq_base; + tc35892->num_gpio = id->driver_data; + + i2c_set_clientdata(i2c, tc35892); + + ret = tc35892_chip_init(tc35892); + if (ret) + goto out_free; + + ret = tc35892_irq_init(tc35892); + if (ret) + goto out_free; + + ret = request_threaded_irq(tc35892->i2c->irq, NULL, tc35892_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "tc35892", tc35892); + if (ret) { + dev_err(tc35892->dev, "failed to request IRQ: %d\n", ret); + goto out_removeirq; + } + + ret = mfd_add_devices(tc35892->dev, -1, tc35892_devs, + ARRAY_SIZE(tc35892_devs), NULL, + tc35892->irq_base); + if (ret) { + dev_err(tc35892->dev, "failed to add children\n"); + goto out_freeirq; + } + + return 0; + +out_freeirq: + free_irq(tc35892->i2c->irq, tc35892); +out_removeirq: + tc35892_irq_remove(tc35892); +out_free: + kfree(tc35892); + return ret; +} + +static int __devexit tc35892_remove(struct i2c_client *client) +{ + struct tc35892 *tc35892 = i2c_get_clientdata(client); + + mfd_remove_devices(tc35892->dev); + + free_irq(tc35892->i2c->irq, tc35892); + tc35892_irq_remove(tc35892); + + kfree(tc35892); + + return 0; +} + +static const struct i2c_device_id tc35892_id[] = { + { "tc35892", 24 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tc35892_id); + +static struct i2c_driver tc35892_driver = { + .driver.name = "tc35892", + .driver.owner = THIS_MODULE, + .probe = tc35892_probe, + .remove = __devexit_p(tc35892_remove), + .id_table = tc35892_id, +}; + +static int __init tc35892_init(void) +{ + return i2c_add_driver(&tc35892_driver); +} +subsys_initcall(tc35892_init); + +static void __exit tc35892_exit(void) +{ + i2c_del_driver(&tc35892_driver); +} +module_exit(tc35892_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TC35892 MFD core driver"); +MODULE_AUTHOR("Hanumath Prasad, Rabin Vincent"); diff --git a/include/linux/mfd/tc35892.h b/include/linux/mfd/tc35892.h new file mode 100644 index 00000000000..e47f770d306 --- /dev/null +++ b/include/linux/mfd/tc35892.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License, version 2 + */ + +#ifndef __LINUX_MFD_TC35892_H +#define __LINUX_MFD_TC35892_H + +#include + +#define TC35892_RSTCTRL_IRQRST (1 << 4) +#define TC35892_RSTCTRL_TIMRST (1 << 3) +#define TC35892_RSTCTRL_ROTRST (1 << 2) +#define TC35892_RSTCTRL_KBDRST (1 << 1) +#define TC35892_RSTCTRL_GPIRST (1 << 0) + +#define TC35892_IRQST 0x91 + +#define TC35892_MANFCODE_MAGIC 0x03 +#define TC35892_MANFCODE 0x80 +#define TC35892_VERSION 0x81 +#define TC35892_IOCFG 0xA7 + +#define TC35892_CLKMODE 0x88 +#define TC35892_CLKCFG 0x89 +#define TC35892_CLKEN 0x8A + +#define TC35892_RSTCTRL 0x82 +#define TC35892_EXTRSTN 0x83 +#define TC35892_RSTINTCLR 0x84 + +#define TC35892_GPIOIS0 0xC9 +#define TC35892_GPIOIS1 0xCA +#define TC35892_GPIOIS2 0xCB +#define TC35892_GPIOIBE0 0xCC +#define TC35892_GPIOIBE1 0xCD +#define TC35892_GPIOIBE2 0xCE +#define TC35892_GPIOIEV0 0xCF +#define TC35892_GPIOIEV1 0xD0 +#define TC35892_GPIOIEV2 0xD1 +#define TC35892_GPIOIE0 0xD2 +#define TC35892_GPIOIE1 0xD3 +#define TC35892_GPIOIE2 0xD4 +#define TC35892_GPIORIS0 0xD6 +#define TC35892_GPIORIS1 0xD7 +#define TC35892_GPIORIS2 0xD8 +#define TC35892_GPIOMIS0 0xD9 +#define TC35892_GPIOMIS1 0xDA +#define TC35892_GPIOMIS2 0xDB +#define TC35892_GPIOIC0 0xDC +#define TC35892_GPIOIC1 0xDD +#define TC35892_GPIOIC2 0xDE + +#define TC35892_GPIODATA0 0xC0 +#define TC35892_GPIOMASK0 0xc1 +#define TC35892_GPIODATA1 0xC2 +#define TC35892_GPIOMASK1 0xc3 +#define TC35892_GPIODATA2 0xC4 +#define TC35892_GPIOMASK2 0xC5 + +#define TC35892_GPIODIR0 0xC6 +#define TC35892_GPIODIR1 0xC7 +#define TC35892_GPIODIR2 0xC8 + +#define TC35892_GPIOSYNC0 0xE6 +#define TC35892_GPIOSYNC1 0xE7 +#define TC35892_GPIOSYNC2 0xE8 + +#define TC35892_GPIOWAKE0 0xE9 +#define TC35892_GPIOWAKE1 0xEA +#define TC35892_GPIOWAKE2 0xEB + +#define TC35892_GPIOODM0 0xE0 +#define TC35892_GPIOODE0 0xE1 +#define TC35892_GPIOODM1 0xE2 +#define TC35892_GPIOODE1 0xE3 +#define TC35892_GPIOODM2 0xE4 +#define TC35892_GPIOODE2 0xE5 + +#define TC35892_INT_GPIIRQ 0 +#define TC35892_INT_TI0IRQ 1 +#define TC35892_INT_TI1IRQ 2 +#define TC35892_INT_TI2IRQ 3 +#define TC35892_INT_ROTIRQ 5 +#define TC35892_INT_KBDIRQ 6 +#define TC35892_INT_PORIRQ 7 + +#define TC35892_NR_INTERNAL_IRQS 8 +#define TC35892_INT_GPIO(x) (TC35892_NR_INTERNAL_IRQS + (x)) + +struct tc35892 { + struct mutex lock; + struct device *dev; + struct i2c_client *i2c; + + int irq_base; + int num_gpio; + struct tc35892_platform_data *pdata; +}; + +extern int tc35892_reg_write(struct tc35892 *tc35892, u8 reg, u8 data); +extern int tc35892_reg_read(struct tc35892 *tc35892, u8 reg); +extern int tc35892_block_read(struct tc35892 *tc35892, u8 reg, u8 length, + u8 *values); +extern int tc35892_block_write(struct tc35892 *tc35892, u8 reg, u8 length, + const u8 *values); +extern int tc35892_set_bits(struct tc35892 *tc35892, u8 reg, u8 mask, u8 val); + +/** + * struct tc35892_gpio_platform_data - TC35892 GPIO platform data + * @gpio_base: first gpio number assigned to TC35892. A maximum of + * %TC35892_NR_GPIOS GPIOs will be allocated. + */ +struct tc35892_gpio_platform_data { + int gpio_base; +}; + +/** + * struct tc35892_platform_data - TC35892 platform data + * @irq_base: base IRQ number. %TC35892_NR_IRQS irqs will be used. + * @gpio: GPIO-specific platform data + */ +struct tc35892_platform_data { + int irq_base; + struct tc35892_gpio_platform_data *gpio; +}; + +#define TC35892_NR_GPIOS 24 +#define TC35892_NR_IRQS TC35892_INT_GPIO(TC35892_NR_GPIOS) + +#endif -- cgit v1.2.3 From b5774f98a1c5ab417ea8dc4ec527292693f64597 Mon Sep 17 00:00:00 2001 From: Virupax Sadashivpetimath Date: Wed, 17 Aug 2011 21:04:48 +0530 Subject: mfd: Convert tc35892 to new irq_ methods Change-Id: I841eff44f2939b5e718050039a0a2ccb87477461 Signed-off-by: Virupax Sadashivpetimath --- drivers/mfd/tc35892.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c index e619e2a5599..3f3a5d8d435 100644 --- a/drivers/mfd/tc35892.c +++ b/drivers/mfd/tc35892.c @@ -170,8 +170,8 @@ static void tc35892_irq_dummy(unsigned int irq) static struct irq_chip tc35892_irq_chip = { .name = "tc35892", - .mask = tc35892_irq_dummy, - .unmask = tc35892_irq_dummy, + .irq_mask = tc35892_irq_dummy, + .irq_unmask = tc35892_irq_dummy, }; static int tc35892_irq_init(struct tc35892 *tc35892) @@ -180,10 +180,10 @@ static int tc35892_irq_init(struct tc35892 *tc35892) int irq; for (irq = base; irq < base + TC35892_NR_INTERNAL_IRQS; irq++) { - set_irq_chip_data(irq, tc35892); - set_irq_chip_and_handler(irq, &tc35892_irq_chip, + irq_set_chip_data(irq, tc35892); + irq_set_chip_and_handler(irq, &tc35892_irq_chip, handle_edge_irq); - set_irq_nested_thread(irq, 1); + irq_set_nested_thread(irq, 1); #ifdef CONFIG_ARM set_irq_flags(irq, IRQF_VALID); #else @@ -203,8 +203,8 @@ static void tc35892_irq_remove(struct tc35892 *tc35892) #ifdef CONFIG_ARM set_irq_flags(irq, 0); #endif - set_irq_chip_and_handler(irq, NULL, NULL); - set_irq_chip_data(irq, NULL); + irq_set_chip_and_handler(irq, NULL, NULL); + irq_set_chip_data(irq, NULL); } } -- cgit v1.2.3 From f79a8b2af6d36150308f2d79fb8fa53f9e236aa0 Mon Sep 17 00:00:00 2001 From: Sundar Iyer Date: Sat, 30 Oct 2010 11:39:08 +0530 Subject: mfd/tc35892: add suspend/resume support ST-Ericcson ID: TASK_ER170555 Change-Id: I6e36b7430ad1ece88600ab4e17f81d08878b34b4 Signed-off-by: Sundar Iyer --- drivers/mfd/tc35892.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c index 3f3a5d8d435..9fd67e1e9a7 100644 --- a/drivers/mfd/tc35892.c +++ b/drivers/mfd/tc35892.c @@ -14,6 +14,9 @@ #include #include +#define TC35892_CLKMODE_MODCTL_SLEEP 0x0 +#define TC35892_CLKMODE_MODCTL_OPERATION (1 << 0) + /** * tc35892_reg_read() - read a single TC35892 register * @tc35892: Device to read from @@ -314,6 +317,42 @@ static int __devexit tc35892_remove(struct i2c_client *client) return 0; } +#if CONFIG_PM +static int tc35892_suspend(struct device *dev) +{ + struct tc35892 *tc35892 = dev_get_drvdata(dev); + struct i2c_client *client = tc35892->i2c; + int ret = 0; + + /* put the system to sleep mode */ + if (!device_may_wakeup(&client->dev)) + ret = tc35892_reg_write(tc35892, TC35892_CLKMODE, + TC35892_CLKMODE_MODCTL_SLEEP); + + return ret; +} + +static int tc35892_resume(struct device *dev) +{ + struct tc35892 *tc35892 = dev_get_drvdata(dev); + struct i2c_client *client = tc35892->i2c; + int ret = 0; + + /* enable the system into operation */ + if (!device_may_wakeup(&client->dev)) + ret = tc35892_reg_write(tc35892, TC35892_CLKMODE, + TC35892_CLKMODE_MODCTL_OPERATION); + + return ret; +} + + +static const struct dev_pm_ops tc35892_dev_pm_ops = { + .suspend = tc35892_suspend, + .resume = tc35892_resume, +}; +#endif + static const struct i2c_device_id tc35892_id[] = { { "tc35892", 24 }, { } @@ -323,6 +362,9 @@ MODULE_DEVICE_TABLE(i2c, tc35892_id); static struct i2c_driver tc35892_driver = { .driver.name = "tc35892", .driver.owner = THIS_MODULE, +#if CONFIG_PM + .driver.pm = &tc35892_dev_pm_ops, +#endif .probe = tc35892_probe, .remove = __devexit_p(tc35892_remove), .id_table = tc35892_id, -- cgit v1.2.3 From b211c1b66fcfb09b917a20cf7d58e883f76db149 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Mon, 15 Nov 2010 11:19:20 +0100 Subject: MFD: Correct a few #if to #ifdef Signed-off-by: Jonas Aaberg --- drivers/mfd/tc35892.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c index 9fd67e1e9a7..a149e7f62ea 100644 --- a/drivers/mfd/tc35892.c +++ b/drivers/mfd/tc35892.c @@ -317,7 +317,7 @@ static int __devexit tc35892_remove(struct i2c_client *client) return 0; } -#if CONFIG_PM +#ifdef CONFIG_PM static int tc35892_suspend(struct device *dev) { struct tc35892 *tc35892 = dev_get_drvdata(dev); @@ -362,7 +362,7 @@ MODULE_DEVICE_TABLE(i2c, tc35892_id); static struct i2c_driver tc35892_driver = { .driver.name = "tc35892", .driver.owner = THIS_MODULE, -#if CONFIG_PM +#ifdef CONFIG_PM .driver.pm = &tc35892_dev_pm_ops, #endif .probe = tc35892_probe, -- cgit v1.2.3 From 2a16d6b4845099c7791f1b77dc6653063dbf825f Mon Sep 17 00:00:00 2001 From: Sundar Iyer Date: Tue, 16 Nov 2010 18:18:34 +0530 Subject: mfd/tc35892: fix random interrupt misses On the TC35892, a random delayed interrupt clear (GPIO IC) write locks up the child interrupts. In such a case, the original interrupt is active and not yet acknowledged. Re-check the IRQST bit for any pending interrupts and handle those. ST-Ericsson ID: 277560 Change-Id: Idce53fd1214f9ffc255b5662abb8eb030d43f0f5 Signed-off-by: Sundar Iyer --- drivers/mfd/tc35892.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c index a149e7f62ea..c470917e288 100644 --- a/drivers/mfd/tc35892.c +++ b/drivers/mfd/tc35892.c @@ -145,6 +145,7 @@ static irqreturn_t tc35892_irq(int irq, void *data) struct tc35892 *tc35892 = data; int status; +again: status = tc35892_reg_read(tc35892, TC35892_IRQST); if (status < 0) return IRQ_NONE; @@ -159,9 +160,12 @@ static irqreturn_t tc35892_irq(int irq, void *data) /* * A dummy read or write (to any register) appears to be necessary to * have the last interrupt clear (for example, GPIO IC write) take - * effect. + * effect. In such a case, recheck for any interrupt which is still + * pending. */ - tc35892_reg_read(tc35892, TC35892_IRQST); + status = tc35892_reg_read(tc35892, TC35892_IRQST); + if (status) + goto again; return IRQ_HANDLED; } -- cgit v1.2.3 From 4e1f07655034220f9a3ea7405c56726a662268a6 Mon Sep 17 00:00:00 2001 From: Sundar Iyer Date: Wed, 24 Nov 2010 12:31:55 +0530 Subject: mfd/tc35892: undo gpio module reset during chip init Skip putting the GPIO module into a reset during the chip init. This makes sure to preserve any existing GPIO configurations done by pre-kernel boot code. ST-Ericsson ID: ER277334 Change-Id: Ia6727a1f268db0576f5eb40b5dc5c30f6abecba7 Signed-off-by: Sundar Iyer --- drivers/mfd/tc35892.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c index c470917e288..33d44bbc731 100644 --- a/drivers/mfd/tc35892.c +++ b/drivers/mfd/tc35892.c @@ -234,12 +234,15 @@ static int tc35892_chip_init(struct tc35892 *tc35892) dev_info(tc35892->dev, "manufacturer: %#x, version: %#x\n", manf, ver); - /* Put everything except the IRQ module into reset */ + /* + * Put everything except the IRQ module into reset; + * also spare the GPIO module for any pin initialization + * done during pre-kernel boot + */ ret = tc35892_reg_write(tc35892, TC35892_RSTCTRL, TC35892_RSTCTRL_TIMRST | TC35892_RSTCTRL_ROTRST - | TC35892_RSTCTRL_KBDRST - | TC35892_RSTCTRL_GPIRST); + | TC35892_RSTCTRL_KBDRST); if (ret < 0) return ret; -- cgit v1.2.3 From 8a905091029a9b9a70b5e868e7f647f17aba6d7c Mon Sep 17 00:00:00 2001 From: David Paris Date: Fri, 4 Mar 2011 14:57:08 +0100 Subject: MFD: tc35892: Optimize power save settings for suspend Compared to HSI GPIO V3.1, register 0x82 is set with a different value 0x82 = 0xE (HSI GPIO V3.1) 0x82 = 0xF (on android) In case of android, it seems that GPIO reset should be applied to reach 13uA of consumption on TC35892 when AP in DeepSleep Change-Id: I64eaa4304fcd9f71546cadba0aa703c91b6075df Signed-off-by: David Paris Signed-off-by: Jonas Aaberg --- drivers/mfd/tc35892.c | 131 ++++++++++++++++++++++++++++++++++++++++---- include/linux/mfd/tc35892.h | 60 +++++++++++--------- 2 files changed, 155 insertions(+), 36 deletions(-) diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c index 33d44bbc731..c997a4b67aa 100644 --- a/drivers/mfd/tc35892.c +++ b/drivers/mfd/tc35892.c @@ -325,17 +325,112 @@ static int __devexit tc35892_remove(struct i2c_client *client) } #ifdef CONFIG_PM + +static u32 sleep_regs[] = { + TC35892_IOPC0_L, + TC35892_IOPC0_H, + TC35892_IOPC1_L, + TC35892_IOPC1_H, + TC35892_IOPC2_L, + TC35892_IOPC2_H, + TC35892_DRIVE0_L, + TC35892_DRIVE0_H, + TC35892_DRIVE1_L, + TC35892_DRIVE1_H, + TC35892_DRIVE2_L, + TC35892_DRIVE2_H, + TC35892_DRIVE3, + TC35892_GPIODATA0, + TC35892_GPIOMASK0, + TC35892_GPIODATA1, + TC35892_GPIOMASK1, + TC35892_GPIODATA2, + TC35892_GPIOMASK2, + TC35892_GPIODIR0, + TC35892_GPIODIR1, + TC35892_GPIODIR2, + TC35892_GPIOIE0, + TC35892_GPIOIE1, + TC35892_GPIOIE2, + TC35892_RSTCTRL, + TC35892_CLKCFG, +}; + +static u8 sleep_regs_val[] = { + 0x00, /* TC35892_IOPC0_L */ + 0x00, /* TC35892_IOPC0_H */ + 0x00, /* TC35892_IOPC1_L */ + 0x00, /* TC35892_IOPC1_H */ + 0x00, /* TC35892_IOPC2_L */ + 0x00, /* TC35892_IOPC2_H */ + 0xff, /* TC35892_DRIVE0_L */ + 0xff, /* TC35892_DRIVE0_H */ + 0xff, /* TC35892_DRIVE1_L */ + 0xff, /* TC35892_DRIVE1_H */ + 0xff, /* TC35892_DRIVE2_L */ + 0xff, /* TC35892_DRIVE2_H */ + 0x0f, /* TC35892_DRIVE3 */ + 0x80, /* TC35892_GPIODATA0 */ + 0x80, /* TC35892_GPIOMASK0 */ + 0x80, /* TC35892_GPIODATA1 */ + 0x80, /* TC35892_GPIOMASK1 */ + 0x06, /* TC35892_GPIODATA2 */ + 0x06, /* TC35892_GPIOMASK2 */ + 0xf0, /* TC35892_GPIODIR0 */ + 0xe0, /* TC35892_GPIODIR1 */ + 0xee, /* TC35892_GPIODIR2 */ + 0x0f, /* TC35892_GPIOIE0 */ + 0x1f, /* TC35892_GPIOIE1 */ + 0x11, /* TC35892_GPIOIE2 */ + 0x0f, /* TC35892_RSTCTRL */ + 0xb0 /* TC35892_CLKCFG */ + +}; + +static u8 sleep_regs_backup[ARRAY_SIZE(sleep_regs)]; + static int tc35892_suspend(struct device *dev) { struct tc35892 *tc35892 = dev_get_drvdata(dev); struct i2c_client *client = tc35892->i2c; int ret = 0; - - /* put the system to sleep mode */ - if (!device_may_wakeup(&client->dev)) - ret = tc35892_reg_write(tc35892, TC35892_CLKMODE, - TC35892_CLKMODE_MODCTL_SLEEP); - + int i, j; + int val; + + /* Put the system to sleep mode */ + if (!device_may_wakeup(&client->dev)) { + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + val = tc35892_reg_read(tc35892, + sleep_regs[i]); + if (val < 0) + goto out; + + sleep_regs_backup[i] = (u8) (val & 0xff); + } + + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + ret = tc35892_reg_write(tc35892, + sleep_regs[i], + sleep_regs_val[i]); + if (ret < 0) + goto fail; + + } + + ret = tc35892_reg_write(tc35892, + TC35892_CLKMODE, + TC35892_CLKMODE_MODCTL_SLEEP); + } +out: + return ret; +fail: + for (j = 0; j <= i; j++) { + ret = tc35892_reg_write(tc35892, + sleep_regs[i], + sleep_regs_backup[i]); + if (ret < 0) + break; + } return ret; } @@ -344,16 +439,30 @@ static int tc35892_resume(struct device *dev) struct tc35892 *tc35892 = dev_get_drvdata(dev); struct i2c_client *client = tc35892->i2c; int ret = 0; + int i; - /* enable the system into operation */ + /* Enable the system into operation */ if (!device_may_wakeup(&client->dev)) - ret = tc35892_reg_write(tc35892, TC35892_CLKMODE, - TC35892_CLKMODE_MODCTL_OPERATION); - + { + ret = tc35892_reg_write(tc35892, + TC35892_CLKMODE, + TC35892_CLKMODE_MODCTL_OPERATION); + if (ret < 0) + goto out; + + for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + ret = tc35892_reg_write(tc35892, + sleep_regs[i], + sleep_regs_backup[i]); + /* Not much to do here if we fail */ + if (ret < 0) + break; + } + } +out: return ret; } - static const struct dev_pm_ops tc35892_dev_pm_ops = { .suspend = tc35892_suspend, .resume = tc35892_resume, diff --git a/include/linux/mfd/tc35892.h b/include/linux/mfd/tc35892.h index e47f770d306..41b572c1f8e 100644 --- a/include/linux/mfd/tc35892.h +++ b/include/linux/mfd/tc35892.h @@ -15,21 +15,43 @@ #define TC35892_RSTCTRL_KBDRST (1 << 1) #define TC35892_RSTCTRL_GPIRST (1 << 0) -#define TC35892_IRQST 0x91 -#define TC35892_MANFCODE_MAGIC 0x03 #define TC35892_MANFCODE 0x80 +#define TC35892_MANFCODE_MAGIC 0x03 #define TC35892_VERSION 0x81 -#define TC35892_IOCFG 0xA7 - +#define TC35892_RSTCTRL 0x82 +#define TC35892_EXTRSTN 0x83 +#define TC35892_RSTINTCLR 0x84 #define TC35892_CLKMODE 0x88 #define TC35892_CLKCFG 0x89 #define TC35892_CLKEN 0x8A +#define TC35892_IRQST 0x91 -#define TC35892_RSTCTRL 0x82 -#define TC35892_EXTRSTN 0x83 -#define TC35892_RSTINTCLR 0x84 +#define TC35892_DRIVE0_L 0xA0 +#define TC35892_DRIVE0_H 0xA1 +#define TC35892_DRIVE1_L 0xA2 +#define TC35892_DRIVE1_H 0xA3 +#define TC35892_DRIVE2_L 0xA4 +#define TC35892_DRIVE2_H 0XA5 +#define TC35892_DRIVE3 0xA6 +#define TC35892_IOCFG 0xA7 + +#define TC35892_IOPC0_L 0xAA +#define TC35892_IOPC0_H 0xAB +#define TC35892_IOPC1_L 0xAC +#define TC35892_IOPC1_H 0xAD +#define TC35892_IOPC2_L 0xAE +#define TC35892_IOPC2_H 0xAF +#define TC35892_GPIODATA0 0xC0 +#define TC35892_GPIOMASK0 0xC1 +#define TC35892_GPIODATA1 0xC2 +#define TC35892_GPIOMASK1 0xC3 +#define TC35892_GPIODATA2 0xC4 +#define TC35892_GPIOMASK2 0xC5 +#define TC35892_GPIODIR0 0xC6 +#define TC35892_GPIODIR1 0xC7 +#define TC35892_GPIODIR2 0xC8 #define TC35892_GPIOIS0 0xC9 #define TC35892_GPIOIS1 0xCA #define TC35892_GPIOIS2 0xCB @@ -51,17 +73,12 @@ #define TC35892_GPIOIC0 0xDC #define TC35892_GPIOIC1 0xDD #define TC35892_GPIOIC2 0xDE - -#define TC35892_GPIODATA0 0xC0 -#define TC35892_GPIOMASK0 0xc1 -#define TC35892_GPIODATA1 0xC2 -#define TC35892_GPIOMASK1 0xc3 -#define TC35892_GPIODATA2 0xC4 -#define TC35892_GPIOMASK2 0xC5 - -#define TC35892_GPIODIR0 0xC6 -#define TC35892_GPIODIR1 0xC7 -#define TC35892_GPIODIR2 0xC8 +#define TC35892_GPIOODM0 0xE0 +#define TC35892_GPIOODE0 0xE1 +#define TC35892_GPIOODM1 0xE2 +#define TC35892_GPIOODE1 0xE3 +#define TC35892_GPIOODM2 0xE4 +#define TC35892_GPIOODE2 0xE5 #define TC35892_GPIOSYNC0 0xE6 #define TC35892_GPIOSYNC1 0xE7 @@ -71,13 +88,6 @@ #define TC35892_GPIOWAKE1 0xEA #define TC35892_GPIOWAKE2 0xEB -#define TC35892_GPIOODM0 0xE0 -#define TC35892_GPIOODE0 0xE1 -#define TC35892_GPIOODM1 0xE2 -#define TC35892_GPIOODE1 0xE3 -#define TC35892_GPIOODM2 0xE4 -#define TC35892_GPIOODE2 0xE5 - #define TC35892_INT_GPIIRQ 0 #define TC35892_INT_TI0IRQ 1 #define TC35892_INT_TI1IRQ 2 -- cgit v1.2.3 From e77e9bf248195083b52f49c1480635cd2a15f789 Mon Sep 17 00:00:00 2001 From: Martin Persson Date: Mon, 28 Mar 2011 11:50:24 +0200 Subject: MFD: tc35892: Correct optimized power settings Change-Id: I83fd9379c037fe3c949aa5d01a99423d2fc555b4 Signed-off-by: Martin Persson --- drivers/mfd/tc35892.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/tc35892.c b/drivers/mfd/tc35892.c index c997a4b67aa..91211f29623 100644 --- a/drivers/mfd/tc35892.c +++ b/drivers/mfd/tc35892.c @@ -450,7 +450,7 @@ static int tc35892_resume(struct device *dev) if (ret < 0) goto out; - for (i = 0; i < ARRAY_SIZE(sleep_regs); i++) { + for (i = ARRAY_SIZE(sleep_regs) - 1; i >= 0; i--) { ret = tc35892_reg_write(tc35892, sleep_regs[i], sleep_regs_backup[i]); -- cgit v1.2.3 From 387a09f254f1feb67b295ab511fac5bc4775ccd8 Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Wed, 17 Aug 2011 21:29:59 +0530 Subject: ARM: 6373/1: tc35892-gpio: add setup/remove callbacks For board-specific initialization. Change-Id: I8a13382bf91608b9edf2e712cff157b14e20848a Cc: Samuel Ortiz Cc: linux-kernel@vger.kernel.org Acked-by: Linus Walleij Signed-off-by: Rabin Vincent Signed-off-by: Russell King Signed-off-by: Virupax Sadashivpetimath --- include/linux/mfd/tc35892.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/linux/mfd/tc35892.h b/include/linux/mfd/tc35892.h index 41b572c1f8e..8c5385c2191 100644 --- a/include/linux/mfd/tc35892.h +++ b/include/linux/mfd/tc35892.h @@ -121,9 +121,13 @@ extern int tc35892_set_bits(struct tc35892 *tc35892, u8 reg, u8 mask, u8 val); * struct tc35892_gpio_platform_data - TC35892 GPIO platform data * @gpio_base: first gpio number assigned to TC35892. A maximum of * %TC35892_NR_GPIOS GPIOs will be allocated. + * @setup: callback for board-specific initialization + * @remove: callback for board-specific teardown */ struct tc35892_gpio_platform_data { int gpio_base; + void (*setup)(struct tc35892 *tc35892, unsigned gpio_base); + void (*remove)(struct tc35892 *tc35892, unsigned gpio_base); }; /** -- cgit v1.2.3 From 7c404ab0c508a5047f27507e29b5aa42935430bf Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Fri, 26 Aug 2011 14:33:56 +0530 Subject: ab8500_charger: fail properly if no otg transceiver Change-Id: I97c26a78d52ed5cf65e8d344b55ffce6cf22947e --- drivers/power/ab8500_charger.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index e9db45494fa..b0c0453878f 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -2473,6 +2473,7 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) di->otg = otg_get_transceiver(); if (!di->otg) { dev_err(di->dev, "failed to get otg transceiver\n"); + ret = -EINVAL; goto free_usb; } di->nb.notifier_call = ab8500_charger_usb_notifier_call; -- cgit v1.2.3 From d3cdc52d1283b5319fa907fbd6f3dcea2b4a550b Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Fri, 26 Aug 2011 16:19:48 +0530 Subject: u8500: enable PRCMU and disable 5500 in defconfig Change-Id: Ieffe86726e7efb682c58b00dbc7796875e2dfd44 --- arch/arm/configs/u8500_defconfig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 68318c7c527..54642da022d 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -20,21 +20,24 @@ CONFIG_MODULE_SRCVERSION_ALL=y # CONFIG_BLK_DEV_BSG is not set CONFIG_DEFAULT_DEADLINE=y CONFIG_ARCH_U8500=y -CONFIG_UX500_SOC_DB5500=y CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_HREFV60=y CONFIG_MACH_SNOWBALL=y -CONFIG_MACH_U5500=y CONFIG_UX500_PRCMU_TIMER=y +CONFIG_U8500_PRCMU=y +CONFIG_UX500_PRCMU_DEBUG=y +CONFIG_U8500_REGULATOR_DEBUG=y CONFIG_DB8500_MLOADER=y -CONFIG_U5500_MLOADER=y CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y +CONFIG_U8500_CPUIDLE_DEBUG=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_STANDBY=y CONFIG_UX500_SUSPEND_MEM=y +CONFIG_UX500_SUSPEND_DBG=y +CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y @@ -231,7 +234,6 @@ CONFIG_CG2900_AUDIO=y CONFIG_CG2900_TEST=y CONFIG_BT_CG2900=y CONFIG_U8500_MMIO=y -CONFIG_U5500_MMIO=y CONFIG_U8500_CM=y CONFIG_U8500_FLASH=y CONFIG_MODEM=y @@ -291,10 +293,8 @@ CONFIG_SYSCTL_SYSCALL_CHECK=y CONFIG_FUNCTION_TRACER=y CONFIG_DEBUG_USER=y CONFIG_KEYS=y -CONFIG_CRYPTO_ECB=y CONFIG_CRYPTO_MD5=m CONFIG_CRYPTO_SHA1=m -CONFIG_CRYPTO_AES=y CONFIG_CRYPTO_ARC4=y CONFIG_CRYPTO_TWOFISH=y # CONFIG_CRYPTO_ANSI_CPRNG is not set -- cgit v1.2.3 From d31217644089402d33169f9e12f259de3d46a4ba Mon Sep 17 00:00:00 2001 From: Fredrik Allansson Date: Fri, 15 Oct 2010 16:07:53 +0200 Subject: misc: dispdev: Migrate to kernel 3.0 apply below mcde mach-ux500 patches for kernel3.0 migration ce479ee [ANDROID] misc: dispdev: Add dispdev driver f499637 misc: dispdev: Enable dispdev in U5500 b07b4c [ANDROID] misc: dispdev: Disable UI overlay ST-Ericsson ID: 352334 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: NA Signed-off-by: Rajagopala V Change-Id: I4afae8f61b28754cb0380d44c9b2273c958572a9 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/29655 --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 54642da022d..7eb0de472c7 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -109,6 +109,7 @@ CONFIG_SENSORS_BH1780=y CONFIG_STE_TRACE_MODEM=y CONFIG_U8500_SIM_DETECT=y CONFIG_STM_TRACE=y +CONFIG_DISPDEV=y # CONFIG_STE_AUDIO_IO_DEV is not set CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y -- cgit v1.2.3 From fa6042244f7d6cc75d1080b842673ebfd0c6f71a Mon Sep 17 00:00:00 2001 From: Rajagopala V Date: Fri, 5 Aug 2011 16:52:41 +0530 Subject: hwmon: dbx500: Migrate to kernel3.0 Patch includes following commits u5500: prcmu: add irqs for db5500 temperature sensor dbx500: hwmon: temperature monitor support for DB5500 ST-Ericsson ID: 352334 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: NA Signed-off-by: Rajagopala V Change-Id: I4df816759c0f8d039b88788e35372c05990c11ce Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/29681 --- arch/arm/configs/u8500_defconfig | 2 +- drivers/hwmon/Kconfig | 12 +- drivers/hwmon/Makefile | 2 +- drivers/hwmon/db8500.c | 450 -------------------------------------- drivers/hwmon/dbx500.c | 455 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 463 insertions(+), 458 deletions(-) delete mode 100755 drivers/hwmon/db8500.c create mode 100644 drivers/hwmon/dbx500.c diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 7eb0de472c7..f0f1eee893e 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -161,7 +161,7 @@ CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=y CONFIG_AB5500_BM=y CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL=y CONFIG_SENSORS_AB8500=y -CONFIG_SENSORS_DB8500=y +CONFIG_SENSORS_DBX500=y CONFIG_SENSORS_LSM303DLH=y CONFIG_SENSORS_L3G4200D=y CONFIG_WATCHDOG=y diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 0bf9eac1633..dc662015c13 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -64,17 +64,17 @@ config SENSORS_AB5500 This driver can also be built as a module. If so, the module will be called abx500-temp. -config SENSORS_DB8500 - tristate "DB8500 thermal monitoring" - depends on UX500_SOC_DB8500 +config SENSORS_DBX500 + tristate "DBX500 thermal monitoring" + depends on U8500_PRCMU || U5500_PRCMU default n help If you say yes here you get support for the thermal sensor part - of the DB8500 chip. The driver includes thermal management for - DB8500 die. + of the DBX500 chip. The driver includes thermal management for + DBX500 die. This driver can also be built as a module. If so, the module - will be called db8500_temp. + will be called dbx500_temp. config SENSORS_ABITUGURU diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 7ce440cdd6c..974e23cf262 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -21,7 +21,7 @@ obj-$(CONFIG_SENSORS_W83791D) += w83791d.o obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o obj-$(CONFIG_SENSORS_AB5500) += abx500.o ab5500.o -obj-$(CONFIG_SENSORS_DB8500) += db8500.o +obj-$(CONFIG_SENSORS_DBX500) += dbx500.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o obj-$(CONFIG_SENSORS_AD7414) += ad7414.o diff --git a/drivers/hwmon/db8500.c b/drivers/hwmon/db8500.c deleted file mode 100755 index c08ab033ada..00000000000 --- a/drivers/hwmon/db8500.c +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2010. All rights reserved. - * This code is ST-Ericsson proprietary and confidential. - * Any use of the code for whatever purpose is subject to - * specific written permission of ST-Ericsson SA. - * - * Author: WenHai Fang for - * ST-Ericsson. - * License terms: GNU Gereral Public License (GPL) version 2 - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * If DB8500 warm interrupt is set, user space will be notified. - * If user space doesn't shut down the platform within this time - * frame, this driver will. Time unit is ms. - */ -#define DEFAULT_POWER_OFF_DELAY 10000 - -/* - * Default measure period to 0xFF x cycle32k - */ -#define DEFAULT_MEASURE_TIME 0xFF - -/* This driver monitors DB thermal*/ -#define NUM_SENSORS 1 - -struct db8500_temp { - struct platform_device *pdev; - struct device *hwmon_dev; - unsigned char min[NUM_SENSORS]; - unsigned char max[NUM_SENSORS]; - unsigned char crit[NUM_SENSORS]; - unsigned char min_alarm[NUM_SENSORS]; - unsigned char max_alarm[NUM_SENSORS]; - unsigned short measure_time; - struct delayed_work power_off_work; - struct mutex lock; - /* Delay (ms) before power off */ - unsigned long power_off_delay; -}; - -static void thermal_power_off(struct work_struct *work) -{ - struct db8500_temp *data = container_of(work, struct db8500_temp, - power_off_work.work); - - dev_warn(&data->pdev->dev, "Power off due to DB8500 thermal warning\n"); - pm_power_off(); -} - -static ssize_t set_temp_power_off_delay(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - int res; - unsigned long delay_in_s; - struct db8500_temp *data = dev_get_drvdata(dev); - - res = strict_strtoul(buf, 10, &delay_in_s); - if (res < 0) { - dev_warn(&data->pdev->dev, "Set power_off_delay wrong\n"); - return res; - } - - mutex_lock(&data->lock); - data->power_off_delay = delay_in_s * 1000; - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_temp_power_off_delay(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct db8500_temp *data = dev_get_drvdata(dev); - /* return time in s, not ms */ - return sprintf(buf, "%lu\n", (data->power_off_delay) / 1000); -} - -/* HWMON sysfs interface */ -static ssize_t show_name(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - return sprintf(buf, "db8500\n"); -} - -static ssize_t show_label(struct device *dev, struct device_attribute *devattr, - char *buf) -{ - return sprintf(buf, "db8500\n"); -} - -/* set functions (RW nodes) */ -static ssize_t set_min(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct db8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int res = strict_strtoul(buf, 10, &val); - if (res < 0) - return res; - - mutex_lock(&data->lock); - val &= 0xFF; - if (val > data->max[attr->index - 1]) - val = data->max[attr->index - 1]; - - data->min[attr->index - 1] = val; - - (void)prcmu_config_hotmon(data->min[attr->index - 1], - data->max[attr->index - 1]); - mutex_unlock(&data->lock); - return count; -} - -static ssize_t set_max(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct db8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int res = strict_strtoul(buf, 10, &val); - if (res < 0) - return res; - - mutex_lock(&data->lock); - val &= 0xFF; - if (val < data->min[attr->index - 1]) - val = data->min[attr->index - 1]; - - data->max[attr->index - 1] = val; - - (void)prcmu_config_hotmon(data->min[attr->index - 1], - data->max[attr->index - 1]); - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t set_crit(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct db8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int res = strict_strtoul(buf, 10, &val); - if (res < 0) - return res; - - mutex_lock(&data->lock); - val &= 0xFF; - data->crit[attr->index - 1] = val; - (void)prcmu_config_hotdog(data->crit[attr->index - 1]); - mutex_unlock(&data->lock); - - return count; -} - -/* start/stop temperature measurement */ -static ssize_t start_temp(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct db8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int res = strict_strtoul(buf, 10, &val); - if (res < 0) - return res; - - mutex_lock(&data->lock); - data->measure_time = val & 0xFFFF; - data->min_alarm[attr->index - 1] = 0; - data->max_alarm[attr->index - 1] = 0; - mutex_unlock(&data->lock); - - (void)prcmu_start_temp_sense(data->measure_time); - dev_dbg(&data->pdev->dev, "DB8500 thermal start measurement\n"); - return count; -} - -static ssize_t stop_temp(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct db8500_temp *data = dev_get_drvdata(dev); - int res = strict_strtoul(buf, 10, &val); - if (res < 0) - return res; - - (void)prcmu_stop_temp_sense(); - dev_dbg(&data->pdev->dev, "DB8500 thermal stop measurement\n"); - - return count; -} - -/* - * show functions (RO nodes) - * Notice that min/max/crit refer to degrees - */ -static ssize_t show_min(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct db8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%d\n", data->min[attr->index - 1]); -} - -static ssize_t show_max(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct db8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%d\n", data->max[attr->index - 1]); -} - -static ssize_t show_crit(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct db8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%d\n", data->crit[attr->index - 1]); -} - -/* Alarms */ -static ssize_t show_min_alarm(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct db8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%d\n", data->min_alarm[attr->index - 1]); -} - -static ssize_t show_max_alarm(struct device *dev, - struct device_attribute *devattr, char *buf) -{ - struct db8500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - /* hwmon attr index starts at 1, thus "attr->index-1" below */ - return sprintf(buf, "%d\n", data->max_alarm[attr->index - 1]); -} - -/*These node are not included in the kernel hwmon sysfs interface */ -static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, - show_temp_power_off_delay, - set_temp_power_off_delay, 0); - -/* Chip name, required by hwmon*/ -static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); -static SENSOR_DEVICE_ATTR(temp1_start, S_IWUSR, NULL, start_temp, 1); -static SENSOR_DEVICE_ATTR(temp1_stop, S_IWUSR, NULL, stop_temp, 1); -static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); -static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); -static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, - show_crit, set_crit, 1); -static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 1); -static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); -static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); - -static struct attribute *db8500_temp_attributes[] = { - &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, - &sensor_dev_attr_name.dev_attr.attr, - &sensor_dev_attr_temp1_start.dev_attr.attr, - &sensor_dev_attr_temp1_stop.dev_attr.attr, - &sensor_dev_attr_temp1_min.dev_attr.attr, - &sensor_dev_attr_temp1_max.dev_attr.attr, - &sensor_dev_attr_temp1_crit.dev_attr.attr, - &sensor_dev_attr_temp1_label.dev_attr.attr, - &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, - &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, - NULL -}; - -static const struct attribute_group db8500_temp_group = { - .attrs = db8500_temp_attributes, -}; - -static irqreturn_t prcmu_hotmon_low_irq_handler(int irq, void *irq_data) -{ - struct platform_device *pdev = irq_data; - struct db8500_temp *data = platform_get_drvdata(pdev); - - mutex_lock(&data->lock); - data->min_alarm[0] = 1; - mutex_unlock(&data->lock); - - sysfs_notify(&pdev->dev.kobj, NULL, "temp1_min_alarm"); - dev_dbg(&pdev->dev, "DB8500 thermal low warning\n"); - return IRQ_HANDLED; -} - -static irqreturn_t prcmu_hotmon_high_irq_handler(int irq, void *irq_data) -{ - unsigned long delay_in_jiffies; - struct platform_device *pdev = irq_data; - struct db8500_temp *data = platform_get_drvdata(pdev); - - mutex_lock(&data->lock); - data->max_alarm[0] = 1; - mutex_unlock(&data->lock); - - sysfs_notify(&pdev->dev.kobj, NULL, "temp1_max_alarm"); - dev_dbg(&pdev->dev, "DB8500 thermal warning, power off in %lu s\n", - (data->power_off_delay) / 1000); - delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); - schedule_delayed_work(&data->power_off_work, delay_in_jiffies); - return IRQ_HANDLED; -} - -static int __devinit db8500_temp_probe(struct platform_device *pdev) -{ - struct db8500_temp *data; - int err = 0, i; - int irq; - - dev_dbg(&pdev->dev, "db8500_temp: Function db8500_temp_probe.\n"); - - data = kzalloc(sizeof(struct db8500_temp), GFP_KERNEL); - if (!data) - return -ENOMEM; - - irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); - if (irq < 0) { - dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed\n"); - goto exit; - } - - err = request_threaded_irq(irq, NULL, prcmu_hotmon_low_irq_handler, - IRQF_NO_SUSPEND, "db8500_temp_low", pdev); - if (err < 0) { - dev_err(&pdev->dev, "db8500: Failed allocate HOTMON_LOW.\n"); - goto exit; - } else { - dev_dbg(&pdev->dev, "db8500: Succeed allocate HOTMON_LOW.\n"); - } - - irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); - if (irq < 0) { - dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed\n"); - goto exit; - } - - err = request_threaded_irq(irq, NULL, prcmu_hotmon_high_irq_handler, - IRQF_NO_SUSPEND, "db8500_temp_high", pdev); - if (err < 0) { - dev_err(&pdev->dev, "db8500: Failed allocate HOTMON_HIGH.\n"); - goto exit; - } else { - dev_dbg(&pdev->dev, "db8500: Succeed allocate HOTMON_HIGH.\n"); - } - - data->hwmon_dev = hwmon_device_register(&pdev->dev); - if (IS_ERR(data->hwmon_dev)) { - err = PTR_ERR(data->hwmon_dev); - dev_err(&pdev->dev, "Class registration failed (%d)\n", err); - goto exit; - } - - for (i = 0; i < NUM_SENSORS; i++) { - data->min[i] = 0; - data->max[i] = 0xFF; - data->crit[i] = 0xFF; - data->min_alarm[i] = 0; - data->max_alarm[i] = 0; - } - - mutex_init(&data->lock); - INIT_DELAYED_WORK(&data->power_off_work, thermal_power_off); - - data->pdev = pdev; - data->power_off_delay = DEFAULT_POWER_OFF_DELAY; - data->measure_time = DEFAULT_MEASURE_TIME; - - platform_set_drvdata(pdev, data); - - err = sysfs_create_group(&pdev->dev.kobj, &db8500_temp_group); - if (err < 0) { - dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); - goto exit_platform_data; - } - - return 0; - -exit_platform_data: - platform_set_drvdata(pdev, NULL); -exit: - kfree(data); - return err; -} - -static int __devexit db8500_temp_remove(struct platform_device *pdev) -{ - struct db8500_temp *data = platform_get_drvdata(pdev); - - hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&pdev->dev.kobj, &db8500_temp_group); - platform_set_drvdata(pdev, NULL); - kfree(data); - return 0; -} - -/* No action required in suspend/resume, thus the lack of functions */ -static struct platform_driver db8500_temp_driver = { - .driver = { - .owner = THIS_MODULE, - .name = "db8500_temp", - }, - .probe = db8500_temp_probe, - .remove = __devexit_p(db8500_temp_remove), -}; - -static int __init db8500_temp_init(void) -{ - return platform_driver_register(&db8500_temp_driver); -} - -static void __exit db8500_temp_exit(void) -{ - platform_driver_unregister(&db8500_temp_driver); -} - -MODULE_AUTHOR("WenHai Fang "); -MODULE_DESCRIPTION("DB8500 temperature driver"); -MODULE_LICENSE("GPL"); - -module_init(db8500_temp_init) -module_exit(db8500_temp_exit) diff --git a/drivers/hwmon/dbx500.c b/drivers/hwmon/dbx500.c new file mode 100644 index 00000000000..a26e08b3707 --- /dev/null +++ b/drivers/hwmon/dbx500.c @@ -0,0 +1,455 @@ +/* + * Copyright (C) ST-Ericsson SA 2010. All rights reserved. + * This code is ST-Ericsson proprietary and confidential. + * Any use of the code for whatever purpose is subject to + * specific written permission of ST-Ericsson SA. + * + * Author: WenHai Fang for + * ST-Ericsson. + * License terms: GNU Gereral Public License (GPL) version 2 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * If DBX500 warm interrupt is set, user space will be notified. + * If user space doesn't shut down the platform within this time + * frame, this driver will. Time unit is ms. + */ +#define DEFAULT_POWER_OFF_DELAY 10000 + +/* + * Default measure period to 0xFF x cycle32k + */ +#define DEFAULT_MEASURE_TIME 0xFF + +/* This driver monitors DB thermal*/ +#define NUM_SENSORS 1 + +struct dbx500_temp { + struct platform_device *pdev; + struct device *hwmon_dev; + unsigned char min[NUM_SENSORS]; + unsigned char max[NUM_SENSORS]; + unsigned char crit[NUM_SENSORS]; + unsigned char min_alarm[NUM_SENSORS]; + unsigned char max_alarm[NUM_SENSORS]; + unsigned short measure_time; + struct delayed_work power_off_work; + struct mutex lock; + /* Delay (ms) before power off */ + unsigned long power_off_delay; +}; + +static void thermal_power_off(struct work_struct *work) +{ + struct dbx500_temp *data = container_of(work, struct dbx500_temp, + power_off_work.work); + + dev_warn(&data->pdev->dev, "Power off due to DBX500 thermal warning\n"); + pm_power_off(); +} + +static ssize_t set_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int res; + unsigned long delay_in_s; + struct dbx500_temp *data = dev_get_drvdata(dev); + + res = strict_strtoul(buf, 10, &delay_in_s); + if (res < 0) { + dev_warn(&data->pdev->dev, "Set power_off_delay wrong\n"); + return res; + } + + mutex_lock(&data->lock); + data->power_off_delay = delay_in_s * 1000; + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_temp_power_off_delay(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + struct dbx500_temp *data = dev_get_drvdata(dev); + /* return time in s, not ms */ + return sprintf(buf, "%lu\n", (data->power_off_delay) / 1000); +} + +/* HWMON sysfs interface */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return sprintf(buf, "dbx500\n"); +} + +static ssize_t show_label(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + return show_name(dev, devattr, buf); +} + +/* set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val &= 0xFF; + if (val > data->max[attr->index - 1]) + val = data->max[attr->index - 1]; + + data->min[attr->index - 1] = val; + + (void)prcmu_config_hotmon(data->min[attr->index - 1], + data->max[attr->index - 1]); + mutex_unlock(&data->lock); + return count; +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val &= 0xFF; + if (val < data->min[attr->index - 1]) + val = data->min[attr->index - 1]; + + data->max[attr->index - 1] = val; + + (void)prcmu_config_hotmon(data->min[attr->index - 1], + data->max[attr->index - 1]); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_crit(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + val &= 0xFF; + data->crit[attr->index - 1] = val; + (void)prcmu_config_hotdog(data->crit[attr->index - 1]); + mutex_unlock(&data->lock); + + return count; +} + +/* start/stop temperature measurement */ +static ssize_t start_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + mutex_lock(&data->lock); + data->measure_time = val & 0xFFFF; + data->min_alarm[attr->index - 1] = 0; + data->max_alarm[attr->index - 1] = 0; + mutex_unlock(&data->lock); + + (void)prcmu_start_temp_sense(data->measure_time); + dev_dbg(&data->pdev->dev, "DBX500 thermal start measurement\n"); + + return count; +} + +static ssize_t stop_temp(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct dbx500_temp *data = dev_get_drvdata(dev); + int res = strict_strtoul(buf, 10, &val); + if (res < 0) + return res; + + (void)prcmu_stop_temp_sense(); + dev_dbg(&data->pdev->dev, "DBX500 thermal stop measurement\n"); + + return count; +} + +/* + * show functions (RO nodes) + * Notice that min/max/crit refer to degrees + */ +static ssize_t show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->min[attr->index - 1]); +} + +static ssize_t show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->max[attr->index - 1]); +} + +static ssize_t show_crit(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->crit[attr->index - 1]); +} + +/* Alarms */ +static ssize_t show_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->min_alarm[attr->index - 1]); +} + +static ssize_t show_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct dbx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + /* hwmon attr index starts at 1, thus "attr->index-1" below */ + return sprintf(buf, "%d\n", data->max_alarm[attr->index - 1]); +} + +/*These node are not included in the kernel hwmon sysfs interface */ +static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, + show_temp_power_off_delay, + set_temp_power_off_delay, 0); + +/* Chip name, required by hwmon*/ +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_start, S_IWUSR, NULL, start_temp, 1); +static SENSOR_DEVICE_ATTR(temp1_stop, S_IWUSR, NULL, stop_temp, 1); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, + show_crit, set_crit, 1); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); + +static struct attribute *dbx500_temp_attributes[] = { + &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, + &sensor_dev_attr_name.dev_attr.attr, + &sensor_dev_attr_temp1_start.dev_attr.attr, + &sensor_dev_attr_temp1_stop.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group dbx500_temp_group = { + .attrs = dbx500_temp_attributes, +}; + +static irqreturn_t prcmu_hotmon_low_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + struct dbx500_temp *data = platform_get_drvdata(pdev); + + mutex_lock(&data->lock); + data->min_alarm[0] = 1; + mutex_unlock(&data->lock); + + sysfs_notify(&pdev->dev.kobj, NULL, "temp1_min_alarm"); + dev_dbg(&pdev->dev, "DBX500 thermal low warning\n"); + return IRQ_HANDLED; +} + +static irqreturn_t prcmu_hotmon_high_irq_handler(int irq, void *irq_data) +{ + unsigned long delay_in_jiffies; + struct platform_device *pdev = irq_data; + struct dbx500_temp *data = platform_get_drvdata(pdev); + + mutex_lock(&data->lock); + data->max_alarm[0] = 1; + mutex_unlock(&data->lock); + + sysfs_notify(&pdev->dev.kobj, NULL, "temp1_max_alarm"); + dev_dbg(&pdev->dev, "DBX500 thermal warning, power off in %lu s\n", + (data->power_off_delay) / 1000); + delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); + schedule_delayed_work(&data->power_off_work, delay_in_jiffies); + return IRQ_HANDLED; +} + +static int __devinit dbx500_temp_probe(struct platform_device *pdev) +{ + struct dbx500_temp *data; + int err = 0, i; + int irq; + + dev_dbg(&pdev->dev, "dbx500_temp: Function dbx500_temp_probe.\n"); + + data = kzalloc(sizeof(struct dbx500_temp), GFP_KERNEL); + if (!data) + return -ENOMEM; + + irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_LOW"); + if (irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_LOW failed\n"); + goto exit; + } + + err = request_threaded_irq(irq, NULL, + prcmu_hotmon_low_irq_handler, + IRQF_NO_SUSPEND, + "dbx500_temp_low", pdev); + if (err < 0) { + dev_err(&pdev->dev, "dbx500: Failed allocate HOTMON_LOW.\n"); + goto exit; + } else { + dev_dbg(&pdev->dev, "dbx500: Succeed allocate HOTMON_LOW.\n"); + } + + irq = platform_get_irq_byname(pdev, "IRQ_HOTMON_HIGH"); + if (irq < 0) { + dev_err(&pdev->dev, "Get IRQ_HOTMON_HIGH failed\n"); + goto exit; + } + + err = request_threaded_irq(irq, NULL, + prcmu_hotmon_high_irq_handler, + IRQF_NO_SUSPEND, + "dbx500_temp_high", pdev); + if (err < 0) { + dev_err(&pdev->dev, "dbx500: Failed allocate HOTMON_HIGH.\n"); + goto exit; + } else { + dev_dbg(&pdev->dev, "dbx500: Succeed allocate HOTMON_HIGH.\n"); + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit; + } + + for (i = 0; i < NUM_SENSORS; i++) { + data->min[i] = 0; + data->max[i] = 0xFF; + data->crit[i] = 0xFF; + data->min_alarm[i] = 0; + data->max_alarm[i] = 0; + } + + mutex_init(&data->lock); + INIT_DELAYED_WORK(&data->power_off_work, thermal_power_off); + + data->pdev = pdev; + data->power_off_delay = DEFAULT_POWER_OFF_DELAY; + data->measure_time = DEFAULT_MEASURE_TIME; + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &dbx500_temp_group); + if (err < 0) { + dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); + goto exit_platform_data; + } + + return 0; + +exit_platform_data: + platform_set_drvdata(pdev, NULL); +exit: + kfree(data); + return err; +} + +static int __devexit dbx500_temp_remove(struct platform_device *pdev) +{ + struct dbx500_temp *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &dbx500_temp_group); + platform_set_drvdata(pdev, NULL); + kfree(data); + return 0; +} + +/* No action required in suspend/resume, thus the lack of functions */ +static struct platform_driver dbx500_temp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "dbx500_temp", + }, + .probe = dbx500_temp_probe, + .remove = __devexit_p(dbx500_temp_remove), +}; + +static int __init dbx500_temp_init(void) +{ + return platform_driver_register(&dbx500_temp_driver); +} + +static void __exit dbx500_temp_exit(void) +{ + platform_driver_unregister(&dbx500_temp_driver); +} + +MODULE_AUTHOR("WenHai Fang "); +MODULE_DESCRIPTION("DBX500 temperature driver"); +MODULE_LICENSE("GPL"); + +module_init(dbx500_temp_init) +module_exit(dbx500_temp_exit) -- cgit v1.2.3 From 663f09df8e674811ed49f075dcdf4c691dfb798d Mon Sep 17 00:00:00 2001 From: Magnus Wendt Date: Thu, 19 May 2011 16:59:12 +0200 Subject: ux500: configs/: Include mali in ux500_defconfigs Include mali device driver support in the ux500_defconfigs. If the mali400ko git is not checked out in the drivers/gpu/imali/mali400ko folder the mali driver will be excluded from the build. ST-Ericsson ID: CR335265, CR341392 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I5ad86540ba0516d0564b8bf95a66d479a61d1882 Signed-off-by: Magnus Wendt Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/29946 Reviewed-by: Robert FEKETE Tested-by: Robert FEKETE Reviewed-by: Ushit KUMAR Tested-by: Ushit KUMAR --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index f0f1eee893e..9635928d368 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -180,6 +180,7 @@ CONFIG_FB=y CONFIG_FB_MCDE=y CONFIG_AV8100_HWTRIG_I2SDAT3=y CONFIG_FB_B2R2=y +CONFIG_GPU_MALI=y CONFIG_B2R2_PLUG_CONF=y CONFIG_SOUND=y CONFIG_SND=y -- cgit v1.2.3 From 0f2bb430669680806a7243155a70a9ace788b5ab Mon Sep 17 00:00:00 2001 From: Naveen Kumar Gaddipati Date: Mon, 5 Sep 2011 19:09:29 +0530 Subject: mach-ux500:Video default config kernel 3.0 migration Video default config kernel 3.0 migrate for MCDE, B2R2 and av8100 drivers. ST-Ericsson ID: 352334 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: NA Signed-off-by: Naveen Kumar Gaddipati Change-Id: I67e7466b79cfeb593ba9113fd75e60bdaf3cdfbb Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/30129 --- arch/arm/configs/u8500_defconfig | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 9635928d368..3f0051220c7 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -28,7 +28,11 @@ CONFIG_U8500_PRCMU=y CONFIG_UX500_PRCMU_DEBUG=y CONFIG_U8500_REGULATOR_DEBUG=y CONFIG_DB8500_MLOADER=y +CONFIG_DISPLAY_GENERIC_PRIMARY=y +CONFIG_DISPLAY_GENERIC_DSI_PRIMARY=y CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y +CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_ROTATION_90=y +CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_ROTATION_ANGLE=90 CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y @@ -176,12 +180,28 @@ CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y # CONFIG_MEDIA_TUNER_CUSTOMISE is not set # CONFIG_VIDEO_CAPTURE_DRIVERS is not set +CONFIG_GPU_MALI=y +CONFIG_GPU_MALI_DEBUG=y CONFIG_FB=y +CONFIG_FB_SYS_FILLRECT=y +CONFIG_FB_SYS_COPYAREA=y +CONFIG_FB_SYS_IMAGEBLIT=y +CONFIG_FB_SYS_FOPS=y CONFIG_FB_MCDE=y -CONFIG_AV8100_HWTRIG_I2SDAT3=y +CONFIG_MCDE_DISPLAY_GENERIC_DSI=y +CONFIG_MCDE_DISPLAY_AV8100=y +CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE=y CONFIG_FB_B2R2=y -CONFIG_GPU_MALI=y CONFIG_B2R2_PLUG_CONF=y +CONFIG_B2R2_OPSIZE_64=y +CONFIG_B2R2_CHSIZE_128=y +CONFIG_B2R2_MGSIZE_128=y +CONFIG_B2R2_PGSIZE_256=y +CONFIG_B2R2_GENERIC=y +CONFIG_B2R2_GENERIC_FALLBACK=y +CONFIG_AV8100=y +CONFIG_AV8100_HWTRIG_I2SDAT3=y +CONFIG_DUMMY_CONSOLE=y CONFIG_SOUND=y CONFIG_SND=y CONFIG_SND_USB_AUDIO=y -- cgit v1.2.3 From 23eb365446ac57e140b44f4f74957e9df87149d8 Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Tue, 6 Sep 2011 09:26:47 +0530 Subject: ab5500: move to separate header Change-Id: I11dd2a23323229096b6e0ee2d4dc68cab8919bff --- drivers/leds/leds-ab5500.c | 1 + drivers/mfd/ab5500-core.c | 1 + drivers/mfd/ab5500-gpadc.c | 1 + drivers/mfd/ab5500-power.c | 1 + drivers/power/ab5500_btemp.c | 1 + drivers/power/ab5500_charger.c | 1 + drivers/power/ab5500_fg.c | 1 + drivers/rtc/rtc-ab.c | 1 + include/linux/mfd/abx500.h | 158 --------------------------------- include/linux/mfd/abx500/ab5500-bm.h | 4 +- include/linux/mfd/abx500/ab5500.h | 164 +++++++++++++++++++++++++++++++++++ 11 files changed, 174 insertions(+), 160 deletions(-) create mode 100644 include/linux/mfd/abx500/ab5500.h diff --git a/drivers/leds/leds-ab5500.c b/drivers/leds/leds-ab5500.c index e4376d1b59b..94db3a6feea 100644 --- a/drivers/leds/leds-ab5500.c +++ b/drivers/leds/leds-ab5500.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 1e8fbc9e091..e3dbd930cce 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/mfd/ab5500-gpadc.c b/drivers/mfd/ab5500-gpadc.c index dbfc9c30e38..6756d3cf37a 100644 --- a/drivers/mfd/ab5500-gpadc.c +++ b/drivers/mfd/ab5500-gpadc.c @@ -17,6 +17,7 @@ #include #include #include +#include #include /* diff --git a/drivers/mfd/ab5500-power.c b/drivers/mfd/ab5500-power.c index d23960bc976..a549b3e7538 100644 --- a/drivers/mfd/ab5500-power.c +++ b/drivers/mfd/ab5500-power.c @@ -10,6 +10,7 @@ #include #include +#include static struct device *dev; diff --git a/drivers/power/ab5500_btemp.c b/drivers/power/ab5500_btemp.c index 2e2a5054a58..7867455f493 100644 --- a/drivers/power/ab5500_btemp.c +++ b/drivers/power/ab5500_btemp.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include diff --git a/drivers/power/ab5500_charger.c b/drivers/power/ab5500_charger.c index b30f00a57f9..8b5b081fe34 100644 --- a/drivers/power/ab5500_charger.c +++ b/drivers/power/ab5500_charger.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/drivers/power/ab5500_fg.c b/drivers/power/ab5500_fg.c index bb2bfb9e840..0975c7417a4 100644 --- a/drivers/power/ab5500_fg.c +++ b/drivers/power/ab5500_fg.c @@ -26,6 +26,7 @@ #include #include #include +#include static LIST_HEAD(ab5500_fg_list); diff --git a/drivers/rtc/rtc-ab.c b/drivers/rtc/rtc-ab.c index 8e595e05d99..db1992632fa 100644 --- a/drivers/rtc/rtc-ab.c +++ b/drivers/rtc/rtc-ab.c @@ -13,6 +13,7 @@ #include #include #include +#include #define AB5500_RTC_CLOCK_RATE 32768 #define AB5500_RTC 0x00 diff --git a/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 79b3e9eb4f3..816b3983ac4 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -215,164 +215,6 @@ struct ab3550_platform_data { unsigned int init_settings_sz; }; -/** - * - * ab5500 - * - */ - -enum ab5500_devid { - AB5500_DEVID_ADC, - AB5500_DEVID_LEDS, - AB5500_DEVID_POWER, - AB5500_DEVID_REGULATORS, - AB5500_DEVID_SIM, - AB5500_DEVID_RTC, - AB5500_DEVID_CHARGER, - AB5500_DEVID_FG, - AB5500_DEVID_VIBRATOR, - AB5500_DEVID_CODEC, - AB5500_DEVID_USB, - AB5500_DEVID_OTP, - AB5500_DEVID_VIDEO, - AB5500_DEVID_DBIECI, - AB5500_DEVID_ONSWA, - AB5500_DEVID_CHARGALG, - AB5500_DEVID_BTEMP, - AB5500_DEVID_TEMPMON, - AB5500_NUM_DEVICES, -}; - -enum ab5500_banks { - AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP = 0, - AB5500_BANK_VDDDIG_IO_I2C_CLK_TST = 1, - AB5500_BANK_VDENC = 2, - AB5500_BANK_SIM_USBSIM = 3, - AB5500_BANK_LED = 4, - AB5500_BANK_ADC = 5, - AB5500_BANK_RTC = 6, - AB5500_BANK_STARTUP = 7, - AB5500_BANK_DBI_ECI = 8, - AB5500_BANK_CHG = 9, - AB5500_BANK_FG_BATTCOM_ACC = 10, - AB5500_BANK_USB = 11, - AB5500_BANK_IT = 12, - AB5500_BANK_VIBRA = 13, - AB5500_BANK_AUDIO_HEADSETUSB = 14, - AB5500_NUM_BANKS = 15, -}; - -enum ab5500_banks_addr { - AB5500_ADDR_VIT_IO_I2C_CLK_TST_OTP = 0x4A, - AB5500_ADDR_VDDDIG_IO_I2C_CLK_TST = 0x4B, - AB5500_ADDR_VDENC = 0x06, - AB5500_ADDR_SIM_USBSIM = 0x04, - AB5500_ADDR_LED = 0x10, - AB5500_ADDR_ADC = 0x0A, - AB5500_ADDR_RTC = 0x0F, - AB5500_ADDR_STARTUP = 0x03, - AB5500_ADDR_DBI_ECI = 0x07, - AB5500_ADDR_CHG = 0x0B, - AB5500_ADDR_FG_BATTCOM_ACC = 0x0C, - AB5500_ADDR_USB = 0x05, - AB5500_ADDR_IT = 0x0E, - AB5500_ADDR_VIBRA = 0x02, - AB5500_ADDR_AUDIO_HEADSETUSB = 0x0D, -}; - -/* - * Interrupt register offsets - * Bank : 0x0E - */ -#define AB5500_IT_SOURCE0_REG 0x20 -#define AB5500_IT_SOURCE1_REG 0x21 -#define AB5500_IT_SOURCE2_REG 0x22 -#define AB5500_IT_SOURCE3_REG 0x23 -#define AB5500_IT_SOURCE4_REG 0x24 -#define AB5500_IT_SOURCE5_REG 0x25 -#define AB5500_IT_SOURCE6_REG 0x26 -#define AB5500_IT_SOURCE7_REG 0x27 -#define AB5500_IT_SOURCE8_REG 0x28 -#define AB5500_IT_SOURCE9_REG 0x29 -#define AB5500_IT_SOURCE10_REG 0x2A -#define AB5500_IT_SOURCE11_REG 0x2B -#define AB5500_IT_SOURCE12_REG 0x2C -#define AB5500_IT_SOURCE13_REG 0x2D -#define AB5500_IT_SOURCE14_REG 0x2E -#define AB5500_IT_SOURCE15_REG 0x2F -#define AB5500_IT_SOURCE16_REG 0x30 -#define AB5500_IT_SOURCE17_REG 0x31 -#define AB5500_IT_SOURCE18_REG 0x32 -#define AB5500_IT_SOURCE19_REG 0x33 -#define AB5500_IT_SOURCE20_REG 0x34 -#define AB5500_IT_SOURCE21_REG 0x35 -#define AB5500_IT_SOURCE22_REG 0x36 -#define AB5500_IT_SOURCE23_REG 0x37 - -#define AB5500_NUM_IRQ_REGS 23 - -/** - * struct ab5500 - * @access_mutex: lock out concurrent accesses to the AB registers - * @dev: a pointer to the device struct for this chip driver - * @ab5500_irq: the analog baseband irq - * @irq_base: the platform configuration irq base for subdevices - * @chip_name: name of this chip variant - * @chip_id: 8 bit chip ID for this chip variant - * @irq_lock: a lock to protect the mask - * @num_event_reg: number events registered - * @abb_events: a local bit mask of the prcmu wakeup events - * @event_mask: a local copy of the mask event registers - * @last_event_mask: a copy of the last event_mask written to hardware - * @startup_events: a copy of the first reading of the event registers - * @startup_events_read: whether the first events have been read - */ -struct ab5500 { - struct mutex access_mutex; - struct device *dev; - unsigned int ab5500_irq; - unsigned int irq_base; - char chip_name[32]; - u8 chip_id; - struct mutex irq_lock; - u32 num_event_reg; - u32 abb_events; - u8 mask[AB5500_NUM_IRQ_REGS]; - u8 oldmask[AB5500_NUM_IRQ_REGS]; - u8 startup_events[AB5500_NUM_IRQ_REGS]; - bool startup_events_read; -#ifdef CONFIG_DEBUG_FS - unsigned int debug_bank; - unsigned int debug_address; -#endif -}; - -#ifndef CONFIG_AB5500_CORE -static inline int ab5500_clock_rtc_enable(int num, bool enable) -{ - return -ENOSYS; -} -#else -extern int ab5500_clock_rtc_enable(int num, bool enable); -#endif - -/* Forward Declaration */ -struct ab5500_regulator_platform_data; - -struct ab5500_platform_data { - struct {unsigned int base; unsigned int count; } irq; - void *dev_data[AB5500_NUM_DEVICES]; - size_t dev_data_sz[AB5500_NUM_DEVICES]; - struct abx500_init_settings *init_settings; - unsigned int init_settings_sz; - bool pm_power_off; - struct ab5500_regulator_platform_data *regulator; -}; - -struct ab5500_ponkey_platform_data { - u8 shutdown_secs; -}; - int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg, u8 value); int abx500_get_register_interruptible(struct device *dev, u8 bank, u8 reg, diff --git a/include/linux/mfd/abx500/ab5500-bm.h b/include/linux/mfd/abx500/ab5500-bm.h index 1bb22614b27..f62137a6e9d 100644 --- a/include/linux/mfd/abx500/ab5500-bm.h +++ b/include/linux/mfd/abx500/ab5500-bm.h @@ -106,11 +106,11 @@ int ab5500_btemp_get_batctrl_temp(struct ab5500_btemp *btemp); static void ab5500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) { } -inline struct ab5500_btemp *ab5500_btemp_get(void) +static inline struct ab5500_btemp *ab5500_btemp_get(void) { return 0; } -inline int ab5500_btemp_get_batctrl_temp(struct ab5500_btemp *btemp) +static inline int ab5500_btemp_get_batctrl_temp(struct ab5500_btemp *btemp) { return 0; } diff --git a/include/linux/mfd/abx500/ab5500.h b/include/linux/mfd/abx500/ab5500.h new file mode 100644 index 00000000000..148f88fa8dd --- /dev/null +++ b/include/linux/mfd/abx500/ab5500.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) ST-Ericsson BLR 2011 + * + * License Terms: GNU General Public License v2 + * Author: Bibek Basu + */ +#ifndef MFD_AB5500_H +#define MFD_AB5500_H + +#include + + +enum ab5500_devid { + AB5500_DEVID_ADC, + AB5500_DEVID_LEDS, + AB5500_DEVID_POWER, + AB5500_DEVID_REGULATORS, + AB5500_DEVID_SIM, + AB5500_DEVID_RTC, + AB5500_DEVID_CHARGER, + AB5500_DEVID_FG, + AB5500_DEVID_VIBRATOR, + AB5500_DEVID_CODEC, + AB5500_DEVID_USB, + AB5500_DEVID_OTP, + AB5500_DEVID_VIDEO, + AB5500_DEVID_DBIECI, + AB5500_DEVID_ONSWA, + AB5500_DEVID_CHARGALG, + AB5500_DEVID_BTEMP, + AB5500_DEVID_TEMPMON, + AB5500_NUM_DEVICES, +}; + +enum ab5500_banks { + AB5500_BANK_VIT_IO_I2C_CLK_TST_OTP = 0, + AB5500_BANK_VDDDIG_IO_I2C_CLK_TST = 1, + AB5500_BANK_VDENC = 2, + AB5500_BANK_SIM_USBSIM = 3, + AB5500_BANK_LED = 4, + AB5500_BANK_ADC = 5, + AB5500_BANK_RTC = 6, + AB5500_BANK_STARTUP = 7, + AB5500_BANK_DBI_ECI = 8, + AB5500_BANK_CHG = 9, + AB5500_BANK_FG_BATTCOM_ACC = 10, + AB5500_BANK_USB = 11, + AB5500_BANK_IT = 12, + AB5500_BANK_VIBRA = 13, + AB5500_BANK_AUDIO_HEADSETUSB = 14, + AB5500_NUM_BANKS = 15, +}; + +enum ab5500_banks_addr { + AB5500_ADDR_VIT_IO_I2C_CLK_TST_OTP = 0x4A, + AB5500_ADDR_VDDDIG_IO_I2C_CLK_TST = 0x4B, + AB5500_ADDR_VDENC = 0x06, + AB5500_ADDR_SIM_USBSIM = 0x04, + AB5500_ADDR_LED = 0x10, + AB5500_ADDR_ADC = 0x0A, + AB5500_ADDR_RTC = 0x0F, + AB5500_ADDR_STARTUP = 0x03, + AB5500_ADDR_DBI_ECI = 0x07, + AB5500_ADDR_CHG = 0x0B, + AB5500_ADDR_FG_BATTCOM_ACC = 0x0C, + AB5500_ADDR_USB = 0x05, + AB5500_ADDR_IT = 0x0E, + AB5500_ADDR_VIBRA = 0x02, + AB5500_ADDR_AUDIO_HEADSETUSB = 0x0D, +}; + +/* + * Interrupt register offsets + * Bank : 0x0E + */ +#define AB5500_IT_SOURCE0_REG 0x20 +#define AB5500_IT_SOURCE1_REG 0x21 +#define AB5500_IT_SOURCE2_REG 0x22 +#define AB5500_IT_SOURCE3_REG 0x23 +#define AB5500_IT_SOURCE4_REG 0x24 +#define AB5500_IT_SOURCE5_REG 0x25 +#define AB5500_IT_SOURCE6_REG 0x26 +#define AB5500_IT_SOURCE7_REG 0x27 +#define AB5500_IT_SOURCE8_REG 0x28 +#define AB5500_IT_SOURCE9_REG 0x29 +#define AB5500_IT_SOURCE10_REG 0x2A +#define AB5500_IT_SOURCE11_REG 0x2B +#define AB5500_IT_SOURCE12_REG 0x2C +#define AB5500_IT_SOURCE13_REG 0x2D +#define AB5500_IT_SOURCE14_REG 0x2E +#define AB5500_IT_SOURCE15_REG 0x2F +#define AB5500_IT_SOURCE16_REG 0x30 +#define AB5500_IT_SOURCE17_REG 0x31 +#define AB5500_IT_SOURCE18_REG 0x32 +#define AB5500_IT_SOURCE19_REG 0x33 +#define AB5500_IT_SOURCE20_REG 0x34 +#define AB5500_IT_SOURCE21_REG 0x35 +#define AB5500_IT_SOURCE22_REG 0x36 +#define AB5500_IT_SOURCE23_REG 0x37 +#define AB5500_IT_SOURCE24_REG 0x38 + +#define AB5500_NUM_IRQ_REGS 25 + +/** + * struct ab5500 + * @access_mutex: lock out concurrent accesses to the AB registers + * @dev: a pointer to the device struct for this chip driver + * @ab5500_irq: the analog baseband irq + * @irq_base: the platform configuration irq base for subdevices + * @chip_name: name of this chip variant + * @chip_id: 8 bit chip ID for this chip variant + * @irq_lock: a lock to protect the mask + * @abb_events: a local bit mask of the prcmu wakeup events + * @event_mask: a local copy of the mask event registers + * @last_event_mask: a copy of the last event_mask written to hardware + * @startup_events: a copy of the first reading of the event registers + * @startup_events_read: whether the first events have been read + */ +struct ab5500 { + struct mutex access_mutex; + struct device *dev; + unsigned int ab5500_irq; + unsigned int irq_base; + char chip_name[32]; + u8 chip_id; + struct mutex irq_lock; + u32 num_event_reg; + u32 abb_events; + u8 mask[AB5500_NUM_IRQ_REGS]; + u8 oldmask[AB5500_NUM_IRQ_REGS]; + u8 startup_events[AB5500_NUM_IRQ_REGS]; + bool startup_events_read; +#ifdef CONFIG_DEBUG_FS + unsigned int debug_bank; + unsigned int debug_address; +#endif +}; + +#ifndef CONFIG_AB5500_CORE +static inline int ab5500_clock_rtc_enable(int num, bool enable) +{ + return -ENOSYS; +} +#else +extern int ab5500_clock_rtc_enable(int num, bool enable); +#endif + +/* Forward Declaration */ +struct ab5500_regulator_platform_data; + +struct ab5500_platform_data { + struct {unsigned int base; unsigned int count; } irq; + void *dev_data[AB5500_NUM_DEVICES]; + size_t dev_data_sz[AB5500_NUM_DEVICES]; + struct abx500_init_settings *init_settings; + unsigned int init_settings_sz; + bool pm_power_off; + struct ab5500_regulator_platform_data *regulator; +}; + +struct ab5500_ponkey_platform_data { + u8 shutdown_secs; +}; +#endif /* MFD_AB5500_H */ -- cgit v1.2.3 From 9902da26ec5d62ab02e11d7f2a2a3ad45adc6974 Mon Sep 17 00:00:00 2001 From: Bartosz Markowski Date: Thu, 1 Sep 2011 08:33:09 +0200 Subject: WLAN: Add compat-wireless settings to u8500_defconfig ST-Ericsson ID: 352334 ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Bartosz Markowski Change-Id: I77ff7475f13e94337af48eab0c1f3c67644f8dc8 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/30003 --- arch/arm/configs/u8500_defconfig | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 3f0051220c7..d276ad86eba 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -95,10 +95,38 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y -CONFIG_CFG80211=m -CONFIG_MAC80211=m + +#WIRELESS +CONFIG_WIRELESS=y +CONFIG_COMPAT_WIRELESS=y +CONFIG_COMPAT_WIRELESS_MODULES=y +CONFIG_CFG80211=y +CONFIG_COMPAT_MAC80211_RC_DEFAULT="minstrel_ht" +CONFIG_MAC80211_RC_DEFAULT_MINSTREL=y +CONFIG_COMPAT_RFKILL=y +CONFIG_NL80211_TESTMODE=y +CONFIG_CFG80211_DEFAULT_PS=y +CONFIG_CFG80211_REG_DEBUG=y +CONFIG_MAC80211_RC_PID=y +CONFIG_MAC80211_RC_MINSTREL=y +CONFIG_MAC80211_RC_MINSTREL_HT=y CONFIG_MAC80211_LEDS=y +CONFIG_MAC80211_MESH=y +# CONFIG_CFG80211_DEVELOPER_WARNINGS is not set +# CONFIG_CFG80211_DEBUGFS is not set +# CONFIG_CFG80211_INTERNAL_REGDB is not set +# CONFIG_CFG80211_WEXT is not set +# CONFIG_LIB80211 is not set +# CONFIG_MAC80211 is not set +# CONFIG_MAC80211_HAS_RC is not set +# CONFIG_MAC80211_RC_DEFAULT_PID is not set +# CONFIG_MAC80211_RC_DEFAULT is not set +# CONFIG_MAC80211_DEBUGFS is not set +# CONFIG_MAC80211_DEBUG_MENU is not set +# CONFIG_WIMAX is not set CONFIG_RFKILL=y +CONFIG_RFKILL_PM=y +CONFIG_RFKILL_LEDS=y CONFIG_RFKILL_INPUT=y CONFIG_NET_9P=y CONFIG_CAIF=y -- cgit v1.2.3 From 28db8ab7612bfa76f2eae54f6b1a881a7da26209 Mon Sep 17 00:00:00 2001 From: Bartosz Markowski Date: Fri, 2 Sep 2011 08:02:39 +0200 Subject: u8500: Enable AVERAGE and LED_TRIGGERS * reqired by WLAN (compat-wireless) ST-Ericsson ID: 352334 ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Bartosz Markowski Change-Id: I8a7ac523ce05549d31f968a00d32ca01de26d891 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/30007 --- arch/arm/configs/u8500_defconfig | 3 +++ 1 file changed, 3 insertions(+) mode change 100644 => 100755 arch/arm/configs/u8500_defconfig diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig old mode 100644 new mode 100755 index d276ad86eba..89b94b76dfe --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -96,6 +96,8 @@ CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y +CONFIG_AVERAGE=y + #WIRELESS CONFIG_WIRELESS=y CONFIG_COMPAT_WIRELESS=y @@ -265,6 +267,7 @@ CONFIG_LEDS_CLASS=y CONFIG_LEDS_LM3530=y CONFIG_LEDS_LP5521=y CONFIG_LEDS_PWM=y +CONFIG_LEDS_TRIGGERS=y CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_SWITCH=y CONFIG_RTC_CLASS=y -- cgit v1.2.3 From 6aa27ee491756b1799d542d493a38e3750af20e2 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Wed, 6 Jul 2011 08:24:42 +0200 Subject: drivers:watchdog:ux500: Only use auto kick on suspend Configure the watchdog to get automatically kicked only during suspend. Previous it was automatically kicked during cpuidle sleep stages as well. ST-Ericsson Linux next: Not tested, ask SSM for ER ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Jonas Aaberg Change-Id: Ifb42c4eac7cf36581dec4d8646af0b51f0a24483 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/26648 Reviewed-by: Rickard ANDERSSON Reviewed-by: QATEST Reviewed-by: Per-Inge TALLBERG --- drivers/watchdog/ux500_wdt.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/drivers/watchdog/ux500_wdt.c b/drivers/watchdog/ux500_wdt.c index fd343197681..b620200ea3e 100644 --- a/drivers/watchdog/ux500_wdt.c +++ b/drivers/watchdog/ux500_wdt.c @@ -40,7 +40,7 @@ MODULE_PARM_DESC(nowayout, __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); static u8 wdog_id; static bool wdt_en; -static bool wdt_auto_off = true; +static bool wdt_auto_off = false; static bool safe_close; static int ux500_wdt_open(struct inode *inode, struct file *file) @@ -382,13 +382,44 @@ static int __exit ux500_wdt_remove(struct platform_device *dev) misc_deregister(&ux500_wdt_miscdev); return 0; } +#ifdef CONFIG_PM +static int ux500_wdt_suspend(struct platform_device *pdev, + pm_message_t state) +{ + if (wdt_en && !wdt_auto_off) { + prcmu_disable_a9wdog(wdog_id); + prcmu_config_a9wdog(1, true); + + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + } + return 0; +} +static int ux500_wdt_resume(struct platform_device *pdev) +{ + if (wdt_en && !wdt_auto_off) { + prcmu_disable_a9wdog(wdog_id); + prcmu_config_a9wdog(1, wdt_auto_off); + + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + } + return 0; +} + +#else +#define ux500_wdt_suspend NULL +#define ux500_wdt_resume NULL +#endif static struct platform_driver ux500_wdt_driver = { .remove = __exit_p(ux500_wdt_remove), .driver = { .owner = THIS_MODULE, .name = "ux500_wdt", }, + .suspend = ux500_wdt_suspend, + .resume = ux500_wdt_resume, }; static int __init ux500_wdt_init(void) -- cgit v1.2.3 From d9088b3dbd9ea89a0d1981268703bdfac8f627b7 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 09:48:55 +0200 Subject: ab8500 mfd: USB: Set the charging cur to 300mA for ABV3 In case of AB-V3, the eye diagram related issues are resolved. So, set the device charging current to 300mA when connected to standard host. Also, add the USB PHY tuning values to improve the USB eye diagram Signed-off-by: --- drivers/mfd/ab8500-debugfs.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index 30854257586..6e62714bcb6 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -144,7 +144,7 @@ static struct hwreg_cfg hwreg_cfg = { #define AB8500_NAME_STRING "ab8500" #define AB8500_ADC_NAME_STRING "gpadc" -#define AB8500_NUM_BANKS 22 +#define AB8500_NUM_BANKS 24 #define AB8500_REV_REG 0x80 @@ -316,7 +316,7 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { }, }, [AB8500_CHARGER] = { - .num_ranges = 8, + .num_ranges = 9, .range = (struct ab8500_reg_range[]) { { .first = 0x00, @@ -350,6 +350,10 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { .first = 0xC0, .last = 0xC2, }, + { + .first = 0xf5, + .last = 0xf6, + }, }, }, [AB8500_GAS_GAUGE] = { @@ -369,6 +373,24 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { }, }, }, + [AB8500_DEVELOPMENT] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + }, + }, + [AB8500_DEBUG] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x05, + .last = 0x07, + }, + }, + }, [AB8500_AUDIO] = { .num_ranges = 1, .range = (struct ab8500_reg_range[]) { -- cgit v1.2.3 From b550d52b80949229f70f638ca6811d31e89af564 Mon Sep 17 00:00:00 2001 From: Bartosz Markowski Date: Wed, 7 Sep 2011 11:45:28 +0200 Subject: Update kernelrelease target in kernel Makefile setlocalversion script has input parameter which should be passed when calling kernelrelease target. Add kernelrelease to no-dot-config-targets. We should not run oldconfig when calling kernelrelease target. ST-Ericsson ID: 352334 ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Bartosz Markowski Change-Id: I10ab7bf7963685887bbb97b7f8dfa5ea9a43c29d Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/30296 Reviewed-by: Axel FAGERSTEDT Tested-by: Said BAGHERI Reviewed-by: Ushit KUMAR Reviewed-by: Srinidhi KASAGAR --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c68a0a084bf..ae7cd870c7c 100644 --- a/Makefile +++ b/Makefile @@ -443,7 +443,7 @@ asm-generic: no-dot-config-targets := clean mrproper distclean \ cscope gtags TAGS tags help %docs check% coccicheck \ include/linux/version.h headers_% \ - kernelversion %src-pkg + kernelrelease kernelversion %src-pkg config-targets := 0 mixed-targets := 0 @@ -1462,7 +1462,7 @@ checkstack: $(PERL) $(src)/scripts/checkstack.pl $(CHECKSTACK_ARCH) kernelrelease: - @echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion $(srctree))" + @echo "$(KERNELVERSION)$$($(CONFIG_SHELL) $(srctree)/scripts/setlocalversion -s $(srctree) -t v$(KERNELVERSION))" kernelversion: @echo $(KERNELVERSION) -- cgit v1.2.3 From 550d893edfecdc37c89f6b34f255519097e9cade Mon Sep 17 00:00:00 2001 From: Daniel Willerud Date: Fri, 26 Aug 2011 10:49:11 +0200 Subject: hwmon: Adding notifiers to hwmon ST-Ericsson ID: 354533 ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I068a3c801405b6f7ca2b78df7cd9f7461ed8738b Signed-off-by: Daniel Willerud Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/29502 Reviewed-by: QABUILD Reviewed-by: Jonas ABERG --- drivers/hwmon/hwmon.c | 21 ++++++++++++++++++++- include/linux/hwmon.h | 5 +++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index a61e7815a2a..b3c62f84b0b 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -21,6 +21,7 @@ #include #include #include +#include #define HWMON_ID_PREFIX "hwmon" #define HWMON_ID_FORMAT HWMON_ID_PREFIX "%d" @@ -29,7 +30,7 @@ static struct class *hwmon_class; static DEFINE_IDR(hwmon_idr); static DEFINE_SPINLOCK(idr_lock); - +static BLOCKING_NOTIFIER_HEAD(hwmon_notifier_list); /** * hwmon_device_register - register w/ hwmon * @dev: the device to register @@ -89,6 +90,24 @@ void hwmon_device_unregister(struct device *dev) "hwmon_device_unregister() failed: bad class ID!\n"); } +int hwmon_notifier_register(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&hwmon_notifier_list, nb); +} +EXPORT_SYMBOL(hwmon_notifier_register); + +int hwmon_notifier_unregister(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&hwmon_notifier_list, nb); +} +EXPORT_SYMBOL(hwmon_notifier_unregister); + +void hwmon_notify(unsigned long val, void *v) +{ + blocking_notifier_call_chain(&hwmon_notifier_list, val, v); +} +EXPORT_SYMBOL(hwmon_notify); + static void __init hwmon_pci_quirks(void) { #if defined CONFIG_X86 && defined CONFIG_PCI diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 6b6ee702b00..8e891b5a777 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -15,11 +15,16 @@ #define _HWMON_H_ #include +#include struct device *hwmon_device_register(struct device *dev); void hwmon_device_unregister(struct device *dev); +int hwmon_notifier_register(struct notifier_block *nb); +int hwmon_notifier_unregister(struct notifier_block *nb); +void hwmon_notify(unsigned long val, void *v); + /* Scale user input to sensible values */ static inline int SENSORS_LIMIT(long value, long low, long high) { -- cgit v1.2.3 From f1934e379ce3c325b85fc06e41170a09529d92d7 Mon Sep 17 00:00:00 2001 From: Daniel Willerud Date: Thu, 25 Aug 2011 17:39:18 +0200 Subject: ux500: Thermal power off To determine whether the system had a thermal power off or a regular software power off upon the next boot, the system must utilize the thermal power off bit, ThDB8500SWoff, in AB8500 register STw4500Ctrl1. ST-Ericsson ID: 354533 ST-Ericsson Linux next: N/A ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I07440dcdc39a86cb72aa95d86411a1f020b05cae Signed-off-by: Daniel Willerud Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/28515 Reviewed-by: QABUILD Reviewed-by: Jonas ABERG --- drivers/hwmon/ab8500.c | 2 ++ drivers/hwmon/abx500.c | 1 + drivers/hwmon/dbx500.c | 1 + drivers/mfd/ab8500-sysctrl.c | 50 ++++++++++++++++++++++++++++++++++++++++---- include/linux/mfd/ab8500.h | 7 +++++++ 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c index cb6cf4cf4d4..67118a4f11d 100644 --- a/drivers/hwmon/ab8500.c +++ b/drivers/hwmon/ab8500.c @@ -117,6 +117,8 @@ static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) mutex_lock(&data->lock); data->crit_alarm[4] = 1; mutex_unlock(&data->lock); + + hwmon_notify(data->crit_alarm[4], NULL); sysfs_notify(&data->pdev->dev.kobj, NULL, "temp5_crit_alarm"); dev_info(&data->pdev->dev, "AB8500 thermal warning," " power off in %lu s\n", data->power_off_delay); diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c index 6666dc27065..68da2d03dbd 100644 --- a/drivers/hwmon/abx500.c +++ b/drivers/hwmon/abx500.c @@ -163,6 +163,7 @@ static void gpadc_monitor(struct work_struct *work) ret); break; } + hwmon_notify(data->max_alarm[i], NULL); sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); } if (updated_max_hyst_alarm) { diff --git a/drivers/hwmon/dbx500.c b/drivers/hwmon/dbx500.c index a26e08b3707..cd44e78030c 100644 --- a/drivers/hwmon/dbx500.c +++ b/drivers/hwmon/dbx500.c @@ -323,6 +323,7 @@ static irqreturn_t prcmu_hotmon_high_irq_handler(int irq, void *irq_data) data->max_alarm[0] = 1; mutex_unlock(&data->lock); + hwmon_notify(data->max_alarm[0], NULL); sysfs_notify(&pdev->dev.kobj, NULL, "temp1_max_alarm"); dev_dbg(&pdev->dev, "DBX500 thermal warning, power off in %lu s\n", (data->power_off_delay) / 1000); diff --git a/drivers/mfd/ab8500-sysctrl.c b/drivers/mfd/ab8500-sysctrl.c index d24c41fed7a..b22c6bfc9fd 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -13,11 +13,15 @@ #include #include #include +#include +#include static struct device *sysctrl_dev; void ab8500_power_off(void) { + struct ab8500_platform_data *plat; + struct timespec ts; sigset_t old; sigset_t all; static char *pss[] = {"ab8500_ac", "ab8500_usb"}; @@ -65,14 +69,51 @@ void ab8500_power_off(void) shutdown: sigfillset(&all); + plat = dev_get_platdata(sysctrl_dev->parent); + getnstimeofday(&ts); if (!sigprocmask(SIG_BLOCK, &all, &old)) { - (void)ab8500_sysctrl_set(AB8500_STW4500CTRL1, - AB8500_STW4500CTRL1_SWOFF | - AB8500_STW4500CTRL1_SWRESET4500N); - (void)sigprocmask(SIG_SETMASK, &old, NULL); + if (ts.tv_sec == 0 || + (ts.tv_sec - plat->thermal_set_time_sec > + plat->thermal_time_out)) + plat->thermal_power_off_pending = false; + if (!plat->thermal_power_off_pending) { + (void)ab8500_sysctrl_set(AB8500_STW4500CTRL1, + AB8500_STW4500CTRL1_SWOFF | + AB8500_STW4500CTRL1_SWRESET4500N); + (void)sigprocmask(SIG_SETMASK, &old, NULL); + } else { + (void)ab8500_sysctrl_set(AB8500_STW4500CTRL1, + AB8500_STW4500CTRL1_THDB8500SWOFF | + AB8500_STW4500CTRL1_SWRESET4500N); + (void)sigprocmask(SIG_SETMASK, &old, NULL); + } } } +static int ab8500_notifier_call(struct notifier_block *this, + unsigned long val, void *data) +{ + struct ab8500_platform_data *plat; + static struct timespec ts; + if (sysctrl_dev == NULL) + return -EAGAIN; + + plat = dev_get_platdata(sysctrl_dev->parent); + if (val) { + getnstimeofday(&ts); + plat->thermal_set_time_sec = ts.tv_sec; + plat->thermal_power_off_pending = true; + } else { + plat->thermal_set_time_sec = 0; + plat->thermal_power_off_pending = false; + } + return 0; +} + +static struct notifier_block ab8500_notifier = { + .notifier_call = ab8500_notifier_call, +}; + static inline bool valid_bank(u8 bank) { return ((bank == AB8500_SYS_CTRL1_BLOCK) || @@ -117,6 +158,7 @@ static int __devinit ab8500_sysctrl_probe(struct platform_device *pdev) plat = dev_get_platdata(pdev->dev.parent); if (plat->pm_power_off) pm_power_off = ab8500_power_off; + hwmon_notifier_register(&ab8500_notifier); return 0; } diff --git a/include/linux/mfd/ab8500.h b/include/linux/mfd/ab8500.h index 8f66bd52679..13924d0c861 100644 --- a/include/linux/mfd/ab8500.h +++ b/include/linux/mfd/ab8500.h @@ -179,6 +179,10 @@ struct ab8500_gpio_platform_data; /** * struct ab8500_platform_data - AB8500 platform data + * @pm_power_off: Should machine pm power off hook be registered or not + * @thermal_power_off_pending: Set if there was a thermal alarm + * @thermal_set_time_sec: Time of the thermal alarm + * @thermal_time_out: Time out before the thermal alarm should be ignored * @irq_base: start of AB8500 IRQs, AB8500_NR_IRQS will be used * @init: board-specific initialization after detection of ab8500 * @num_regulator_reg_init: number of regulator init registers @@ -192,6 +196,9 @@ struct ab8500_gpio_platform_data; struct ab8500_platform_data { int irq_base; bool pm_power_off; + bool thermal_power_off_pending; + long thermal_set_time_sec; + long thermal_time_out; void (*init) (struct ab8500 *); int num_regulator_reg_init; struct ab8500_regulator_reg_init *regulator_reg_init; -- cgit v1.2.3 From e8919af220004e3977d28a31250bf8f065187d7f Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Fri, 9 Sep 2011 17:50:48 +0530 Subject: u8500: set cpuidle deepest state to 2 Some drivers such as MMC and UART do not yet managed the v-ape regulator on 3.0. Change-Id: Ifc3d989542eb44476f711d46e8c7dd27852998a7 --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 89b94b76dfe..d9097a15abb 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -36,6 +36,7 @@ CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_ROTATION_ANGLE=90 CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y +CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 CONFIG_U8500_CPUIDLE_DEBUG=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_STANDBY=y -- cgit v1.2.3 From 928f5861f006532547fb6431277e797c6c90dfce Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Fri, 9 Sep 2011 17:13:47 +0530 Subject: PM: Export AMBA bus type's default PM callbacks Export the default PM callbacks defined for the platform bus type so that they can be used by power domains for suspending and resuming platform devices in the future. Like 69c9dd1ecf446ad8a830e4afc539a2a1adc85b78 ("PM: Export platform bus type's default PM callbacks"). Change-Id: I92aa13abe15c153aa5d90def7eb212626f6d0213 Signed-off-by: Rabin Vincent --- drivers/amba/bus.c | 43 ++++++++++++---------------------- include/linux/amba/bus.h | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 28 deletions(-) diff --git a/drivers/amba/bus.c b/drivers/amba/bus.c index d74926e0939..6e4b1553afe 100644 --- a/drivers/amba/bus.c +++ b/drivers/amba/bus.c @@ -109,7 +109,7 @@ static int amba_legacy_resume(struct device *dev) return ret; } -static int amba_pm_prepare(struct device *dev) +int amba_pm_prepare(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -120,7 +120,7 @@ static int amba_pm_prepare(struct device *dev) return ret; } -static void amba_pm_complete(struct device *dev) +void amba_pm_complete(struct device *dev) { struct device_driver *drv = dev->driver; @@ -137,7 +137,7 @@ static void amba_pm_complete(struct device *dev) #ifdef CONFIG_SUSPEND -static int amba_pm_suspend(struct device *dev) +int amba_pm_suspend(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -155,7 +155,7 @@ static int amba_pm_suspend(struct device *dev) return ret; } -static int amba_pm_suspend_noirq(struct device *dev) +int amba_pm_suspend_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -171,7 +171,7 @@ static int amba_pm_suspend_noirq(struct device *dev) return ret; } -static int amba_pm_resume(struct device *dev) +int amba_pm_resume(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -189,7 +189,7 @@ static int amba_pm_resume(struct device *dev) return ret; } -static int amba_pm_resume_noirq(struct device *dev) +int amba_pm_resume_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -216,7 +216,7 @@ static int amba_pm_resume_noirq(struct device *dev) #ifdef CONFIG_HIBERNATE_CALLBACKS -static int amba_pm_freeze(struct device *dev) +int amba_pm_freeze(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -234,7 +234,7 @@ static int amba_pm_freeze(struct device *dev) return ret; } -static int amba_pm_freeze_noirq(struct device *dev) +int amba_pm_freeze_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -250,7 +250,7 @@ static int amba_pm_freeze_noirq(struct device *dev) return ret; } -static int amba_pm_thaw(struct device *dev) +int amba_pm_thaw(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -268,7 +268,7 @@ static int amba_pm_thaw(struct device *dev) return ret; } -static int amba_pm_thaw_noirq(struct device *dev) +int amba_pm_thaw_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -284,7 +284,7 @@ static int amba_pm_thaw_noirq(struct device *dev) return ret; } -static int amba_pm_poweroff(struct device *dev) +int amba_pm_poweroff(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -302,7 +302,7 @@ static int amba_pm_poweroff(struct device *dev) return ret; } -static int amba_pm_poweroff_noirq(struct device *dev) +int amba_pm_poweroff_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -318,7 +318,7 @@ static int amba_pm_poweroff_noirq(struct device *dev) return ret; } -static int amba_pm_restore(struct device *dev) +int amba_pm_restore(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -336,7 +336,7 @@ static int amba_pm_restore(struct device *dev) return ret; } -static int amba_pm_restore_noirq(struct device *dev) +int amba_pm_restore_noirq(struct device *dev) { struct device_driver *drv = dev->driver; int ret = 0; @@ -368,20 +368,7 @@ static int amba_pm_restore_noirq(struct device *dev) #ifdef CONFIG_PM static const struct dev_pm_ops amba_pm = { - .prepare = amba_pm_prepare, - .complete = amba_pm_complete, - .suspend = amba_pm_suspend, - .resume = amba_pm_resume, - .freeze = amba_pm_freeze, - .thaw = amba_pm_thaw, - .poweroff = amba_pm_poweroff, - .restore = amba_pm_restore, - .suspend_noirq = amba_pm_suspend_noirq, - .resume_noirq = amba_pm_resume_noirq, - .freeze_noirq = amba_pm_freeze_noirq, - .thaw_noirq = amba_pm_thaw_noirq, - .poweroff_noirq = amba_pm_poweroff_noirq, - .restore_noirq = amba_pm_restore_noirq, + USE_AMBA_PM_SLEEP_OPS SET_RUNTIME_PM_OPS( pm_generic_runtime_suspend, pm_generic_runtime_resume, diff --git a/include/linux/amba/bus.h b/include/linux/amba/bus.h index fcbbe71a3cc..849aec9aba5 100644 --- a/include/linux/amba/bus.h +++ b/include/linux/amba/bus.h @@ -94,4 +94,64 @@ void amba_release_regions(struct amba_device *); #define amba_manf(d) AMBA_MANF_BITS((d)->periphid) #define amba_part(d) AMBA_PART_BITS((d)->periphid) +#ifdef CONFIG_PM_SLEEP +extern int amba_pm_prepare(struct device *dev); +extern void amba_pm_complete(struct device *dev); +#else +#define amba_pm_prepare NULL +#define amba_pm_complete NULL +#endif + +#ifdef CONFIG_SUSPEND +extern int amba_pm_suspend(struct device *dev); +extern int amba_pm_suspend_noirq(struct device *dev); +extern int amba_pm_resume(struct device *dev); +extern int amba_pm_resume_noirq(struct device *dev); +#else +#define amba_pm_suspend NULL +#define amba_pm_resume NULL +#define amba_pm_suspend_noirq NULL +#define amba_pm_resume_noirq NULL +#endif + +#ifdef CONFIG_HIBERNATE_CALLBACKS +extern int amba_pm_freeze(struct device *dev); +extern int amba_pm_freeze_noirq(struct device *dev); +extern int amba_pm_thaw(struct device *dev); +extern int amba_pm_thaw_noirq(struct device *dev); +extern int amba_pm_poweroff(struct device *dev); +extern int amba_pm_poweroff_noirq(struct device *dev); +extern int amba_pm_restore(struct device *dev); +extern int amba_pm_restore_noirq(struct device *dev); +#else +#define amba_pm_freeze NULL +#define amba_pm_thaw NULL +#define amba_pm_poweroff NULL +#define amba_pm_restore NULL +#define amba_pm_freeze_noirq NULL +#define amba_pm_thaw_noirq NULL +#define amba_pm_poweroff_noirq NULL +#define amba_pm_restore_noirq NULL +#endif + +#ifdef CONFIG_PM_SLEEP +#define USE_AMBA_PM_SLEEP_OPS \ + .prepare = amba_pm_prepare, \ + .complete = amba_pm_complete, \ + .suspend = amba_pm_suspend, \ + .resume = amba_pm_resume, \ + .freeze = amba_pm_freeze, \ + .thaw = amba_pm_thaw, \ + .poweroff = amba_pm_poweroff, \ + .restore = amba_pm_restore, \ + .suspend_noirq = amba_pm_suspend_noirq, \ + .resume_noirq = amba_pm_resume_noirq, \ + .freeze_noirq = amba_pm_freeze_noirq, \ + .thaw_noirq = amba_pm_thaw_noirq, \ + .poweroff_noirq = amba_pm_poweroff_noirq, \ + .restore_noirq = amba_pm_restore_noirq, +#else +#define USE_AMBA_PM_SLEEP_OPS +#endif + #endif -- cgit v1.2.3 From ebf13e51b9bf2708eda85888ec2989157219f18b Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Mon, 12 Sep 2011 10:45:15 +0530 Subject: u5500: enable PRCMU QoS and PRCMU debug Change-Id: I8985a0224cea4854f4b733c9294e4096516ac5a4 --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index d9097a15abb..e7fc2a04710 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -26,6 +26,7 @@ CONFIG_MACH_SNOWBALL=y CONFIG_UX500_PRCMU_TIMER=y CONFIG_U8500_PRCMU=y CONFIG_UX500_PRCMU_DEBUG=y +CONFIG_UX500_DEBUG_UART=0 CONFIG_U8500_REGULATOR_DEBUG=y CONFIG_DB8500_MLOADER=y CONFIG_DISPLAY_GENERIC_PRIMARY=y -- cgit v1.2.3 From 6c1437fc4716c56134e7a77d62c6471f4c6f1362 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Mon, 29 Aug 2011 08:33:42 +0200 Subject: rtc: ab8500: Change to mdelay The resolution of msleep is related to HZ, so with HZ set to 100 any msleep of less than 10ms will become ~10ms. ST-Ericsson ID: - ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Jonas Aaberg --- drivers/rtc/rtc-ab8500.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-ab8500.c b/drivers/rtc/rtc-ab8500.c index 8e017f2cf79..7323a5b5850 100644 --- a/drivers/rtc/rtc-ab8500.c +++ b/drivers/rtc/rtc-ab8500.c @@ -90,7 +90,7 @@ static int ab8500_rtc_read_time(struct device *dev, struct rtc_time *tm) /* Early AB8500 chips will not clear the rtc read request bit */ if (abx500_get_chip_id(dev) == 0) { - msleep(1); + mdelay(1); } else { /* Wait for some cycles after enabling the rtc read in ab8500 */ while (time_before(jiffies, timeout)) { @@ -102,7 +102,7 @@ static int ab8500_rtc_read_time(struct device *dev, struct rtc_time *tm) if (!(value & RTC_READ_REQUEST)) break; - msleep(1); + mdelay(1); } } @@ -306,7 +306,7 @@ static int __devinit ab8500_rtc_probe(struct platform_device *pdev) return err; /* Wait for reset by the PorRtc */ - msleep(1); + mdelay(1); err = abx500_get_register_interruptible(&pdev->dev, AB8500_RTC, AB8500_RTC_STAT_REG, &rtc_ctrl); -- cgit v1.2.3 From 46d714a9e2cd7c4cdce337d50d6c8dbdb9ac8b59 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 18 Oct 2011 13:25:14 +0200 Subject: pl011: migrate pl011 changes from 2.3v3.2 Ref: ARM: ux500: uart: context save/restore uses relaxed pl011: add ifdef pl011: don't touch registers when clock is off ux500: pl011: Workaround for UART registers lockup Migrated Changes from: e1f512c, 852b78d, c2195fa, ac87c0e --- drivers/tty/serial/amba-pl011.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index ff4761e482c..73bd1bc59df 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -1236,7 +1236,7 @@ static irqreturn_t pl011_int(int irq, void *dev_id) do { writew(status & ~(UART011_TXIS|UART011_RTIS| UART011_RXIS), - uap->port.membase + UART011_ICR); + uap->port.membase + UART011_ICR); if (status & (UART011_RTIS|UART011_RXIS)) { if (pl011_dma_rx_running(uap)) @@ -1723,8 +1723,6 @@ static struct uart_ops amba_pl011_pops = { #endif }; -static struct uart_amba_port *amba_ports[UART_NR]; - #ifdef CONFIG_SERIAL_AMBA_PL011_CONSOLE static void pl011_console_putchar(struct uart_port *port, int ch) @@ -1961,7 +1959,12 @@ static int pl011_suspend(struct amba_device *dev, pm_message_t state) if (!uap) return -EINVAL; +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL + cancel_delayed_work_sync(&uap->clk_off_work); + if (uap->clk_state == PL011_CLK_OFF) + return 0; +#endif return uart_suspend_port(&amba_reg, &uap->port); } @@ -1971,6 +1974,10 @@ static int pl011_resume(struct amba_device *dev) if (!uap) return -EINVAL; +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL + if (uap->clk_state == PL011_CLK_OFF) + return 0; +#endif return uart_resume_port(&amba_reg, &uap->port); } -- cgit v1.2.3 From cdee8e504182a182aa2cced92d55268a486f7f8f Mon Sep 17 00:00:00 2001 From: "Rajanikanth H.V" Date: Mon, 12 Sep 2011 11:37:20 +0530 Subject: UART:pl011: migrate pl011 support to 3.0 pl011 changes from the following commits have been manually merged. ef45647, d57a627, 588c0bb, 9b4ecf9, 913afa0, 13aa593, e7d5dcb, 5115d25, 19fcf44 ST-Ericsson ID: NA ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: NA Change-Id: I1759c825add127867cf6edc688b860e82743ce6f Signed-off-by: Rajanikanth H.V --- arch/arm/configs/u8500_defconfig | 1 + drivers/tty/serial/Kconfig | 8 + drivers/tty/serial/amba-pl011.c | 367 +++++++++++++++++++++++++++++++++++---- 3 files changed, 346 insertions(+), 30 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index e7fc2a04710..fddc8248005 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -180,6 +180,7 @@ CONFIG_VT_HW_CONSOLE_BINDING=y # CONFIG_DEVKMEM is not set CONFIG_SERIAL_AMBA_PL011=y CONFIG_SERIAL_AMBA_PL011_CONSOLE=y +CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL=y CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM_NOMADIK=y CONFIG_I2C=y diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 4dcb37bbdf9..6deb387415e 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -323,6 +323,14 @@ config SERIAL_AMBA_PL011_CONSOLE your boot loader (lilo or loadlin) about how to pass options to the kernel at boot time.) +config SERIAL_AMBA_PL011_CLOCK_CONTROL + bool "Support for clock control on AMBA serial port" + depends on SERIAL_AMBA_PL011 + select CONSOLE_POLL + ---help--- + Say Y here if you wish to use amba set_termios function to control + the pl011 clock. Any positive baudrate passed enables clock, + config SERIAL_SB1250_DUART tristate "BCM1xxx on-chip DUART serial support" depends on SIBYTE_SB1xxx_SOC=y diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 73bd1bc59df..638c2f63f66 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,37 @@ #define UART_DR_ERROR (UART011_DR_OE|UART011_DR_BE|UART011_DR_PE|UART011_DR_FE) #define UART_DUMMY_DR_RX (1 << 16) +/* + * The console UART is handled differently for power management (it doesn't + * take the regulator, in order to allow the system to go to sleep even if the + * console is open). This should be removed once cable detect is in place. + */ +#ifdef CONFIG_SERIAL_CORE_CONSOLE +#define uart_console(port) ((port)->cons \ + && (port)->cons->index == (port)->line) +#else +#define uart_console(port) (0) +#endif + +/* Available amba pl011 port clock states */ +enum pl011_clk_states { + PL011_CLK_OFF = 0, /* clock disabled */ + PL011_CLK_REQUEST_OFF, /* disable after TX flushed */ + PL011_CLK_ON, /* clock enabled */ + PL011_PORT_OFF, /* port disabled */ +}; + +/* + * Backup registers to be used during regulator startup/shutdown + */ +static const u32 backup_regs[] = { + UART011_IBRD, + UART011_FBRD, + ST_UART011_LCRH_RX, + ST_UART011_LCRH_TX, + UART011_CR, + UART011_IMSC, +}; #define UART_WA_SAVE_NR 14 @@ -157,9 +189,17 @@ struct uart_amba_port { unsigned int im; /* interrupt mask */ unsigned int old_status; unsigned int fifosize; /* vendor-specific */ + unsigned int ifls; /* vendor-specific */ unsigned int lcrh_tx; /* vendor-specific */ unsigned int lcrh_rx; /* vendor-specific */ bool autorts; +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL + enum pl011_clk_states clk_state; /* actual clock state */ + struct delayed_work clk_off_work; /* work used for clock off */ + unsigned int clk_off_delay; /* clock off delay */ +#endif + struct regulator *regulator; + u32 backup[ARRAY_SIZE(backup_regs)]; char type[12]; bool interrupt_may_hang; /* vendor-specific */ #ifdef CONFIG_DMA_ENGINE @@ -1100,6 +1140,246 @@ static void pl011_lockup_wa(unsigned long data) tty->hw_stopped = 0; } +static void __pl011_startup(struct uart_amba_port *uap) +{ + unsigned int cr; + + writew(uap->ifls, uap->port.membase + UART011_IFLS); + + /* + * Provoke TX FIFO interrupt into asserting. + */ + cr = UART01x_CR_UARTEN | UART011_CR_TXE | UART011_CR_LBE; + writew(cr, uap->port.membase + UART011_CR); + writew(0, uap->port.membase + UART011_FBRD); + writew(1, uap->port.membase + UART011_IBRD); + writew(0, uap->port.membase + uap->lcrh_rx); + if (uap->lcrh_tx != uap->lcrh_rx) { + int i; + /* + * Wait 10 PCLKs before writing LCRH_TX register, + * to get this delay write read only register 10 times + */ + for (i = 0; i < 10; ++i) + writew(0xff, uap->port.membase + UART011_MIS); + writew(0, uap->port.membase + uap->lcrh_tx); + } + writew(0, uap->port.membase + UART01x_DR); + while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY) + barrier(); +} + +/* Backup the registers during regulator startup/shutdown */ +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL +static int pl011_backup(struct uart_amba_port *uap, bool suspend) +{ + int i, cnt; + + if (!suspend) { + __pl011_startup(uap); + writew(0, uap->port.membase + UART011_CR); + } + + for (i = 0; i < ARRAY_SIZE(backup_regs); i++) { + if (suspend) + uap->backup[i] = readw(uap->port.membase + + backup_regs[i]); + else { + if (backup_regs[i] == ST_UART011_LCRH_TX) { + /* + * Wait 10 PCLKs before writing LCRH_TX + * register, to get this delay write read + * only register 10 times + */ + for (cnt = 0; cnt < 10; ++cnt) + writew(0xff, uap->port.membase + + UART011_MIS); + } + + writew(uap->backup[i], + uap->port.membase + backup_regs[i]); + } + } + return 0; +} +#endif + +#ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL +/* Turn clock off if TX buffer is empty, otherwise reschedule */ +static void pl011_clock_off(struct work_struct *work) +{ + struct uart_amba_port *uap = container_of(work, struct uart_amba_port, + clk_off_work.work); + struct uart_port *port = &uap->port; + struct circ_buf *xmit = &port->state->xmit; + unsigned long flags; + bool disable_regulator = false; + unsigned int busy, interrupt_status; + + spin_lock_irqsave(&port->lock, flags); + + interrupt_status = readw(uap->port.membase + UART011_MIS); + busy = readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY; + + if (uap->clk_state == PL011_CLK_REQUEST_OFF) { + if (uart_circ_empty(xmit) && !interrupt_status && !busy) { + if (!uart_console(&uap->port) && uap->regulator) { + pl011_backup(uap, true); + disable_regulator = true; + } + uap->clk_state = PL011_CLK_OFF; + clk_disable(uap->clk); + } else + schedule_delayed_work(&uap->clk_off_work, + uap->clk_off_delay); + } + + spin_unlock_irqrestore(&port->lock, flags); + + if (disable_regulator) + regulator_disable(uap->regulator); +} + +/* Request to turn off uart clock once pending TX is flushed */ +static void pl011_clock_request_off(struct uart_port *port) +{ + unsigned long flags; + struct uart_amba_port *uap = (struct uart_amba_port *)(port); + + spin_lock_irqsave(&port->lock, flags); + + if (uap->clk_state == PL011_CLK_ON) { + uap->clk_state = PL011_CLK_REQUEST_OFF; + /* Turn off later */ + schedule_delayed_work(&uap->clk_off_work, + uap->clk_off_delay); + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +/* Request to immediately turn on uart clock */ +static void pl011_clock_on(struct uart_port *port) +{ + unsigned long flags; + struct uart_amba_port *uap = (struct uart_amba_port *)(port); + + spin_lock_irqsave(&port->lock, flags); + + switch (uap->clk_state) { + case PL011_CLK_OFF: + clk_enable(uap->clk); + if (!uart_console(&uap->port) && uap->regulator) { + spin_unlock_irqrestore(&port->lock, flags); + regulator_enable(uap->regulator); + spin_lock_irqsave(&port->lock, flags); + pl011_backup(uap, false); + } + /* fallthrough */ + case PL011_CLK_REQUEST_OFF: + cancel_delayed_work(&uap->clk_off_work); + uap->clk_state = PL011_CLK_ON; + break; + default: + break; + } + + spin_unlock_irqrestore(&port->lock, flags); +} + +static void pl011_clock_check(struct uart_amba_port *uap) +{ + /* Reshedule work during off request */ + if (uap->clk_state == PL011_CLK_REQUEST_OFF) + /* New TX - restart work */ + if (cancel_delayed_work(&uap->clk_off_work)) + schedule_delayed_work(&uap->clk_off_work, + uap->clk_off_delay); +} + +static int pl011_power_startup(struct uart_amba_port *uap) +{ + int retval = 0; + + if (uap->clk_state == PL011_PORT_OFF) { + if (!uart_console(&uap->port) && uap->regulator) + regulator_enable(uap->regulator); + retval = clk_enable(uap->clk); + if (!retval) + uap->clk_state = PL011_CLK_ON; + else + uap->clk_state = PL011_PORT_OFF; + } + + return retval; +} + +static void pl011_power_shutdown(struct uart_amba_port *uap) +{ + bool disable_regulator = false; + + cancel_delayed_work_sync(&uap->clk_off_work); + + spin_lock_irq(&uap->port.lock); + if (uap->clk_state == PL011_CLK_ON || + uap->clk_state == PL011_CLK_REQUEST_OFF) { + clk_disable(uap->clk); + if (!uart_console(&uap->port) && uap->regulator) + disable_regulator = true; + } + uap->clk_state = PL011_PORT_OFF; + spin_unlock_irq(&uap->port.lock); + + if (disable_regulator) + regulator_disable(uap->regulator); +} + +static void +pl011_clock_control(struct uart_port *port, struct ktermios *termios, + struct ktermios *old) +{ + speed_t new_baud = tty_termios_baud_rate(termios); + + if (new_baud == 0) + pl011_clock_request_off(port); + else + pl011_clock_on(port); +} + +static void pl011_clock_control_init(struct uart_amba_port *uap) +{ + uap->clk_state = PL011_PORT_OFF; + INIT_DELAYED_WORK(&uap->clk_off_work, pl011_clock_off); + uap->clk_off_delay = HZ / 10; /* 100 ms */ +} + +#else +/* Blank functions for clock control */ +static inline void pl011_clock_check(struct uart_amba_port *uap) +{ +} + +static inline int pl011_power_startup(struct uart_amba_port *uap) +{ + return clk_enable(uap->clk); +} + +static inline void pl011_power_shutdown(struct uart_amba_port *uap) +{ + clk_disable(uap->clk); +} + +static inline void +pl011_clock_control(struct uart_port *port, struct ktermios *termios, + struct ktermios *old) +{ +} + +static inline void pl011_clock_control_init(struct uart_amba_port *uap) +{ +} +#endif + static void pl011_stop_tx(struct uart_port *port) { struct uart_amba_port *uap = (struct uart_amba_port *)port; @@ -1191,6 +1471,9 @@ static void pl011_tx_chars(struct uart_amba_port *uap) break; } while (--count > 0); + if (count) + pl011_clock_check(uap); + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) uart_write_wakeup(&uap->port); @@ -1368,9 +1651,9 @@ static int pl011_startup(struct uart_port *port) int retval; /* - * Try to enable the clock producer. + * Try to enable the clock producer and the regulator. */ - retval = clk_enable(uap->clk); + retval = pl011_power_startup(uap); if (retval) goto out; @@ -1383,29 +1666,7 @@ static int pl011_startup(struct uart_port *port) if (retval) goto clk_dis; - writew(uap->vendor->ifls, uap->port.membase + UART011_IFLS); - - /* - * Provoke TX FIFO interrupt into asserting. - */ - cr = UART01x_CR_UARTEN | UART011_CR_TXE | UART011_CR_LBE; - writew(cr, uap->port.membase + UART011_CR); - writew(0, uap->port.membase + UART011_FBRD); - writew(1, uap->port.membase + UART011_IBRD); - writew(0, uap->port.membase + uap->lcrh_rx); - if (uap->lcrh_tx != uap->lcrh_rx) { - int i; - /* - * Wait 10 PCLKs before writing LCRH_TX register, - * to get this delay write read only register 10 times - */ - for (i = 0; i < 10; ++i) - writew(0xff, uap->port.membase + UART011_MIS); - writew(0, uap->port.membase + uap->lcrh_tx); - } - writew(0, uap->port.membase + UART01x_DR); - while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY) - barrier(); + __pl011_startup(uap); cr = UART01x_CR_UARTEN | UART011_CR_RXE | UART011_CR_TXE; writew(cr, uap->port.membase + UART011_CR); @@ -1445,7 +1706,7 @@ static int pl011_startup(struct uart_port *port) return 0; clk_dis: - clk_disable(uap->clk); + pl011_power_shutdown(uap); out: return retval; } @@ -1502,9 +1763,10 @@ static void pl011_shutdown(struct uart_port *port) } /* - * Shut down the clock producer + * Shut down the clock producer and the producer */ - clk_disable(uap->clk); + pl011_power_shutdown(uap); + if (uap->port.dev->platform_data) { struct amba_pl011_data *plat; @@ -1516,6 +1778,32 @@ static void pl011_shutdown(struct uart_port *port) } +/* Power/Clock management. */ +static void pl011_serial_pm(struct uart_port *port, unsigned int state, +unsigned int oldstate) +{ + struct uart_amba_port *uap = (struct uart_amba_port *)port; + + switch (state) { + case 0: /*fully on */ + /* + * Enable the peripheral clock for this serial port. + * This is called on uart_open() or a resume event. + */ + pl011_power_startup(uap); + break; + case 3: /* powered down */ + /* + * Disable the peripheral clock for this serial port. + * This is called on uart_close() or a suspend event. + */ + pl011_power_shutdown(uap); + break; + default: + printk(KERN_ERR "pl011_serial: unknown pm %d\n", state); + } +} + static void pl011_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) @@ -1529,7 +1817,12 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, clkdiv = 8; else clkdiv = 16; - + /* + * Must be before uart_get_baud_rate() call, because + * this function changes baudrate to default in case of 0 + * B0 hangup !!! + */ + pl011_clock_control(port, termios, old); /* * Ask the core to calculate the divisor for us. */ @@ -1717,6 +2010,7 @@ static struct uart_ops amba_pl011_pops = { .request_port = pl010_request_port, .config_port = pl010_config_port, .verify_port = pl010_verify_port, + .pm = pl011_serial_pm, #ifdef CONFIG_CONSOLE_POLL .poll_get_char = pl010_get_poll_char, .poll_put_char = pl010_put_poll_char, @@ -1861,7 +2155,6 @@ static struct uart_driver amba_reg = { .nr = UART_NR, .cons = AMBA_CONSOLE, }; - static int pl011_probe(struct amba_device *dev, const struct amba_id *id) { struct uart_amba_port *uap; @@ -1890,6 +2183,12 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) goto free; } + uap->regulator = regulator_get(&dev->dev, "v-uart"); + if (IS_ERR(uap->regulator)) { + dev_warn(&dev->dev, "could not get uart regulator\n"); + uap->regulator = NULL; + } + uap->clk = clk_get(&dev->dev, NULL); if (IS_ERR(uap->clk)) { ret = PTR_ERR(uap->clk); @@ -1897,6 +2196,7 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) } uap->vendor = vendor; + uap->ifls = vendor->ifls; uap->lcrh_rx = vendor->lcrh_rx; uap->lcrh_tx = vendor->lcrh_tx; uap->fifosize = vendor->fifosize; @@ -1917,6 +2217,9 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) amba_ports[i] = uap; amba_set_drvdata(dev, uap); + + pl011_clock_control_init(uap); + ret = uart_add_one_port(&amba_reg, &uap->port); if (ret) { amba_set_drvdata(dev, NULL); @@ -1924,6 +2227,8 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id) pl011_dma_remove(uap); clk_put(uap->clk); unmap: + if (uap->regulator) + regulator_put(uap->regulator); iounmap(base); free: kfree(uap); @@ -1947,6 +2252,8 @@ static int pl011_remove(struct amba_device *dev) pl011_dma_remove(uap); iounmap(uap->port.membase); + if (uap->regulator) + regulator_put(uap->regulator); clk_put(uap->clk); kfree(uap); return 0; -- cgit v1.2.3 From 28c5bb60fd3352682f8fca1123d61bab37478212 Mon Sep 17 00:00:00 2001 From: Johan Palmaeus Date: Tue, 13 Sep 2011 13:43:19 +0200 Subject: CG2900 FM Radio: Enable CG2900 FM Radio Several modules needed for CG2900 FM Radio was missing in u5500/u8500 defconfig files. This patch adds them. ST-Ericsson ID: 352334 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I720d40f0596c906bf4d2f52cddad098912ad4824 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/30832 Tested-by: Johan PALMAEUS Reviewed-by: Virupax SADASHIVPETIMATH --- arch/arm/configs/u8500_defconfig | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index fddc8248005..f417f11e417 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -211,8 +211,17 @@ CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y +CONFIG_VIDEO_V4L2_COMMON=y +CONFIG_VIDEO_ALLOW_V4L1=y +CONFIG_VIDEO_V4L1_COMPAT=y +CONFIG_VIDEO_MEDIA=y +CONFIG_MEDIA_TUNER=y # CONFIG_MEDIA_TUNER_CUSTOMISE is not set +CONFIG_VIDEO_V4L2=y +CONFIG_VIDEO_V4L1=y # CONFIG_VIDEO_CAPTURE_DRIVERS is not set +CONFIG_RADIO_ADAPTERS=y +CONFIG_RADIO_CG2900=y CONFIG_GPU_MALI=y CONFIG_GPU_MALI_DEBUG=y CONFIG_FB=y -- cgit v1.2.3 From 7a73bfdc79a7e1689af6035c74f45082f0961c37 Mon Sep 17 00:00:00 2001 From: Johan Palsson Date: Fri, 26 Aug 2011 14:01:10 +0200 Subject: power: ab8500-bm: Changed behaviour of the charging safety timer The safety timer will not be started until the capacity reported from the FG algorithm is 100%. Then we know that the amount of charge that's gone into the battery is enough for the battery to be full. If it has not reached end-of-charge before the safety timer has expired then we know that the battery is overcharged and charging will be stopped to protect the battery. ST-Ericsson ID: 358130 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: Trivial Change-Id: If3cfb49c03235dba09dc35d6b885ff22ea592aa9 Signed-off-by: Johan Palsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/29550 Reviewed-by: QABUILD Reviewed-by: Jonas ABERG Reviewed-by: Karl KOMIEROWSKI Reviewed-by: Andrew LYNN Tested-by: Andrew LYNN Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/30881 Reviewed-by: Arun MURTHY Tested-by: Arun MURTHY --- drivers/power/ab8500_chargalg.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/power/ab8500_chargalg.c b/drivers/power/ab8500_chargalg.c index cb56c67475c..97ea736424a 100644 --- a/drivers/power/ab8500_chargalg.c +++ b/drivers/power/ab8500_chargalg.c @@ -399,7 +399,8 @@ static void ab8500_chargalg_start_safety_timer(struct ab8500_chargalg *di) static void ab8500_chargalg_stop_safety_timer(struct ab8500_chargalg *di) { di->events.safety_timer_expired = false; - del_timer(&di->safety_timer); + if (timer_pending(&di->safety_timer)) + del_timer(&di->safety_timer); } /** @@ -851,6 +852,27 @@ static void handle_maxim_chg_curr(struct ab8500_chargalg *di) } } +static void ab8500_chargalg_check_safety_timer(struct ab8500_chargalg *di) +{ + /* + * The safety timer will not be started until the capacity reported + * from the FG algorithm is 100%. Then we know that the amount of + * charge that's gone into the battery is enough for the battery + * to be full. If it has not reached end-of-charge before the safety + * timer has expired then we know that the battery is overcharged + * and charging will be stopped to protect the battery. + */ + if (di->batt_data.percent == 100 && + !timer_pending(&di->safety_timer)) { + ab8500_chargalg_start_safety_timer(di); + dev_dbg(di->dev, "start safety timer\n"); + } else if (di->batt_data.percent != 100 && + timer_pending(&di->safety_timer)) { + ab8500_chargalg_stop_safety_timer(di); + dev_dbg(di->dev, "stop safety timer\n"); + } +} + static int ab8500_chargalg_get_ext_psy_data(struct device *dev, void *data) { struct power_supply *psy; @@ -1401,7 +1423,6 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) di->bat->bat_type[di->bat->batt_id].normal_vol_lvl, di->bat->bat_type[di->bat->batt_id].normal_cur_lvl); ab8500_chargalg_state_to(di, STATE_NORMAL); - ab8500_chargalg_start_safety_timer(di); ab8500_chargalg_stop_maintenance_timer(di); init_maxim_chg_curr(di); di->charge_status = POWER_SUPPLY_STATUS_CHARGING; @@ -1422,6 +1443,8 @@ static void ab8500_chargalg_algorithm(struct ab8500_chargalg *di) ab8500_chargalg_state_to(di, STATE_MAINTENANCE_A_INIT); } + /* Check whether we should start the safety timer or not */ + ab8500_chargalg_check_safety_timer(di); break; /* This state will be used when the maintenance state is disabled */ -- cgit v1.2.3 From 8f1b5e3e4ca59763a19112e917d410ffa2ff3e49 Mon Sep 17 00:00:00 2001 From: Magnus Persson Date: Mon, 5 Sep 2011 13:14:08 +0200 Subject: power: ab8500_charger: Fix detection of USB charger Check both VBUS_DET_DBNC1 and VBUS_DET_DBNC100 bits when detecting USB charger. ST-Ericsson ID: 358849 ST-Ericsson FOSS-OUT ID: Trivial ST-Ericsson Linux next: NA Change-Id: Ibca02e5bd89f556fe649eec7f75f105036bcff2f Signed-off-by: Magnus Persson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/30105 Reviewed-by: Daniel WILLERUD Reviewed-by: QABUILD Reviewed-by: Karl KOMIEROWSKI Reviewed-by: QATEST Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/30882 Reviewed-by: Johan PALSSON Reviewed-by: Arun MURTHY Tested-by: Arun MURTHY --- drivers/power/ab8500_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index b0c0453878f..7463b5db04e 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -440,7 +440,7 @@ static int ab8500_charger_detect_chargers(struct ab8500_charger *di) goto out; } - if (val & (VBUS_DET_DBNC100 | VBUS_DET_DBNC1)) + if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100)) result |= USB_PW_CONN; /* -- cgit v1.2.3 From 0d09d14f720012c0169e24032428dd620fe196a5 Mon Sep 17 00:00:00 2001 From: Kalle Komierowski Date: Mon, 12 Sep 2011 09:03:46 +0200 Subject: power: ab8500_charger: Lower VBUS input current when below threshold Due to a bug in AB8500 the input current to the VBUS charger needs to be lowered when the battery voltage is below 3,7 V. Otherwise there is a risk that a higher than allowed current is drawn from the USB host. ST-Ericsson ID: 338533 ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I7f32b94411459a1665cc1c2890051f4d5290ed3d Signed-off-by: Kalle Komierowski Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/30630 Reviewed-by: Johan PALSSON Reviewed-by: Jonas ABERG Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/30883 Reviewed-by: Arun MURTHY Tested-by: Arun MURTHY --- drivers/power/ab8500_charger.c | 127 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index 7463b5db04e..1b827a980ce 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -123,6 +123,8 @@ enum ab8500_usb_state { #define USB_CH_IP_CUR_LVL_1P4 1400 #define USB_CH_IP_CUR_LVL_1P5 1500 +#define VBAT_3700 3700 + #define to_ab8500_charger_usb_device_info(x) container_of((x), \ struct ab8500_charger, usb_chg) #define to_ab8500_charger_ac_device_info(x) container_of((x), \ @@ -173,6 +175,8 @@ struct ab8500_charger_usb_state { * VBUS detected during startup * @ac_conn: This will be true when the AC charger has been plugged * @vddadc_en: Indicate if VDD ADC supply is enabled from this driver + * @vbat Battery voltage + * @old_vbat Previously measured battery voltage * @parent: Pointer to the struct ab8500 * @gpadc: Pointer to the struct gpadc * @pdata: Pointer to the ab8500_charger platform data @@ -185,6 +189,7 @@ struct ab8500_charger_usb_state { * @usb: Structure that holds the USB charger properties * @regu: Pointer to the struct regulator * @charger_wq: Work queue for the IRQs and checking HW state + * @check_vbat_work Work for checking vbat threshold to adjust vbus current * @check_hw_failure_work: Work for checking HW state * @check_usbchgnotok_work: Work for checking USB charger not ok status * @kick_wd_work: Work for kicking the charger watchdog in case @@ -206,6 +211,8 @@ struct ab8500_charger { bool vbus_detected_start; bool ac_conn; bool vddadc_en; + int vbat; + int old_vbat; struct ab8500 *parent; struct ab8500_gpadc *gpadc; struct ab8500_charger_platform_data *pdata; @@ -218,6 +225,7 @@ struct ab8500_charger { struct ab8500_charger_info usb; struct regulator *regu; struct workqueue_struct *charger_wq; + struct delayed_work check_vbat_work; struct delayed_work check_hw_failure_work; struct delayed_work check_usbchgnotok_work; struct delayed_work kick_wd_work; @@ -881,6 +889,19 @@ static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di, /* We should always use to lowest current limit */ min_value = min(di->bat->chg_params->usb_curr_max, ich_in); + switch (min_value) { + case 100: + if (di->vbat < VBAT_3700) + min_value = USB_CH_IP_CUR_LVL_0P05; + break; + case 500: + if (di->vbat < VBAT_3700) + min_value = USB_CH_IP_CUR_LVL_0P45; + break; + default: + break; + } + input_curr_index = ab8500_vbus_in_curr_to_regval(min_value); if (input_curr_index < 0) { dev_err(di->dev, "VBUS input current limit too high\n"); @@ -1174,6 +1195,8 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger, if (ret < 0) dev_err(di->dev, "failed to enable LED\n"); + queue_delayed_work(di->charger_wq, &di->check_vbat_work, HZ); + di->usb.charger_online = 1; } else { /* Disable USB charging */ @@ -1193,6 +1216,11 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger, di->usb.charger_online = 0; di->usb.wd_expired = false; dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); + + /* Cancel any pending Vbat check work */ + if (delayed_work_pending(&di->check_vbat_work)) + cancel_delayed_work(&di->check_vbat_work); + } power_supply_changed(&di->usb_chg.psy); @@ -1274,6 +1302,101 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *charger, return ret; } +static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data) +{ + struct power_supply *psy; + struct power_supply *ext; + struct ab8500_charger *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + struct ux500_charger *usb_chg; + + usb_chg = (struct ux500_charger *)data; + psy = &usb_chg->psy; + + di = to_ab8500_charger_usb_device_info(usb_chg); + + ext = dev_get_drvdata(dev); + + /* For all psy where the driver name appears in any supplied_to */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->type) { + case POWER_SUPPLY_TYPE_BATTERY: + di->vbat = ret.intval / 1000; + break; + default: + break; + } + break; + default: + break; + } + } + return 0; +} + +/** + * ab8500_charger_check_vbat_work() - keep vbus current within spec + * @work pointer to the work_struct structure + * + * Due to a asic bug it is necessary to lower the input current to the vbus + * charger when charging with at some specific levels. This issue is only valid + * for below a certain battery voltage. This function makes sure that the + * the allowed current limit isn't exceeded. + */ +static void ab8500_charger_check_vbat_work(struct work_struct *work) +{ + int t = 10; + struct ab8500_charger *di = container_of(work, + struct ab8500_charger, check_vbat_work.work); + + class_for_each_device(power_supply_class, NULL, + &di->usb_chg.psy, ab8500_charger_get_ext_psy_data); + + /* First run old_vbat is 0. */ + if (di->old_vbat == 0) + di->old_vbat = di->vbat; + + if (!((di->old_vbat <= VBAT_3700 && di->vbat <= VBAT_3700) || + (di->old_vbat > VBAT_3700 && di->vbat > VBAT_3700))) { + dev_dbg(di->dev, "Vbat did cross threshold, curr: %d, new: %d," + " old: %d\n", di->max_usb_in_curr, di->vbat, + di->old_vbat); + ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); + power_supply_changed(&di->usb_chg.psy); + } + + di->old_vbat = di->vbat; + + /* + * No need to check the battery voltage every second when not close to + * the threshold. + */ + if (di->vbat < (VBAT_3700 + 100) && + (di->vbat > (VBAT_3700 - 100))) + t = 1; + + queue_delayed_work(di->charger_wq, &di->check_vbat_work, t * HZ); +} + /** * ab8500_charger_check_hw_failure_work() - check main charger failure * @work: pointer to the work_struct structure @@ -2398,7 +2521,6 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) INIT_DELAYED_WORK_DEFERRABLE(&di->check_usbchgnotok_work, ab8500_charger_check_usbchargernotok_work); - /* * For ABB revision 1.0 and 1.1 there is a bug in the watchdog * logic. That means we have to continously kick the charger @@ -2411,6 +2533,9 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev) INIT_DELAYED_WORK_DEFERRABLE(&di->kick_wd_work, ab8500_charger_kick_watchdog_work); + INIT_DELAYED_WORK_DEFERRABLE(&di->check_vbat_work, + ab8500_charger_check_vbat_work); + /* Init work for charger detection */ INIT_WORK(&di->usb_link_status_work, ab8500_charger_usb_link_status_work); -- cgit v1.2.3 From 3c8739856b8d55e8d3b861bc7cad38bf33608185 Mon Sep 17 00:00:00 2001 From: "Rajanikanth H.V" Date: Wed, 14 Sep 2011 15:26:32 +0530 Subject: UART:pl011: migrate pl011 support to 3.0 fix errors and warnings highlighted by checkpatch.pl ST-Ericsson ID: NA ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: NA Change-Id: I34755b1ffabec32a2beb942e59586c398cc5b642 Signed-off-by: Rajanikanth H.V --- drivers/tty/serial/amba-pl011.c | 173 +++++++++++++++++++++------------------- 1 file changed, 92 insertions(+), 81 deletions(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 638c2f63f66..9b6b7e22bfc 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -52,8 +52,8 @@ #include #include #include +#include -#include #include #define UART_NR 14 @@ -64,7 +64,8 @@ #define AMBA_ISR_PASS_LIMIT 256 -#define UART_DR_ERROR (UART011_DR_OE|UART011_DR_BE|UART011_DR_PE|UART011_DR_FE) +#define UART_DR_ERROR (UART011_DR_OE | UART011_DR_BE | \ + UART011_DR_PE | UART011_DR_FE) #define UART_DUMMY_DR_RX (1 << 16) /* @@ -345,7 +346,8 @@ static void pl011_dma_probe_initcall(struct uart_amba_port *uap) .src_maxburst = uap->fifosize >> 1, }; - chan = dma_request_channel(mask, plat->dma_filter, plat->dma_rx_param); + chan = dma_request_channel(mask, + plat->dma_filter, plat->dma_rx_param); if (!chan) { dev_err(uap->port.dev, "no RX DMA channel!\n"); return; @@ -789,8 +791,9 @@ static void pl011_dma_rx_chars(struct uart_amba_port *uap, */ if (dma_count == pending && readfifo) { /* Clear any error flags */ - writew(UART011_OEIS | UART011_BEIS | UART011_PEIS | UART011_FEIS, - uap->port.membase + UART011_ICR); + writew(UART011_OEIS | UART011_BEIS | + UART011_PEIS | UART011_FEIS, + uap->port.membase + UART011_ICR); /* * If we read all the DMA'd characters, and we had an @@ -987,11 +990,13 @@ static void pl011_dma_shutdown(struct uart_amba_port *uap) spin_unlock_irq(&uap->port.lock); if (uap->using_tx_dma) { - /* In theory, this should already be done by pl011_dma_flush_buffer */ + /* In theory, this should already be done by + * pl011_dma_flush_buffer + */ dmaengine_terminate_all(uap->dmatx.chan); if (uap->dmatx.queued) { - dma_unmap_sg(uap->dmatx.chan->device->dev, &uap->dmatx.sg, 1, - DMA_TO_DEVICE); + dma_unmap_sg(uap->dmatx.chan->device->dev, + &uap->dmatx.sg, 1, DMA_TO_DEVICE); uap->dmatx.queued = false; } @@ -1002,8 +1007,10 @@ static void pl011_dma_shutdown(struct uart_amba_port *uap) if (uap->using_rx_dma) { dmaengine_terminate_all(uap->dmarx.chan); /* Clean up the RX DMA */ - pl011_sgbuf_free(uap->dmarx.chan, &uap->dmarx.sgbuf_a, DMA_FROM_DEVICE); - pl011_sgbuf_free(uap->dmarx.chan, &uap->dmarx.sgbuf_b, DMA_FROM_DEVICE); + pl011_sgbuf_free(uap->dmarx.chan, &uap->dmarx.sgbuf_a, + DMA_FROM_DEVICE); + pl011_sgbuf_free(uap->dmarx.chan, &uap->dmarx.sgbuf_b, + DMA_FROM_DEVICE); uap->using_rx_dma = false; } } @@ -1142,31 +1149,31 @@ static void pl011_lockup_wa(unsigned long data) static void __pl011_startup(struct uart_amba_port *uap) { - unsigned int cr; - - writew(uap->ifls, uap->port.membase + UART011_IFLS); - - /* - * Provoke TX FIFO interrupt into asserting. - */ - cr = UART01x_CR_UARTEN | UART011_CR_TXE | UART011_CR_LBE; - writew(cr, uap->port.membase + UART011_CR); - writew(0, uap->port.membase + UART011_FBRD); - writew(1, uap->port.membase + UART011_IBRD); - writew(0, uap->port.membase + uap->lcrh_rx); - if (uap->lcrh_tx != uap->lcrh_rx) { - int i; - /* - * Wait 10 PCLKs before writing LCRH_TX register, - * to get this delay write read only register 10 times - */ - for (i = 0; i < 10; ++i) - writew(0xff, uap->port.membase + UART011_MIS); - writew(0, uap->port.membase + uap->lcrh_tx); - } - writew(0, uap->port.membase + UART01x_DR); - while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY) - barrier(); + unsigned int cr; + + writew(uap->ifls, uap->port.membase + UART011_IFLS); + + /* + * Provoke TX FIFO interrupt into asserting. + */ + cr = UART01x_CR_UARTEN | UART011_CR_TXE | UART011_CR_LBE; + writew(cr, uap->port.membase + UART011_CR); + writew(0, uap->port.membase + UART011_FBRD); + writew(1, uap->port.membase + UART011_IBRD); + writew(0, uap->port.membase + uap->lcrh_rx); + if (uap->lcrh_tx != uap->lcrh_rx) { + int i; + /* + * Wait 10 PCLKs before writing LCRH_TX register, + * to get this delay write read only register 10 times + */ + for (i = 0; i < 10; ++i) + writew(0xff, uap->port.membase + UART011_MIS); + writew(0, uap->port.membase + uap->lcrh_tx); + } + writew(0, uap->port.membase + UART01x_DR); + while (readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY) + barrier(); } /* Backup the registers during regulator startup/shutdown */ @@ -1175,10 +1182,10 @@ static int pl011_backup(struct uart_amba_port *uap, bool suspend) { int i, cnt; - if (!suspend) { - __pl011_startup(uap); - writew(0, uap->port.membase + UART011_CR); - } + if (!suspend) { + __pl011_startup(uap); + writew(0, uap->port.membase + UART011_CR); + } for (i = 0; i < ARRAY_SIZE(backup_regs); i++) { if (suspend) @@ -1217,16 +1224,16 @@ static void pl011_clock_off(struct work_struct *work) unsigned int busy, interrupt_status; spin_lock_irqsave(&port->lock, flags); - + interrupt_status = readw(uap->port.membase + UART011_MIS); busy = readw(uap->port.membase + UART01x_FR) & UART01x_FR_BUSY; if (uap->clk_state == PL011_CLK_REQUEST_OFF) { - if (uart_circ_empty(xmit) && !interrupt_status && !busy) { - if (!uart_console(&uap->port) && uap->regulator) { - pl011_backup(uap, true); - disable_regulator = true; - } + if (uart_circ_empty(xmit) && !interrupt_status && !busy) { + if (!uart_console(&uap->port) && uap->regulator) { + pl011_backup(uap, true); + disable_regulator = true; + } uap->clk_state = PL011_CLK_OFF; clk_disable(uap->clk); } else @@ -1321,7 +1328,7 @@ static void pl011_power_shutdown(struct uart_amba_port *uap) cancel_delayed_work_sync(&uap->clk_off_work); spin_lock_irq(&uap->port.lock); - if (uap->clk_state == PL011_CLK_ON || + if (uap->clk_state == PL011_CLK_ON || uap->clk_state == PL011_CLK_REQUEST_OFF) { clk_disable(uap->clk); if (!uart_console(&uap->port) && uap->regulator) @@ -1329,7 +1336,7 @@ static void pl011_power_shutdown(struct uart_amba_port *uap) } uap->clk_state = PL011_PORT_OFF; spin_unlock_irq(&uap->port.lock); - + if (disable_regulator) regulator_disable(uap->regulator); } @@ -1339,7 +1346,7 @@ pl011_clock_control(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { speed_t new_baud = tty_termios_baud_rate(termios); - + if (new_baud == 0) pl011_clock_request_off(port); else @@ -1564,7 +1571,7 @@ static unsigned int pl01x_get_mctrl(struct uart_port *port) #define TIOCMBIT(uartbit, tiocmbit) \ if (status & uartbit) \ - result |= tiocmbit + (result |= tiocmbit) TIOCMBIT(UART01x_FR_DCD, TIOCM_CAR); TIOCMBIT(UART01x_FR_DSR, TIOCM_DSR); @@ -1582,10 +1589,12 @@ static void pl011_set_mctrl(struct uart_port *port, unsigned int mctrl) cr = readw(uap->port.membase + UART011_CR); #define TIOCMBIT(tiocmbit, uartbit) \ - if (mctrl & tiocmbit) \ - cr |= uartbit; \ - else \ - cr &= ~uartbit + do {\ + if (mctrl & tiocmbit) \ + cr |= uartbit; \ + else \ + cr &= ~uartbit; \ + } while (0) TIOCMBIT(TIOCM_RTS, UART011_CR_RTS); TIOCMBIT(TIOCM_DTR, UART011_CR_DTR); @@ -1678,7 +1687,8 @@ static int pl011_startup(struct uart_port *port) /* * initialise the old status of the modem signals */ - uap->old_status = readw(uap->port.membase + UART01x_FR) & UART01x_FR_MODEM_ANY; + uap->old_status = readw(uap->port.membase + UART01x_FR) & + UART01x_FR_MODEM_ANY; /* Startup DMA */ pl011_dma_startup(uap); @@ -1714,11 +1724,11 @@ static int pl011_startup(struct uart_port *port) static void pl011_shutdown_channel(struct uart_amba_port *uap, unsigned int lcrh) { - unsigned long val; + unsigned long val; - val = readw(uap->port.membase + lcrh); - val &= ~(UART01x_LCRH_BRK | UART01x_LCRH_FEN); - writew(val, uap->port.membase + lcrh); + val = readw(uap->port.membase + lcrh); + val &= ~(UART01x_LCRH_BRK | UART01x_LCRH_FEN); + writew(val, uap->port.membase + lcrh); } static void pl011_shutdown(struct uart_port *port) @@ -1745,7 +1755,8 @@ static void pl011_shutdown(struct uart_port *port) * disable the port */ uap->autorts = false; - writew(UART01x_CR_UARTEN | UART011_CR_TXE, uap->port.membase + UART011_CR); + writew(UART01x_CR_UARTEN | UART011_CR_TXE, + uap->port.membase + UART011_CR); /* * disable break condition and fifos @@ -1785,22 +1796,22 @@ unsigned int oldstate) struct uart_amba_port *uap = (struct uart_amba_port *)port; switch (state) { - case 0: /*fully on */ - /* - * Enable the peripheral clock for this serial port. - * This is called on uart_open() or a resume event. - */ - pl011_power_startup(uap); - break; - case 3: /* powered down */ - /* - * Disable the peripheral clock for this serial port. - * This is called on uart_close() or a suspend event. - */ - pl011_power_shutdown(uap); - break; - default: - printk(KERN_ERR "pl011_serial: unknown pm %d\n", state); + case 0: /*fully on */ + /* + * Enable the peripheral clock for this serial port. + * This is called on uart_open() or a resume event. + */ + pl011_power_startup(uap); + break; + case 3: /* powered down */ + /* + * Disable the peripheral clock for this serial port. + * This is called on uart_close() or a suspend event. + */ + pl011_power_shutdown(uap); + break; + default: + printk(KERN_ERR "pl011_serial: unknown pm %d\n", state); } } @@ -1844,7 +1855,7 @@ pl011_set_termios(struct uart_port *port, struct ktermios *termios, case CS7: lcr_h = UART01x_LCRH_WLEN_7; break; - default: // CS8 + default: /* CS8 */ lcr_h = UART01x_LCRH_WLEN_8; break; } @@ -2267,10 +2278,10 @@ static int pl011_suspend(struct amba_device *dev, pm_message_t state) if (!uap) return -EINVAL; #ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL - cancel_delayed_work_sync(&uap->clk_off_work); + cancel_delayed_work_sync(&uap->clk_off_work); - if (uap->clk_state == PL011_CLK_OFF) - return 0; + if (uap->clk_state == PL011_CLK_OFF) + return 0; #endif return uart_suspend_port(&amba_reg, &uap->port); } @@ -2282,8 +2293,8 @@ static int pl011_resume(struct amba_device *dev) if (!uap) return -EINVAL; #ifdef CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL - if (uap->clk_state == PL011_CLK_OFF) - return 0; + if (uap->clk_state == PL011_CLK_OFF) + return 0; #endif return uart_resume_port(&amba_reg, &uap->port); -- cgit v1.2.3 From 04a7d8819a2675cef0b3003f04d8ff59119e04f1 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 15 Sep 2011 12:51:22 +0530 Subject: mfd: ab5500-core: Fix for setting platform data Change-Id: Ia8fe567b91b4e18ff737afce22c939e3b5f1cafc Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31011 --- drivers/mfd/ab5500-core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index e3dbd930cce..6e1b5396e7a 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -2538,6 +2538,7 @@ static int __init ab5500_probe(struct platform_device *pdev) /* Set up and register the platform devices. */ for (i = 0; i < AB5500_NUM_DEVICES; i++) { ab5500_devs[i].platform_data = ab5500_plf_data->dev_data[i]; + ab5500_devs[i].pdata_size = ab5500_plf_data->dev_data_sz[i]; } err = mfd_add_devices(&pdev->dev, 0, ab5500_devs, -- cgit v1.2.3 From cbb00ca79290e608bfe4d9d357d2660d332de13d Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 15 Sep 2011 12:54:09 +0530 Subject: power: ab5500: wrong dependancy in kconfig Correct the dependancy for enabling ab5500 battery charging driver. Change-Id: Ia2f0ba7d2631f19424c01efcf07ea6e088d8b784 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31012 --- drivers/power/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 86f782f8127..6df45ff76ba 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -250,7 +250,7 @@ config AB8500_BATTERY_THERM_ON_BATCTRL config AB5500_BM bool "AB5500 Battery Management Driver" - depends on AB5500_CORE && AB8500_GPADC && ARCH_U8500 + depends on AB5500_CORE && AB5500_GPADC && MACH_U5500 help Say Y to include support for AB5500 battery management. -- cgit v1.2.3 From e78cfdeae2781d52e4c1140516391e2a8e59504c Mon Sep 17 00:00:00 2001 From: Kalle Komierowski Date: Thu, 15 Sep 2011 11:30:19 +0200 Subject: ARM: configs: U8500 Disable BATCTRL ADC temperature measurements This is to be coherent with the kernel 2.3 configuration. ST-Ericsson ID: NA ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: NA Signed-off-by: Kalle Komierowski Change-Id: Ia87696d62b6bf0de05017c9c260cc5b313393811 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31036 Reviewed-by: Johan PALSSON Reviewed-by: Srinidhi KASAGAR Tested-by: Karl KOMIEROWSKI --- arch/arm/configs/u8500_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index f417f11e417..bf7bbdf5ec6 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -194,7 +194,7 @@ CONFIG_GPIO_TC3589X=y CONFIG_GPIO_AB8500=y CONFIG_POWER_SUPPLY=y CONFIG_AB8500_BM=y -CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=y +CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=n CONFIG_AB5500_BM=y CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL=y CONFIG_SENSORS_AB8500=y -- cgit v1.2.3 From 7c848d4abf0ec76a3f809e1bcede81f5cfd27ff5 Mon Sep 17 00:00:00 2001 From: psen Date: Fri, 16 Sep 2011 15:50:39 +0530 Subject: u8500: Change FORCE_MAX_ZONEORDER NMF Component Manager driver module uses for the time being dma_alloc_coherent to allocate contiguous SDRAM memory to store MMDSP's code and data: 8M for SIA and SVA data + 2M for SIA code + 2M for SVA code Change-Id: I2994bfaa522cdd49767940ae0488e88f90a291e7 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31251 Reviewed-by: Pankaj SEN Tested-by: Pankaj SEN Reviewed-by: Srinidhi KASAGAR --- arch/arm/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 661276a9632..959fc3738d2 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1610,7 +1610,9 @@ source "mm/Kconfig" config FORCE_MAX_ZONEORDER int "Maximum zone order" if ARCH_SHMOBILE range 11 64 if ARCH_SHMOBILE + depends on SA1111 || UX500_SOC_DB8500 default "9" if SA1111 + default "12" if UX500_SOC_DB8500 default "11" help The kernel memory allocator divides physically contiguous memory -- cgit v1.2.3 From 9c23b9b2d0248290d43c93fd3bc4f111929d0231 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 10:45:15 +0200 Subject: ab5500 mfd: usb:Migrate the patches to kernel 3.0 Following patches for usb driver are manually merged to kernel 3.0 d11e52a ux500: usb: add usb device mode support on u5500 1fecc55 ux500: usb: enabling adb and ACM functionality in u5500_defconfig 2847bf4 ux500: usb: error handling in musb for u5500 37f22da ux500: usb: U5500 v2 update e1b079d ux500: usb: Fix for enumeration when ON with cable 181701a ux500: usb: Fix for V2 emmc2 boot USB device issue 3a60721 mach-ux500: Handle the LinkStatus register. 086a83e usb: ux500: usb gpio enable/disable in pair Signed-off-by: Avinash Kumar --- include/linux/mfd/abx500/ab5500.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linux/mfd/abx500/ab5500.h b/include/linux/mfd/abx500/ab5500.h index 148f88fa8dd..3a35f2d4b52 100644 --- a/include/linux/mfd/abx500/ab5500.h +++ b/include/linux/mfd/abx500/ab5500.h @@ -156,6 +156,7 @@ struct ab5500_platform_data { unsigned int init_settings_sz; bool pm_power_off; struct ab5500_regulator_platform_data *regulator; + struct ab5500_usbgpio_platform_data *usb; }; struct ab5500_ponkey_platform_data { -- cgit v1.2.3 From 48c6c3f0d50b18ac6818474b42d56582e8bb38a9 Mon Sep 17 00:00:00 2001 From: om prakash Date: Fri, 16 Sep 2011 17:50:17 +0530 Subject: Disable STM_TRACE : Disable this for kernel3.0 Disabled CONFIG_STM_TRACE of u8500 for kernel3.0 Change-Id: I0c5c5b1f43d1fbb35c71a48445d71f10a893073b Signed-off-by: om prakash Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31266 Reviewed-by: Srinidhi KASAGAR --- arch/arm/configs/u8500_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index bf7bbdf5ec6..07a3fb06ad4 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -144,7 +144,7 @@ CONFIG_AB8500_PWM=y CONFIG_SENSORS_BH1780=y CONFIG_STE_TRACE_MODEM=y CONFIG_U8500_SIM_DETECT=y -CONFIG_STM_TRACE=y +# CONFIG_STM_TRACE=y CONFIG_DISPDEV=y # CONFIG_STE_AUDIO_IO_DEV is not set CONFIG_SCSI=y -- cgit v1.2.3 From e196fd0955cd57e5888302f4d6c495d3ba9845ad Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Wed, 21 Sep 2011 09:33:03 +0530 Subject: arm: u5500_defconfig: enable battery driver Change-Id: I2bdb4f72b0988b42382994171284cb41ceacc819 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31573 Reviewed-by: Rajanikanth H V --- arch/arm/configs/u8500_defconfig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 07a3fb06ad4..e3521979de3 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -193,11 +193,10 @@ CONFIG_GPIO_SYSFS=y CONFIG_GPIO_TC3589X=y CONFIG_GPIO_AB8500=y CONFIG_POWER_SUPPLY=y -CONFIG_AB8500_BM=y -CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=n +CONFIG_SENSORS_AB8500=y CONFIG_AB5500_BM=y CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL=y -CONFIG_SENSORS_AB8500=y +CONFIG_SENSORS_AB5500=y CONFIG_SENSORS_DBX500=y CONFIG_SENSORS_LSM303DLH=y CONFIG_SENSORS_L3G4200D=y -- cgit v1.2.3 From d495e305d92ad13fec6958e0d5147aca613f0440 Mon Sep 17 00:00:00 2001 From: Yann Gautier Date: Wed, 21 Sep 2011 15:24:48 +0200 Subject: Align u9500_defconfig on 8500 Change-Id: I288ea800be0a6acf7b31a4e247a0dd703ab97277 Change-Id: Iae584f8558f571952c6805ba3788f7241db70636 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31536 Reviewed-by: Yann GAUTIER Tested-by: Yann GAUTIER Reviewed-by: Christopher BLAIR --- arch/arm/configs/u8500_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index e3521979de3..76a68f780f6 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -78,6 +78,7 @@ CONFIG_IPV6=y # CONFIG_INET6_XFRM_MODE_TUNNEL is not set # CONFIG_INET6_XFRM_MODE_BEET is not set # CONFIG_IPV6_SIT is not set +# CONFIG_ANDROID_PARANOID_NETWORK is not set CONFIG_NETFILTER=y CONFIG_NF_CONNTRACK=y CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y @@ -194,6 +195,8 @@ CONFIG_GPIO_TC3589X=y CONFIG_GPIO_AB8500=y CONFIG_POWER_SUPPLY=y CONFIG_SENSORS_AB8500=y +CONFIG_AB8500_BM=y +CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=n CONFIG_AB5500_BM=y CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL=y CONFIG_SENSORS_AB5500=y -- cgit v1.2.3 From 97ad7c9703ddacbf034bc2d36ac0e75f814a5932 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Wed, 21 Sep 2011 11:22:59 +0530 Subject: power: ab5500-bm: update usb state change interface to otg notifier Remove usb_state_changed() exported function. Replace it with otg notifier call. This makes static_di redundant so it is removed. Moreover, there is no need to protect usb_state.usb_changed with usb_lock. So usb_lock is removed as well. Usb state is fabricated based on the reported mA. State is not as accurate as reading it directly from the usb controller. This should not matter as battery- manager driver should care more about mA than the state anyway. Change-Id: I180062a87ca773b5c215aee3ef1c60315d309d42 Signed-off-by: Arun Murthy Signed-off-by: Mian Yousaf Kaukab Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31577 Reviewed-by: Srinidhi KASAGAR --- drivers/power/ab5500_charger.c | 71 +++++++++++++++++++++++++++--------- include/linux/mfd/abx500/ab5500-bm.h | 4 -- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/drivers/power/ab5500_charger.c b/drivers/power/ab5500_charger.c index 8b5b081fe34..b4ac1ac6bb7 100644 --- a/drivers/power/ab5500_charger.c +++ b/drivers/power/ab5500_charger.c @@ -27,6 +27,7 @@ #include #include #include +#include /* Charger constants */ #define NO_PW_CONN 0 @@ -168,6 +169,11 @@ struct ab5500_charger_usb_state { * Work for checking Main thermal status * @check_usb_thermal_prot_work: * Work for checking USB thermal status + * @ otg: pointer to struct otg_transceiver, used to + * notify the current during a standard host + * charger. + * @nb: structture of type notifier_block, which has + * a function pointer referenced by usb driver. */ struct ab5500_charger { struct device *dev; @@ -190,15 +196,10 @@ struct ab5500_charger { struct work_struct usb_link_status_work; struct work_struct usb_state_changed_work; struct work_struct check_usb_thermal_prot_work; + struct otg_transceiver *otg; + struct notifier_block nb; }; -/* - * TODO: This variable is static in order to get information - * about maximum current and USB state from the USB driver - * This should be solved in a better way - */ -static struct ab5500_charger *static_di; - /* USB properties */ static enum power_supply_property ab5500_charger_usb_props[] = { POWER_SUPPLY_PROP_HEALTH, @@ -1130,15 +1131,16 @@ static void ab5500_charger_usb_link_status_work(struct work_struct *work) static void ab5500_charger_usb_state_changed_work(struct work_struct *work) { int ret; + unsigned long flags; struct ab5500_charger *di = container_of(work, struct ab5500_charger, usb_state_changed_work); if (!di->vbus_detected) return; - spin_lock(&di->usb_state.usb_lock); + spin_lock_irqsave(&di->usb_state.usb_lock, flags); di->usb_state.usb_changed = false; - spin_unlock(&di->usb_state.usb_lock); + spin_unlock_irqrestore(&di->usb_state.usb_lock, flags); /* * wait for some time until you get updates from the usb stack @@ -1521,25 +1523,41 @@ static struct ab5500_charger_interrupts ab5500_charger_irq[] = { /*{"CHG_SW_TIMER_OUT", ab5500_charger_chwdexp_handler},*/ }; -void ab5500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) +static int ab5500_charger_usb_notifier_call(struct notifier_block *nb, + unsigned long event, void *power) { - struct ab5500_charger *di = static_di; + struct ab5500_charger *di = + container_of(nb, struct ab5500_charger, nb); + enum ab5500_usb_state bm_usb_state; + unsigned mA = *((unsigned *)power); + + /* TODO: State is fabricate here. See if charger really needs USB + * state or if mA is enough + */ + if ((di->usb_state.usb_current == 2) && (mA > 2)) + bm_usb_state = AB5500_BM_USB_STATE_RESUME; + else if (mA == 0) + bm_usb_state = AB5500_BM_USB_STATE_RESET_HS; + else if (mA == 2) + bm_usb_state = AB5500_BM_USB_STATE_SUSPEND; + else if (mA >= 8) /* 8, 100, 500 */ + bm_usb_state = AB5500_BM_USB_STATE_CONFIGURED; + else /* Should never occur */ + bm_usb_state = AB5500_BM_USB_STATE_RESET_FS; dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n", __func__, bm_usb_state, mA); spin_lock(&di->usb_state.usb_lock); di->usb_state.usb_changed = true; - spin_unlock(&di->usb_state.usb_lock); - di->usb_state.state = bm_usb_state; di->usb_state.usb_current = mA; + spin_unlock(&di->usb_state.usb_lock); queue_work(di->charger_wq, &di->usb_state_changed_work); - return; + return NOTIFY_OK; } -EXPORT_SYMBOL(ab5500_charger_usb_state_changed); #if defined(CONFIG_PM) static int ab5500_charger_resume(struct platform_device *pdev) @@ -1585,6 +1603,9 @@ static int __devexit ab5500_charger_remove(struct platform_device *pdev) free_irq(irq, di); } + otg_unregister_notifier(di->otg, &di->nb); + otg_put_transceiver(di->otg); + /* Delete the work queue */ destroy_workqueue(di->charger_wq); @@ -1606,8 +1627,6 @@ static int __devinit ab5500_charger_probe(struct platform_device *pdev) if (!di) return -ENOMEM; - static_di = di; - /* get parent data */ di->dev = &pdev->dev; di->parent = dev_get_drvdata(pdev->dev.parent); @@ -1702,6 +1721,18 @@ static int __devinit ab5500_charger_probe(struct platform_device *pdev) goto free_device_info; } + di->otg = otg_get_transceiver(); + if (!di->otg) { + dev_err(di->dev, "failed to get otg transceiver\n"); + goto free_usb; + } + di->nb.notifier_call = ab5500_charger_usb_notifier_call; + ret = otg_register_notifier(di->otg, &di->nb); + if (ret) { + dev_err(di->dev, "failed to register otg notifier\n"); + goto put_otg_transceiver; + } + /* Identify the connected charger types during startup */ charger_status = ab5500_charger_detect_chargers(di); if (charger_status & USB_PW_CONN) { @@ -1734,13 +1765,17 @@ static int __devinit ab5500_charger_probe(struct platform_device *pdev) return ret; free_irq: - power_supply_unregister(&di->usb_chg.psy); + otg_unregister_notifier(di->otg, &di->nb); /* We also have to free all successfully registered irqs */ for (i = i - 1; i >= 0; i--) { irq = platform_get_irq_byname(pdev, ab5500_charger_irq[i].name); free_irq(irq, di); } +put_otg_transceiver: + otg_put_transceiver(di->otg); +free_usb: + power_supply_unregister(&di->usb_chg.psy); free_charger_wq: destroy_workqueue(di->charger_wq); free_device_info: diff --git a/include/linux/mfd/abx500/ab5500-bm.h b/include/linux/mfd/abx500/ab5500-bm.h index f62137a6e9d..cfad11c530c 100644 --- a/include/linux/mfd/abx500/ab5500-bm.h +++ b/include/linux/mfd/abx500/ab5500-bm.h @@ -99,13 +99,9 @@ #define BATTERY_UNKNOWN 0 #ifdef CONFIG_AB5500_BM -void ab5500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); struct ab5500_btemp *ab5500_btemp_get(void); int ab5500_btemp_get_batctrl_temp(struct ab5500_btemp *btemp); #else -static void ab5500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) -{ -} static inline struct ab5500_btemp *ab5500_btemp_get(void) { return 0; -- cgit v1.2.3 From 7b24e8e08c81418f67a8633bed29e1be4b935c52 Mon Sep 17 00:00:00 2001 From: Kalle Komierowski Date: Thu, 25 Aug 2011 11:16:07 +0200 Subject: power: ab8500_bm: Add interface to reinit FG algorithm A function has been added to enable reinitialisation of the fuel gauge algorithm. This can be used to to reinitialise with a different battery voltage at start-up, for verification reasons. ST-Ericsson ID: 357480 ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I1ba9775e3c26f49fe746e56c01dce0d5ba8384b3 Signed-off-by: Kalle Komierowski Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31676 Reviewed-by: Bibek BASU Tested-by: Bibek BASU --- drivers/power/ab8500_fg.c | 81 +++++++++++++++++++++++++++++++++++++++---- include/linux/mfd/ab8500/bm.h | 4 +++ 2 files changed, 79 insertions(+), 6 deletions(-) diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index 62b4adae0a6..ce7d9606667 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -175,6 +175,7 @@ struct inst_curr_result_list { * @fg_wq: Work queue for running the FG algorithm * @fg_periodic_work: Work to run the FG algorithm periodically * @fg_low_bat_work: Work to check low bat condition + * @fg_reinit_work Work used to reset and reinitialise the FG algorithm * @fg_work: Work to run the FG algorithm instantly * @fg_acc_cur_work: Work to read the FG accumulator * @cc_lock: Mutex for locking the CC @@ -223,6 +224,7 @@ struct ab8500_fg { struct workqueue_struct *fg_wq; struct delayed_work fg_periodic_work; struct delayed_work fg_low_bat_work; + struct delayed_work fg_reinit_work; struct work_struct fg_work; struct work_struct fg_acc_cur_work; struct mutex cc_lock; @@ -403,6 +405,28 @@ static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample) return avg->avg; } +/** + * ab8500_fg_clear_cap_samples() - Clear average filter + * @di: pointer to the ab8500_fg structure + * + * The capacity filter is is reset to zero. + */ +static void ab8500_fg_clear_cap_samples(struct ab8500_fg *di) +{ + int i; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + avg->pos = 0; + avg->nbr_samples = 0; + avg->sum = 0; + avg->avg = 0; + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = 0; + avg->time_stamps[i] = 0; + } +} + /** * ab8500_fg_fill_cap_sample() - Fill average filter * @di: pointer to the ab8500_fg structure @@ -752,8 +776,7 @@ exit: dev_err(di->dev, "Failed to read or write gas gauge registers\n"); mutex_unlock(&di->cc_lock); - queue_work(di->fg_wq, - &di->fg_work); + queue_work(di->fg_wq, &di->fg_work); } /** @@ -1332,8 +1355,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) AB8500_FG_DISCHARGE_RECOVERY); queue_delayed_work(di->fg_wq, - &di->fg_periodic_work, - 0); + &di->fg_periodic_work, 0); break; } @@ -1860,6 +1882,50 @@ static void ab8500_fg_external_power_changed(struct power_supply *psy) &di->fg_psy, ab8500_fg_get_ext_psy_data); } +/** + * abab8500_fg_reinit_work() - work to reset the FG algorithm + * @work: pointer to the work_struct structure + * + * Used to reset the current battery capacity to be able to + * retrigger a new voltage base capacity calculation. For + * test and verification purpose. + */ +static void ab8500_fg_reinit_work(struct work_struct *work) +{ + struct ab8500_fg *di = container_of(work, struct ab8500_fg, + fg_reinit_work.work); + + if (di->flags.calibrate == false) { + dev_dbg(di->dev, "Resetting FG state machine to init.\n"); + ab8500_fg_clear_cap_samples(di); + ab8500_fg_calc_cap_discharge_voltage(di, true); + ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT); + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); + queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); + + } else { + dev_err(di->dev, "Residual offset calibration ongoing " + "retrying..\n"); + /* Wait one second until next try*/ + queue_delayed_work(di->fg_wq, &di->fg_reinit_work, + round_jiffies(1)); + } +} + +/** + * ab8500_fg_reinit() - forces FG algorithm to reinitialize with current values + * + * This function can be used to force the FG algorithm to recalculate a new + * voltage based battery capacity. + */ +void ab8500_fg_reinit(void) +{ + struct ab8500_fg *di = ab8500_fg_get(); + /* User won't be notified if a null pointer returned. */ + if (di != NULL) + queue_delayed_work(di->fg_wq, &di->fg_reinit_work, 0); +} + /* Exposure to the sysfs interface */ struct ab8500_fg_sysfs_entry { @@ -1887,7 +1953,6 @@ static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf, di->bat_cap.max_mah = (int) charge_full; ret = count; } - return ret; } static struct ab8500_fg_sysfs_entry charge_full_attr = @@ -1907,7 +1972,6 @@ ab8500_fg_show(struct kobject *kobj, struct attribute *attr, char *buf) return entry->show(di, buf); } - static ssize_t ab8500_fg_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) @@ -2134,6 +2198,10 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev) /* Init work for getting the battery accumulated current */ INIT_WORK(&di->fg_acc_cur_work, ab8500_fg_acc_cur_work); + /* Init work for reinitialising the fg algorithm */ + INIT_DELAYED_WORK_DEFERRABLE(&di->fg_reinit_work, + ab8500_fg_reinit_work); + /* Work delayed Queue to run the state machine */ INIT_DELAYED_WORK_DEFERRABLE(&di->fg_periodic_work, ab8500_fg_periodic_work); @@ -2189,6 +2257,7 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev) /* Calibrate the fg first time */ di->flags.calibrate = true; di->calib_state = AB8500_FG_CALIB_INIT; + /* Run the FG algorithm */ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0); diff --git a/include/linux/mfd/ab8500/bm.h b/include/linux/mfd/ab8500/bm.h index 0bd8703b2bd..1fb67d25deb 100644 --- a/include/linux/mfd/ab8500/bm.h +++ b/include/linux/mfd/ab8500/bm.h @@ -474,6 +474,7 @@ struct ab8500_btemp; struct ab8500_gpadc; struct ab8500_fg; #ifdef CONFIG_AB8500_BM +void ab8500_fg_reinit(void); void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA); struct ab8500_btemp *ab8500_btemp_get(void); int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp); @@ -481,6 +482,9 @@ struct ab8500_fg *ab8500_fg_get(void); int ab8500_fg_inst_curr_blocking(struct ab8500_fg *dev); int ab8500_fg_inst_curr_nonblocking(struct ab8500_fg *dev, int *local_result); #else +static void ab8500_fg_reinit(void) +{ +} static void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA) { } -- cgit v1.2.3 From 2ccf4112a2d647549fecb1b58c330ab51609db3a Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Mon, 29 Aug 2011 08:32:36 +0200 Subject: mfd: ab8500-gpadc: Change to mdelay The resolution of msleep is related to HZ, so with HZ set to 100 any msleep of less than 10ms will become ~10ms. ST-Ericsson ID: - ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Jonas Aaberg Change-Id: I3b70c983f332605eea5826ca76bb2a8995de9d3b Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31735 Reviewed-by: Bibek BASU Tested-by: Bibek BASU --- drivers/mfd/ab8500-gpadc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index 4eeac415d84..0794abb86a6 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -338,7 +338,7 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) * Delay might be needed for ABB8500 cut 3.0, if not, remove * when hardware will be availible */ - msleep(1); + mdelay(1); break; } /* Intentional fallthrough */ -- cgit v1.2.3 From 007e2179ba537a0e429a10e7e71d2c664e44d493 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 11:02:18 +0200 Subject: ab5500 mfd:input:misc: Add accessory driver Add driver for the accessory detection block of the ab5500 PMIC. The common functions from the 8500 accessory driver are moved to abx500-accdet generic driver. This generic driver uses callbacks registerd from the 8500 and 5500 specific driver to work as a accessory driver for a perticluar platform. Signed-off-by: Virupax Sadashivpetimath Signed-off-by: Robert Marklund --- drivers/mfd/ab5500-core.c | 67 +++++++++++++++++++++++++++++++++++++++ include/linux/mfd/abx500/ab5500.h | 2 ++ 2 files changed, 69 insertions(+) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index 6e1b5396e7a..e687a148f00 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -805,6 +805,73 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, }, }, + [AB5500_DEVID_ACCDET] = { + .name = "ab5500-acc-det", + .id = AB5500_DEVID_ACCDET, + .num_resources = 10, + .resources = (struct resource[]) { + { + .name = "acc_detedt22db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 7), + .end = AB5500_IRQ(2, 7), + }, + { + .name = "acc_detedt21db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 6), + .end = AB5500_IRQ(2, 6), + }, + { + .name = "acc_detedt21db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(2, 5), + .end = AB5500_IRQ(2, 5), + }, + { + .name = "acc_detedt3db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 4), + .end = AB5500_IRQ(3, 4), + }, + { + .name = "acc_detedt3db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 3), + .end = AB5500_IRQ(3, 3), + }, + { + .name = "acc_detedt1db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 2), + .end = AB5500_IRQ(3, 2), + }, + { + .name = "acc_detedt1db_rising", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 1), + .end = AB5500_IRQ(3, 1), + }, + { + .name = "acc_detedt22db_falling", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(3, 0), + .end = AB5500_IRQ(3, 0), + }, + { + .name = "plugTVdet", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(13, 7), + .end = AB5500_IRQ(13, 7), + }, + { + .name = "plugTVdet_removal", + .flags = IORESOURCE_IRQ, + .start = AB5500_IRQ(23, 2), + .end = AB5500_IRQ(23, 2), + }, + }, + }, }; static ssize_t show_chip_id(struct device *dev, diff --git a/include/linux/mfd/abx500/ab5500.h b/include/linux/mfd/abx500/ab5500.h index 3a35f2d4b52..7c200c483b0 100644 --- a/include/linux/mfd/abx500/ab5500.h +++ b/include/linux/mfd/abx500/ab5500.h @@ -29,6 +29,7 @@ enum ab5500_devid { AB5500_DEVID_CHARGALG, AB5500_DEVID_BTEMP, AB5500_DEVID_TEMPMON, + AB5500_DEVID_ACCDET, AB5500_NUM_DEVICES, }; @@ -157,6 +158,7 @@ struct ab5500_platform_data { bool pm_power_off; struct ab5500_regulator_platform_data *regulator; struct ab5500_usbgpio_platform_data *usb; + struct abx500_accdet_platform_data *accdet; }; struct ab5500_ponkey_platform_data { -- cgit v1.2.3 From b50b5b2b130fa6040fdb79b46e578c672f3e28c4 Mon Sep 17 00:00:00 2001 From: Joakim Bech Date: Thu, 22 Sep 2011 16:11:07 +0200 Subject: ux500 cryp: Disable u8500 crypto hardware This patch disable the crypto hardware in u8500. ST-Ericsson ID: 363148 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I20da80897c88e7e1ff3b53e8c549dd4bd3b4e84a Signed-off-by: Joakim Bech Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31788 Reviewed-by: Berne HEBARK --- arch/arm/configs/u8500_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 76a68f780f6..2438432bc36 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -366,6 +366,6 @@ CONFIG_CRYPTO_ARC4=y CONFIG_CRYPTO_TWOFISH=y # CONFIG_CRYPTO_ANSI_CPRNG is not set CONFIG_CRYPTO_DEV_UX500=y -CONFIG_CRYPTO_DEV_UX500_CRYP=y +# CONFIG_CRYPTO_DEV_UX500_CRYP is not set CONFIG_CRC7=y CONFIG_LIBCRC32C=m -- cgit v1.2.3 From d6dabbb206c63a2d36a42e4f9697fa07248a1042 Mon Sep 17 00:00:00 2001 From: Virupax Sadashivpetimath Date: Mon, 26 Sep 2011 18:22:53 +0530 Subject: u5500_defconfig: Refresh the defconfig file make ARCH=arm u5500_defconfig make ARCH=arm savedefconfig cp defconfig arch/arm/configs/u5500_defconfig git add arch/arm/configs/u5500_defconfig Change-Id: Icc525eaf6c6eb1a205be5f74e9a4881a5c20f9de Signed-off-by: Virupax Sadashivpetimath Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31983 Reviewed-by: Rabin VINCENT --- arch/arm/configs/u8500_defconfig | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 2438432bc36..3646b843b01 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -78,7 +78,6 @@ CONFIG_IPV6=y # CONFIG_INET6_XFRM_MODE_TUNNEL is not set # CONFIG_INET6_XFRM_MODE_BEET is not set # CONFIG_IPV6_SIT is not set -# CONFIG_ANDROID_PARANOID_NETWORK is not set CONFIG_NETFILTER=y CONFIG_NF_CONNTRACK=y CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y @@ -90,6 +89,7 @@ CONFIG_NF_NAT=y CONFIG_IP_NF_TARGET_MASQUERADE=y CONFIG_PHONET=y CONFIG_NET_SCHED=y +# CONFIG_IPV6 is not set CONFIG_BT_L2CAP=y CONFIG_BT_SCO=y CONFIG_BT_RFCOMM=y @@ -98,7 +98,6 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y - CONFIG_AVERAGE=y #WIRELESS @@ -209,20 +208,14 @@ CONFIG_MFD_TC3589X=y CONFIG_AB5500_CORE=y CONFIG_AB8500_CORE=y CONFIG_REGULATOR_DEBUG=y +CONFIG_REGULATOR_DUMMY=y +CONFIG_REGULATOR_FIXED_VOLTAGE=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y -CONFIG_VIDEO_V4L2_COMMON=y -CONFIG_VIDEO_ALLOW_V4L1=y -CONFIG_VIDEO_V4L1_COMPAT=y -CONFIG_VIDEO_MEDIA=y -CONFIG_MEDIA_TUNER=y # CONFIG_MEDIA_TUNER_CUSTOMISE is not set -CONFIG_VIDEO_V4L2=y -CONFIG_VIDEO_V4L1=y # CONFIG_VIDEO_CAPTURE_DRIVERS is not set -CONFIG_RADIO_ADAPTERS=y CONFIG_RADIO_CG2900=y CONFIG_GPU_MALI=y CONFIG_GPU_MALI_DEBUG=y @@ -260,13 +253,20 @@ CONFIG_USB=y CONFIG_USB_SUSPEND=y # CONFIG_USB_OTG_WHITELIST is not set CONFIG_USB_MON=y +# CONFIG_SND_ARM is not set +# CONFIG_SND_SPI is not set +CONFIG_SND_SOC=y +CONFIG_SND_SOC_UX500=y +CONFIG_SND_SOC_U5500=y +CONFIG_SND_SOC_UX500_AB5500=y CONFIG_USB_MUSB_HDRC=y CONFIG_USB_MUSB_UX500=y -CONFIG_USB_MUSB_OTG=y CONFIG_USB_GADGET_MUSB_HDRC=y CONFIG_USB_ACM=y CONFIG_USB_STORAGE=y CONFIG_USB_LIBUSUAL=y +CONFIG_MUSB_PIO_ONLY=y +CONFIG_USB_MUSB_DEBUG=y CONFIG_USB_GADGET=y CONFIG_USB_GADGET_VBUS_DRAW=500 CONFIG_AB8500_USB=y @@ -307,6 +307,7 @@ CONFIG_MODEM=y CONFIG_MODEM_U8500=y CONFIG_U8500_SHRM=y CONFIG_U8500_SHRM_MODEM_SILENT_RESET=y +CONFIG_U5500_MMIO=y CONFIG_EXT2_FS=y CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y @@ -359,6 +360,8 @@ CONFIG_DEBUG_INFO=y CONFIG_SYSCTL_SYSCALL_CHECK=y CONFIG_FUNCTION_TRACER=y CONFIG_DEBUG_USER=y +CONFIG_DEBUG_LL=y +CONFIG_EARLY_PRINTK=y CONFIG_KEYS=y CONFIG_CRYPTO_MD5=m CONFIG_CRYPTO_SHA1=m -- cgit v1.2.3 From 5b56a0b33cc1d3ecedcc84bc75cb4ed9478ee996 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 11:29:08 +0200 Subject: ARM: u8500: config: Not every u8500 has a TPS6105 Do not always select TPS6105X, but enable it via defconfig Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31951 Reviewed-by: Srinidhi KASAGAR --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 3646b843b01..b81710aba7a 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -205,6 +205,7 @@ CONFIG_SENSORS_L3G4200D=y CONFIG_WATCHDOG=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y +CONFIG_TPS6105X=y CONFIG_AB5500_CORE=y CONFIG_AB8500_CORE=y CONFIG_REGULATOR_DEBUG=y -- cgit v1.2.3 From e1626ba29d8b6f6bc34cc264906612a624f3b11b Mon Sep 17 00:00:00 2001 From: Rajagopala V Date: Mon, 26 Sep 2011 18:05:36 +0530 Subject: abx500: support for ab5500 die temp monitoring add support for ab5500 die temperature monitoring as hardware supports reading input temperature ST-Ericsson Linux next: NA ST-Ericsson ID: 339643 ST-Ericsson FOSS-OUT ID: NA Change-Id: I26db88645c4e627829211366bdc1df22ad8ac067 Signed-off-by: Rajagopala V Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31981 Reviewed-by: Srinidhi KASAGAR --- drivers/hwmon/ab5500.c | 6 ++++++ drivers/hwmon/ab8500.c | 22 ++++++++++++++++------ drivers/hwmon/abx500.c | 25 ++++++++++++++++++++++++- drivers/hwmon/abx500.h | 2 ++ 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/drivers/hwmon/ab5500.c b/drivers/hwmon/ab5500.c index 79540c9a79b..7352e07ffde 100644 --- a/drivers/hwmon/ab5500.c +++ b/drivers/hwmon/ab5500.c @@ -139,6 +139,11 @@ static int ab5500_temp_shutdown_auto(struct abx500_temp *data) return ret; } +static int ab5500_is_visible(struct attribute *attr, int n) +{ + return attr->mode; +} + static int ab5500_temp_irq_handler(int irq, struct abx500_temp *data) { /* @@ -195,6 +200,7 @@ int __init abx500_hwmon_init(struct abx500_temp *data) data->ops.irq_handler = ab5500_temp_irq_handler; data->ops.show_name = ab5500_show_name; data->ops.show_label = ab5500_show_label; + data->ops.is_visible = ab5500_is_visible; return 0; } diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c index 67118a4f11d..9e81445322b 100644 --- a/drivers/hwmon/ab8500.c +++ b/drivers/hwmon/ab8500.c @@ -41,13 +41,8 @@ static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor) * Special treatment for the BAT_CTRL node, since this * temperature measurement is more complex than just * an ADC readout - * - * DIE_TEMP input temperature reading is not supported - * in AB8500 */ - if (sensor == DIE_TEMP) - val = 0; - else if (sensor == BAT_CTRL) + if (sensor == BAT_CTRL) val = ab8500_btemp_get_batctrl_temp(data->ab8500_btemp); else val = ab8500_gpadc_convert(data->ab8500_gpadc, sensor); @@ -107,6 +102,20 @@ static ssize_t ab8500_show_label(struct device *dev, return sprintf(buf, "%s\n", name); } +static int ab8500_is_visible(struct attribute *attr, int n) +{ + if (!strcmp(attr->name, "temp5_input") || + !strcmp(attr->name, "temp5_min") || + !strcmp(attr->name, "temp5_max") || + !strcmp(attr->name, "temp5_max_hyst") || + !strcmp(attr->name, "temp5_min_alarm") || + !strcmp(attr->name, "temp5_max_alarm") || + !strcmp(attr->name, "temp5_max_hyst_alarm")) + return 0; + + return attr->mode; +} + static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) { unsigned long delay_in_jiffies; @@ -168,6 +177,7 @@ int __init abx500_hwmon_init(struct abx500_temp *data) data->ops.irq_handler = ab8500_temp_irq_handler; data->ops.show_name = ab8500_show_name; data->ops.show_label = ab8500_show_label; + data->ops.is_visible = ab8500_is_visible; return 0; } diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c index 68da2d03dbd..b26a13e8438 100644 --- a/drivers/hwmon/abx500.c +++ b/drivers/hwmon/abx500.c @@ -436,6 +436,14 @@ static ssize_t show_crit_alarm(struct device *dev, return sprintf(buf, "%ld\n", data->crit_alarm[attr->index - 1]); } +static mode_t abx500_attrs_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct abx500_temp *data = dev_get_drvdata(dev); + return data->ops.is_visible(a, n); +} + static SENSOR_DEVICE_ATTR(temp_monitor_delay, S_IRUGO | S_IWUSR, show_temp_monitor_delay, set_temp_monitor_delay, 0); static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, @@ -493,9 +501,17 @@ static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_max_alarm, NULL, 4); static SENSOR_DEVICE_ATTR(temp4_max_hyst_alarm, S_IRUGO, show_max_hyst_alarm, NULL, 4); -/* GPADC - SENSOR4 */ +/* GPADC - SENSOR5 */ static SENSOR_DEVICE_ATTR(temp5_label, S_IRUGO, show_label, NULL, 5); static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_input, NULL, 5); +static SENSOR_DEVICE_ATTR(temp5_min, S_IWUSR | S_IRUGO, show_min, set_min, 4); +static SENSOR_DEVICE_ATTR(temp5_max, S_IWUSR | S_IRUGO, show_max, set_max, 4); +static SENSOR_DEVICE_ATTR(temp5_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 4); +static SENSOR_DEVICE_ATTR(temp5_min_alarm, S_IRUGO, show_min_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, show_max_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp5_max_hyst_alarm, S_IRUGO, + show_max_hyst_alarm, NULL, 4); static SENSOR_DEVICE_ATTR(temp5_crit_alarm, S_IRUGO, show_crit_alarm, NULL, 5); @@ -542,12 +558,19 @@ struct attribute *abx500_temp_attributes[] = { /* GPADC SENSOR5*/ &sensor_dev_attr_temp5_label.dev_attr.attr, &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_min.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp5_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp5_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_max_hyst_alarm.dev_attr.attr, &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, NULL }; static const struct attribute_group abx500_temp_group = { .attrs = abx500_temp_attributes, + .is_visible = abx500_attrs_visible, }; static irqreturn_t abx500_temp_irq_handler(int irq, void *irq_data) diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h index 77ae7cbd5c1..65a9a8238ba 100644 --- a/drivers/hwmon/abx500.h +++ b/drivers/hwmon/abx500.h @@ -22,6 +22,7 @@ struct abx500_temp; * @irq_handler: irq handler * @show_name: hwmon device name * @show_label: hwmon attribute label + * @is_visible: is attribute visible */ struct abx500_temp_ops { int (*read_sensor)(struct abx500_temp *, u8); @@ -30,6 +31,7 @@ struct abx500_temp_ops { struct device_attribute *, char *); ssize_t (*show_label) (struct device *, struct device_attribute *, char *); + int (*is_visible)(struct attribute *, int); }; /** -- cgit v1.2.3 From 006de2925406d8e12c1bb57323bc4bba48ab17b6 Mon Sep 17 00:00:00 2001 From: Per Persson Date: Tue, 27 Sep 2011 12:50:47 +0200 Subject: arm: u8500: Disable HDMI fb auto creation In order to support dynamic frame buffer creation when HDMI cable is plugged, the Auto creation config flag is default disabled. ST-Ericsson ID: 364125 ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I4098a7640a91c7f2695748a7a7ddf96f16e7981b Signed-off-by: Per Persson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32171 Reviewed-by: Jimmy RUBIN --- arch/arm/configs/u8500_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index b81710aba7a..750eefed0ee 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -228,7 +228,7 @@ CONFIG_FB_SYS_FOPS=y CONFIG_FB_MCDE=y CONFIG_MCDE_DISPLAY_GENERIC_DSI=y CONFIG_MCDE_DISPLAY_AV8100=y -CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE=y +# CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE is not set CONFIG_FB_B2R2=y CONFIG_B2R2_PLUG_CONF=y CONFIG_B2R2_OPSIZE_64=y -- cgit v1.2.3 From bb3c324fc1ed23530bb7b4e3c9200bd2ef8b8f45 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 12:05:43 +0200 Subject: ARM: ux500: Remove powersave Since we don't know really the status of powersave related feature on this development track, let's remove everything, except: - prcmu driver: New location, which is the same as linux-next - cpufreq: new location, same as linux-next - regulator: keep as is for the moment, needed for the system to work. - clocks: same as for regulator. - pm-runtime: keep it as is. Later patches will re-add powersave, based upon what exists on SI u8500-android-2.3_v3.15 MTU from plat-nomadik is now used instead of own copy in mach. Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32048 --- arch/arm/configs/u8500_defconfig | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 750eefed0ee..d4873e9a9e0 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -23,10 +23,7 @@ CONFIG_ARCH_U8500=y CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_HREFV60=y CONFIG_MACH_SNOWBALL=y -CONFIG_UX500_PRCMU_TIMER=y CONFIG_U8500_PRCMU=y -CONFIG_UX500_PRCMU_DEBUG=y -CONFIG_UX500_DEBUG_UART=0 CONFIG_U8500_REGULATOR_DEBUG=y CONFIG_DB8500_MLOADER=y CONFIG_DISPLAY_GENERIC_PRIMARY=y @@ -37,13 +34,6 @@ CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_ROTATION_ANGLE=90 CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y -CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 -CONFIG_U8500_CPUIDLE_DEBUG=y -CONFIG_UX500_SUSPEND=y -CONFIG_UX500_SUSPEND_STANDBY=y -CONFIG_UX500_SUSPEND_MEM=y -CONFIG_UX500_SUSPEND_DBG=y -CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y @@ -54,13 +44,6 @@ CONFIG_HIGHMEM=y CONFIG_CMDLINE="root=/dev/ram0 init=init rw console=ttyAMA2,115200n8 mem=256M initrd=0x800000,72M" CONFIG_KEXEC=y CONFIG_CRASH_DUMP=y -CONFIG_CPU_FREQ=y -CONFIG_CPU_FREQ_STAT_DETAILS=y -CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y -CONFIG_CPU_FREQ_GOV_POWERSAVE=y -CONFIG_CPU_FREQ_GOV_USERSPACE=y -CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y -CONFIG_CPU_IDLE=y CONFIG_FPE_NWFPE=y CONFIG_VFP=y CONFIG_NEON=y -- cgit v1.2.3 From d9d3241d3b313d3bcb5b0344f54f1bd6c49830be Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 13:04:22 +0200 Subject: ARM: ux500: core: Replace mach prcmu driver with mainlined version The prcmu driver now exists in drivers/mfd Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32057 --- arch/arm/configs/u8500_defconfig | 2 +- drivers/mfd/ab5500-core.c | 2 +- drivers/mfd/ab8500-i2c.c | 3 +-- drivers/watchdog/ux500_wdt.c | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index d4873e9a9e0..f349e7c1981 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -23,7 +23,6 @@ CONFIG_ARCH_U8500=y CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_HREFV60=y CONFIG_MACH_SNOWBALL=y -CONFIG_U8500_PRCMU=y CONFIG_U8500_REGULATOR_DEBUG=y CONFIG_DB8500_MLOADER=y CONFIG_DISPLAY_GENERIC_PRIMARY=y @@ -188,6 +187,7 @@ CONFIG_SENSORS_L3G4200D=y CONFIG_WATCHDOG=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y +CONFIG_MFD_DB8500_PRCMU=y CONFIG_TPS6105X=y CONFIG_AB5500_CORE=y CONFIG_AB8500_CORE=y diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index e687a148f00..eac3818f70d 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -30,7 +30,7 @@ #include #include #include -#include +#include #define AB5500_NAME_STRING "ab5500" #define AB5500_ID_FORMAT_STRING "AB5500 %s" diff --git a/drivers/mfd/ab8500-i2c.c b/drivers/mfd/ab8500-i2c.c index 9c64a9ebbbc..3a94ec80192 100644 --- a/drivers/mfd/ab8500-i2c.c +++ b/drivers/mfd/ab8500-i2c.c @@ -11,8 +11,7 @@ #include #include #include -#include - +#include static int ab8500_i2c_write(struct ab8500 *ab8500, u16 addr, u8 data) { diff --git a/drivers/watchdog/ux500_wdt.c b/drivers/watchdog/ux500_wdt.c index b620200ea3e..ccfb3788930 100644 --- a/drivers/watchdog/ux500_wdt.c +++ b/drivers/watchdog/ux500_wdt.c @@ -17,8 +17,7 @@ #include #include #include - -#include +#include #define WATCHDOG_TIMEOUT 600 /* 10 minutes */ -- cgit v1.2.3 From b693a9ab822de71a69ba7f3680930d79c212e865 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 13:20:37 +0200 Subject: ARM: ux500: config: Add prcmu qos power Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32060 --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index f349e7c1981..daf3aac6c2e 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -33,6 +33,7 @@ CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_ROTATION_ANGLE=90 CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y +CONFIG_DBX500_PRCMU_QOS_POWER=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y -- cgit v1.2.3 From 75929c723b69a026857ed6fb0b0afde329ff2d8e Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 13:27:03 +0200 Subject: ARM: ux500: config: Add prcmu-debug Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32061 --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index daf3aac6c2e..20f1b3a546f 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -34,6 +34,7 @@ CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y CONFIG_DBX500_PRCMU_QOS_POWER=y +CONFIG_DBX500_PRCMU_DEBUG=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y -- cgit v1.2.3 From 9fd7db54dda3b530f7c33898072eef3025ead148 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 13:32:28 +0200 Subject: ARM: ux500: pm: config: Added context and pm These files are primarily needed by suspend and cpuidle. Signed-off-by: Rickard Andersson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32068 Reviewed-by: Jonas ABERG Tested-by: Jonas ABERG --- arch/arm/configs/u8500_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 20f1b3a546f..2054d621b23 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -35,6 +35,8 @@ CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y CONFIG_DBX500_PRCMU_QOS_POWER=y CONFIG_DBX500_PRCMU_DEBUG=y +# CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set +CONFIG_UX500_CONTEXT=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y -- cgit v1.2.3 From e32ab238a6f1c52166447bea53e8394305215127 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Tue, 20 Sep 2011 09:16:36 +0200 Subject: ARM: u8500: config: Enable cpufreq Change-Id: I6d29a32251144e3ff33841516ea265ea9964f46e Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32071 --- arch/arm/configs/u8500_defconfig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 2054d621b23..36582c77939 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -37,6 +37,12 @@ CONFIG_DBX500_PRCMU_QOS_POWER=y CONFIG_DBX500_PRCMU_DEBUG=y # CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set CONFIG_UX500_CONTEXT=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT_DETAILS=y +CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y -- cgit v1.2.3 From ca7bb744c193f2f2a2f37c950ad52574725a5ccf Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 13:35:52 +0200 Subject: ARM: ux500: config Add suspend support Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32074 --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 36582c77939..f2085ccadbb 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -36,6 +36,7 @@ CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y CONFIG_DBX500_PRCMU_QOS_POWER=y CONFIG_DBX500_PRCMU_DEBUG=y # CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set +CONFIG_UX500_SUSPEND=y CONFIG_UX500_CONTEXT=y CONFIG_CPU_FREQ=y CONFIG_CPU_FREQ_STAT_DETAILS=y -- cgit v1.2.3 From 0a56fb7a095719861160f1fcf5c3bee9d08fd6ad Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 13:36:59 +0200 Subject: ARM: ux500: config: suspend: Add wake on uart Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32077 --- arch/arm/configs/u8500_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index f2085ccadbb..274b6d98265 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -37,6 +37,8 @@ CONFIG_DBX500_PRCMU_QOS_POWER=y CONFIG_DBX500_PRCMU_DEBUG=y # CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set CONFIG_UX500_SUSPEND=y +CONFIG_UX500_SUSPEND_DBG=y +CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y CONFIG_UX500_CONTEXT=y CONFIG_CPU_FREQ=y CONFIG_CPU_FREQ_STAT_DETAILS=y -- cgit v1.2.3 From 9ce5097cc10a1eda6ce4eeaf6bf1ca0207676405 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 13:38:18 +0200 Subject: ARM: ux500: config: suspend_dbg: Add debug information Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32081 --- arch/arm/configs/u8500_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 274b6d98265..70586bb6087 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -39,6 +39,8 @@ CONFIG_DBX500_PRCMU_DEBUG=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_DBG=y CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y +CONFIG_UX500_SUSPEND_STANDBY=y +CONFIG_UX500_SUSPEND_MEM=y CONFIG_UX500_CONTEXT=y CONFIG_CPU_FREQ=y CONFIG_CPU_FREQ_STAT_DETAILS=y -- cgit v1.2.3 From 9cdb82eea0cbdcc1c2b1293a8e378acb10d70c82 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 13:44:22 +0200 Subject: drivers:cpuidle:config U8500 support cpuidle driver Signed-off-by: Rickard Andersson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32084 Reviewed-by: Jonas ABERG Tested-by: Jonas ABERG --- arch/arm/configs/u8500_defconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 70586bb6087..82277a3a327 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -35,7 +35,11 @@ CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y CONFIG_DBX500_PRCMU_QOS_POWER=y CONFIG_DBX500_PRCMU_DEBUG=y +CONFIG_CPU_IDLE=y +CONFIG_UX500_CPUIDLE=y # CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set +CONFIG_U8500_CPUIDLE_DEEPEST_STATE=3 +CONFIG_UX500_CPUIDLE_DEBUG=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_DBG=y CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y -- cgit v1.2.3 From 6b3321ca43e2c6361f2d15dfe5d557e7a2cf7c92 Mon Sep 17 00:00:00 2001 From: Rickard Andersson Date: Tue, 20 Sep 2011 17:33:17 +0200 Subject: cpuidle: only go to ApIdle Until all drivers can handle ApSleep properly we only go to sleep state ApIdle Change-Id: I952f7521284a41af56698cd8fc72b833210ea30e Signed-off-by: Rickard Andersson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32086 Reviewed-by: Jonas ABERG Tested-by: Jonas ABERG --- arch/arm/configs/u8500_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 82277a3a327..d408ff60e30 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -38,7 +38,7 @@ CONFIG_DBX500_PRCMU_DEBUG=y CONFIG_CPU_IDLE=y CONFIG_UX500_CPUIDLE=y # CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set -CONFIG_U8500_CPUIDLE_DEEPEST_STATE=3 +CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 CONFIG_UX500_CPUIDLE_DEBUG=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_DBG=y -- cgit v1.2.3 From 7df2efcee0b983903539edb7b3238cdac7f86534 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Wed, 21 Sep 2011 10:02:50 +0200 Subject: u8500: watchdog: Reset to mainline status Change-Id: I14fd6f9990e3fe58671ff256baa8e6dfa0376b1f Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32087 --- arch/arm/configs/u8500_defconfig | 1 - drivers/watchdog/Kconfig | 16 -- drivers/watchdog/Makefile | 1 - drivers/watchdog/ux500_wdt.c | 440 --------------------------------------- 4 files changed, 458 deletions(-) delete mode 100644 drivers/watchdog/ux500_wdt.c diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index d408ff60e30..a9cea178317 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -203,7 +203,6 @@ CONFIG_SENSORS_AB5500=y CONFIG_SENSORS_DBX500=y CONFIG_SENSORS_LSM303DLH=y CONFIG_SENSORS_L3G4200D=y -CONFIG_WATCHDOG=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y CONFIG_MFD_DB8500_PRCMU=y diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 7e173851411..86b0735e6aa 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -287,22 +287,6 @@ config COH901327_WATCHDOG This watchdog is used to reset the system and thus cannot be compiled as a module. -config UX500_WATCHDOG - bool "ST-Ericsson UX500 watchdog" - depends on ARCH_U8500 - default y if MACH_U8500 - help - Say Y here to include Watchdog timer support for the - watchdog existing in the prcmu of ST-Ericsson UX500 series platforms. - This watchdog is used to reset the system and thus cannot be - compiled as a module. - -config UX500_WATCHDOG_DEBUG - bool "ST-Ericsson UX500 watchdog DEBUG" - depends on ARCH_U8500 && DEBUG_FS - help - Say Y here to add various debugfs entries in wdog/ - config TWL4030_WATCHDOG tristate "TWL4030 Watchdog" depends on TWL4030_CORE diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 02be29b78f8..55bd5740e91 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -49,7 +49,6 @@ obj-$(CONFIG_IOP_WATCHDOG) += iop_wdt.o obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o -obj-$(CONFIG_UX500_WATCHDOG) += ux500_wdt.o obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o obj-$(CONFIG_ADX_WATCHDOG) += adx_wdt.o diff --git a/drivers/watchdog/ux500_wdt.c b/drivers/watchdog/ux500_wdt.c deleted file mode 100644 index ccfb3788930..00000000000 --- a/drivers/watchdog/ux500_wdt.c +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2011 - * - * License Terms: GNU General Public License v2 - * - * Author: Jonas Aaberg for ST-Ericsson - * - * Heavily based upon geodewdt.c - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define WATCHDOG_TIMEOUT 600 /* 10 minutes */ - -#define WDT_FLAGS_OPEN 1 -#define WDT_FLAGS_ORPHAN 2 - -static unsigned long wdt_flags; - -static int timeout = WATCHDOG_TIMEOUT; -module_param(timeout, int, 0); -MODULE_PARM_DESC(timeout, - "Watchdog timeout in seconds. 1<= timeout <=131, default=" - __MODULE_STRING(WATCHDOG_TIMEOUT) "."); - -static int nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, int, 0); -MODULE_PARM_DESC(nowayout, - "Watchdog cannot be stopped once started (default=" - __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); -static u8 wdog_id; -static bool wdt_en; -static bool wdt_auto_off = false; -static bool safe_close; - -static int ux500_wdt_open(struct inode *inode, struct file *file) -{ - if (test_and_set_bit(WDT_FLAGS_OPEN, &wdt_flags)) - return -EBUSY; - - if (!test_and_clear_bit(WDT_FLAGS_ORPHAN, &wdt_flags)) - __module_get(THIS_MODULE); - - prcmu_enable_a9wdog(wdog_id); - wdt_en = true; - - return nonseekable_open(inode, file); -} - -static int ux500_wdt_release(struct inode *inode, struct file *file) -{ - if (safe_close) { - prcmu_disable_a9wdog(wdog_id); - module_put(THIS_MODULE); - } else { - pr_crit("Unexpected close - watchdog is not stopping.\n"); - prcmu_kick_a9wdog(wdog_id); - - set_bit(WDT_FLAGS_ORPHAN, &wdt_flags); - } - - clear_bit(WDT_FLAGS_OPEN, &wdt_flags); - safe_close = false; - return 0; -} - -static ssize_t ux500_wdt_write(struct file *file, const char __user *data, - size_t len, loff_t *ppos) -{ - if (!len) - return len; - - if (!nowayout) { - size_t i; - safe_close = false; - - for (i = 0; i != len; i++) { - char c; - - if (get_user(c, data + i)) - return -EFAULT; - - if (c == 'V') - safe_close = true; - } - } - - prcmu_kick_a9wdog(wdog_id); - - return len; -} - -static long ux500_wdt_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - int interval; - - static const struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT | - WDIOF_KEEPALIVEPING | - WDIOF_MAGICCLOSE, - .firmware_version = 1, - .identity = "UX500 WDT", - }; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ident, - sizeof(ident)) ? -EFAULT : 0; - - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - - case WDIOC_SETOPTIONS: - { - int options; - int ret = -EINVAL; - - if (get_user(options, p)) - return -EFAULT; - - if (options & WDIOS_DISABLECARD) { - prcmu_disable_a9wdog(wdog_id); - wdt_en = false; - ret = 0; - } - - if (options & WDIOS_ENABLECARD) { - prcmu_enable_a9wdog(wdog_id); - wdt_en = true; - ret = 0; - } - - return ret; - } - case WDIOC_KEEPALIVE: - return prcmu_kick_a9wdog(wdog_id); - - case WDIOC_SETTIMEOUT: - if (get_user(interval, p)) - return -EFAULT; - - /* 28 bit resolution in ms, becomes 268435455 ms */ - if (interval > 26843 || interval < 0) - return -EINVAL; - timeout = interval; - prcmu_disable_a9wdog(wdog_id); - prcmu_load_a9wdog(wdog_id, timeout * 1000); - prcmu_enable_a9wdog(wdog_id); - - /* Fall through */ - case WDIOC_GETTIMEOUT: - return put_user(timeout, p); - - default: - return -ENOTTY; - } - - return 0; -} - -static const struct file_operations ux500_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = ux500_wdt_write, - .unlocked_ioctl = ux500_wdt_ioctl, - .open = ux500_wdt_open, - .release = ux500_wdt_release, -}; - -static struct miscdevice ux500_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &ux500_wdt_fops, -}; - -#ifdef CONFIG_UX500_WATCHDOG_DEBUG - -enum wdog_dbg { - WDOG_DBG_CONFIG, - WDOG_DBG_LOAD, - WDOG_DBG_KICK, - WDOG_DBG_EN, - WDOG_DBG_DIS, -}; - -static ssize_t wdog_dbg_write(struct file *file, - const char __user *user_buf, - size_t count, loff_t *ppos) -{ - unsigned long val; - int err; - enum wdog_dbg v = (enum wdog_dbg)((struct seq_file *) - (file->private_data))->private; - - switch(v) { - case WDOG_DBG_CONFIG: - err = kstrtoul_from_user(user_buf, count, 0, &val); - - if (!err) { - wdt_auto_off = val != 0; - (void) prcmu_config_a9wdog(1, - wdt_auto_off); - } - else { - pr_err("ux500_wdt:dbg: unknown value\n"); - } - break; - case WDOG_DBG_LOAD: - err = kstrtoul_from_user(user_buf, count, 0, &val); - - if (!err) { - timeout = val; - /* Convert seconds to ms */ - prcmu_disable_a9wdog(wdog_id); - prcmu_load_a9wdog(wdog_id, timeout * 1000); - prcmu_enable_a9wdog(wdog_id); - } - else { - pr_err("ux500_wdt:dbg: unknown value\n"); - } - break; - case WDOG_DBG_KICK: - (void) prcmu_kick_a9wdog(wdog_id); - break; - case WDOG_DBG_EN: - wdt_en = true; - (void) prcmu_enable_a9wdog(wdog_id); - break; - case WDOG_DBG_DIS: - wdt_en = false; - (void) prcmu_disable_a9wdog(wdog_id); - break; - } - - return count; -} - -static int wdog_dbg_read(struct seq_file *s, void *p) -{ - enum wdog_dbg v = (enum wdog_dbg)s->private; - - switch(v) { - case WDOG_DBG_CONFIG: - seq_printf(s,"wdog is on id %d, auto off on sleep: %s\n", - (int)wdog_id, - wdt_auto_off ? "enabled": "disabled"); - break; - case WDOG_DBG_LOAD: - /* In 1s */ - seq_printf(s, "wdog load is: %d s\n", - timeout); - break; - case WDOG_DBG_KICK: - break; - case WDOG_DBG_EN: - case WDOG_DBG_DIS: - seq_printf(s, "wdog is %sabled.\n", - wdt_en ? "en" : "dis"); - break; - } - return 0; -} - -static int wdog_dbg_open(struct inode *inode, - struct file *file) -{ - return single_open(file, wdog_dbg_read, inode->i_private); -} - -static const struct file_operations wdog_dbg_fops = { - .open = wdog_dbg_open, - .write = wdog_dbg_write, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, - .owner = THIS_MODULE, -}; - -static int __init wdog_dbg_init(void) -{ - struct dentry *wdog_dir; - - wdog_dir = debugfs_create_dir("wdog", NULL); - if (IS_ERR_OR_NULL(wdog_dir)) - goto fail; - - if (IS_ERR_OR_NULL(debugfs_create_u8("id", - S_IWUGO | S_IRUGO, wdog_dir, - &wdog_id))) - goto fail; - - if (IS_ERR_OR_NULL(debugfs_create_file("config", - S_IWUGO | S_IRUGO, wdog_dir, - (void *)WDOG_DBG_CONFIG, - &wdog_dbg_fops))) - goto fail; - - if (IS_ERR_OR_NULL(debugfs_create_file("load", - S_IWUGO | S_IRUGO, wdog_dir, - (void *)WDOG_DBG_LOAD, - &wdog_dbg_fops))) - goto fail; - - if (IS_ERR_OR_NULL(debugfs_create_file("kick", - S_IWUGO, wdog_dir, - (void *)WDOG_DBG_KICK, - &wdog_dbg_fops))) - goto fail; - - if (IS_ERR_OR_NULL(debugfs_create_file("enable", - S_IWUGO | S_IRUGO, wdog_dir, - (void *)WDOG_DBG_EN, - &wdog_dbg_fops))) - goto fail; - - if (IS_ERR_OR_NULL(debugfs_create_file("disable", - S_IWUGO | S_IRUGO, wdog_dir, - (void *)WDOG_DBG_DIS, - &wdog_dbg_fops))) - goto fail; - - return 0; -fail: - pr_err("ux500:wdog: Failed to initialize wdog dbg.\n"); - debugfs_remove_recursive(wdog_dir); - - return -EFAULT; -} - -#else -static inline int __init wdog_dbg_init(void) -{ - return 0; -} -#endif - -static int __init ux500_wdt_probe(struct platform_device *pdev) -{ - int ret; - - /* Number of watch dogs */ - prcmu_config_a9wdog(1, wdt_auto_off); - /* convert to ms */ - prcmu_load_a9wdog(wdog_id, timeout * 1000); - - ret = misc_register(&ux500_wdt_miscdev); - if (ret < 0) { - dev_err(&pdev->dev, "failed to register misc.\n"); - return ret; - } - - ret = wdog_dbg_init(); - if (ret < 0) - goto fail; - - dev_info(&pdev->dev, "initialized.\n"); - - return 0; -fail: - misc_deregister(&ux500_wdt_miscdev); - return ret; -} - -static int __exit ux500_wdt_remove(struct platform_device *dev) -{ - prcmu_disable_a9wdog(wdog_id); - wdt_en = false; - misc_deregister(&ux500_wdt_miscdev); - return 0; -} -#ifdef CONFIG_PM -static int ux500_wdt_suspend(struct platform_device *pdev, - pm_message_t state) -{ - if (wdt_en && !wdt_auto_off) { - prcmu_disable_a9wdog(wdog_id); - prcmu_config_a9wdog(1, true); - - prcmu_load_a9wdog(wdog_id, timeout * 1000); - prcmu_enable_a9wdog(wdog_id); - } - return 0; -} - -static int ux500_wdt_resume(struct platform_device *pdev) -{ - if (wdt_en && !wdt_auto_off) { - prcmu_disable_a9wdog(wdog_id); - prcmu_config_a9wdog(1, wdt_auto_off); - - prcmu_load_a9wdog(wdog_id, timeout * 1000); - prcmu_enable_a9wdog(wdog_id); - } - return 0; -} - -#else -#define ux500_wdt_suspend NULL -#define ux500_wdt_resume NULL -#endif -static struct platform_driver ux500_wdt_driver = { - .remove = __exit_p(ux500_wdt_remove), - .driver = { - .owner = THIS_MODULE, - .name = "ux500_wdt", - }, - .suspend = ux500_wdt_suspend, - .resume = ux500_wdt_resume, -}; - -static int __init ux500_wdt_init(void) -{ - return platform_driver_probe(&ux500_wdt_driver, ux500_wdt_probe); -} - -static void __exit ux500_wdt_exit(void) -{ - platform_driver_unregister(&ux500_wdt_driver); -} - -module_init(ux500_wdt_init); -module_exit(ux500_wdt_exit); - -MODULE_AUTHOR("Jonas Aaberg "); -MODULE_DESCRIPTION("UX500 Watchdog Driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -- cgit v1.2.3 From 53dab6bf96bbeb41ba46bba7278d0bb76709998b Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Wed, 21 Sep 2011 08:35:08 +0200 Subject: drivers: watchdog: Add watchdog for u8500 Watchdog driver for ST-Ericsson's u8500 platform Change-Id: I9154a96b85493db8c94e52ed84f6e25119f0847c Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32088 --- drivers/watchdog/Kconfig | 10 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/u8500_wdt.c | 265 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 drivers/watchdog/u8500_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 86b0735e6aa..973f5af8d96 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -348,6 +348,16 @@ config IMX2_WDT To compile this driver as a module, choose M here: the module will be called imx2_wdt. +config U8500_WATCHDOG + bool "ST-Ericsson U8500 watchdog" + depends on UX500_SOC_DB8500 + default y + help + Say Y here to include Watchdog timer support for the + watchdog existing in the prcmu of ST-Ericsson U8500 series platforms. + This watchdog is used to reset the system and thus cannot be + compiled as a module. + # AVR32 Architecture config AT32AP700X_WDT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 55bd5740e91..4f48616e15b 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o obj-$(CONFIG_ADX_WATCHDOG) += adx_wdt.o obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o +obj-$(CONFIG_U8500_WATCHDOG) += u8500_wdt.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o diff --git a/drivers/watchdog/u8500_wdt.c b/drivers/watchdog/u8500_wdt.c new file mode 100644 index 00000000000..6433e798916 --- /dev/null +++ b/drivers/watchdog/u8500_wdt.c @@ -0,0 +1,265 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License Terms: GNU General Public License v2 + * + * Author: Jonas Aaberg for ST-Ericsson + * + * Heavily based upon geodewdt.c + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WATCHDOG_TIMEOUT 600 /* 10 minutes */ + +#define WDT_FLAGS_OPEN 1 +#define WDT_FLAGS_ORPHAN 2 + +static unsigned long wdt_flags; + +static int timeout = WATCHDOG_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) "."); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +static u8 wdog_id; +static bool wdt_en; +static bool wdt_auto_off = false; +static bool safe_close; + +static int u8500_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(WDT_FLAGS_OPEN, &wdt_flags)) + return -EBUSY; + + if (!test_and_clear_bit(WDT_FLAGS_ORPHAN, &wdt_flags)) + __module_get(THIS_MODULE); + + prcmu_enable_a9wdog(wdog_id); + wdt_en = true; + + return nonseekable_open(inode, file); +} + +static int u8500_wdt_release(struct inode *inode, struct file *file) +{ + if (safe_close) { + prcmu_disable_a9wdog(wdog_id); + module_put(THIS_MODULE); + } else { + pr_crit("Unexpected close - watchdog is not stopping.\n"); + prcmu_kick_a9wdog(wdog_id); + + set_bit(WDT_FLAGS_ORPHAN, &wdt_flags); + } + + clear_bit(WDT_FLAGS_OPEN, &wdt_flags); + safe_close = false; + return 0; +} + +static ssize_t u8500_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + if (!len) + return len; + + if (!nowayout) { + size_t i; + safe_close = false; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + + if (c == 'V') + safe_close = true; + } + } + + prcmu_kick_a9wdog(wdog_id); + + return len; +} + +static long u8500_wdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int interval; + + static const struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "U8500 WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, + sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_SETOPTIONS: + { + int options; + int ret = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + prcmu_disable_a9wdog(wdog_id); + wdt_en = false; + ret = 0; + } + + if (options & WDIOS_ENABLECARD) { + prcmu_enable_a9wdog(wdog_id); + wdt_en = true; + ret = 0; + } + + return ret; + } + case WDIOC_KEEPALIVE: + return prcmu_kick_a9wdog(wdog_id); + + case WDIOC_SETTIMEOUT: + if (get_user(interval, p)) + return -EFAULT; + + /* 28 bit resolution in ms, becomes 268435.455 s */ + if (interval > 268435 || interval < 0) + return -EINVAL; + timeout = interval; + prcmu_disable_a9wdog(wdog_id); + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + + /* Fall through */ + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + + default: + return -ENOTTY; + } + + return 0; +} + +static const struct file_operations u8500_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = u8500_wdt_write, + .unlocked_ioctl = u8500_wdt_ioctl, + .open = u8500_wdt_open, + .release = u8500_wdt_release, +}; + +static struct miscdevice u8500_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &u8500_wdt_fops, +}; + +static int __init u8500_wdt_probe(struct platform_device *pdev) +{ + int ret; + + /* Number of watch dogs */ + prcmu_config_a9wdog(1, wdt_auto_off); + /* convert to ms */ + prcmu_load_a9wdog(wdog_id, timeout * 1000); + + ret = misc_register(&u8500_wdt_miscdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register misc\n"); + return ret; + } + + dev_info(&pdev->dev, "initialized\n"); + + return 0; +} + +static int __exit u8500_wdt_remove(struct platform_device *dev) +{ + prcmu_disable_a9wdog(wdog_id); + wdt_en = false; + misc_deregister(&u8500_wdt_miscdev); + return 0; +} +#ifdef CONFIG_PM +static int u8500_wdt_suspend(struct platform_device *pdev, + pm_message_t state) +{ + if (wdt_en && !wdt_auto_off) { + prcmu_disable_a9wdog(wdog_id); + prcmu_config_a9wdog(1, true); + + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + } + return 0; +} + +static int u8500_wdt_resume(struct platform_device *pdev) +{ + if (wdt_en && !wdt_auto_off) { + prcmu_disable_a9wdog(wdog_id); + prcmu_config_a9wdog(1, wdt_auto_off); + + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + } + return 0; +} + +#else +#define u8500_wdt_suspend NULL +#define u8500_wdt_resume NULL +#endif +static struct platform_driver u8500_wdt_driver = { + .remove = __exit_p(u8500_wdt_remove), + .driver = { + .owner = THIS_MODULE, + .name = "u8500_wdt", + }, + .suspend = u8500_wdt_suspend, + .resume = u8500_wdt_resume, +}; + +static int __init u8500_wdt_init(void) +{ + return platform_driver_probe(&u8500_wdt_driver, u8500_wdt_probe); +} +module_init(u8500_wdt_init); + +MODULE_AUTHOR("Jonas Aaberg "); +MODULE_DESCRIPTION("U8500 Watchdog Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); -- cgit v1.2.3 From 7d39f2954eac037c372df88bf4f114b51e77e147 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Wed, 21 Sep 2011 09:30:42 +0200 Subject: drivers: watchdog: u8500: Add debugfs interface Change-Id: I0c2da769eea8ff3d89ded8253482d805d4913f00 Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32089 --- drivers/watchdog/Kconfig | 6 ++ drivers/watchdog/u8500_wdt.c | 168 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 973f5af8d96..b4bf8b9880b 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -358,6 +358,12 @@ config U8500_WATCHDOG This watchdog is used to reset the system and thus cannot be compiled as a module. +config U8500_WATCHDOG_DEBUG + bool "ST-Ericsson U8500 watchdog DEBUG" + depends on UX500_SOC_DB8500 && DEBUG_FS + help + Say Y here to add various debugfs entries in wdog/ + # AVR32 Architecture config AT32AP700X_WDT diff --git a/drivers/watchdog/u8500_wdt.c b/drivers/watchdog/u8500_wdt.c index 6433e798916..3ac23ab05f3 100644 --- a/drivers/watchdog/u8500_wdt.c +++ b/drivers/watchdog/u8500_wdt.c @@ -186,6 +186,167 @@ static struct miscdevice u8500_wdt_miscdev = { .fops = &u8500_wdt_fops, }; +#ifdef CONFIG_U8500_WATCHDOG_DEBUG + +enum wdog_dbg { + WDOG_DBG_CONFIG, + WDOG_DBG_LOAD, + WDOG_DBG_KICK, + WDOG_DBG_EN, + WDOG_DBG_DIS, +}; + +static ssize_t wdog_dbg_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + unsigned long val; + int err; + enum wdog_dbg v = (enum wdog_dbg)((struct seq_file *) + (file->private_data))->private; + + switch(v) { + case WDOG_DBG_CONFIG: + err = kstrtoul_from_user(user_buf, count, 0, &val); + + if (!err) { + wdt_auto_off = val != 0; + (void) prcmu_config_a9wdog(1, + wdt_auto_off); + } + else { + pr_err("u8500_wdt:dbg: unknown value\n"); + } + break; + case WDOG_DBG_LOAD: + err = kstrtoul_from_user(user_buf, count, 0, &val); + + if (!err) { + timeout = val; + /* Convert seconds to ms */ + prcmu_disable_a9wdog(wdog_id); + prcmu_load_a9wdog(wdog_id, timeout * 1000); + prcmu_enable_a9wdog(wdog_id); + } + else { + pr_err("u8500_wdt:dbg: unknown value\n"); + } + break; + case WDOG_DBG_KICK: + (void) prcmu_kick_a9wdog(wdog_id); + break; + case WDOG_DBG_EN: + wdt_en = true; + (void) prcmu_enable_a9wdog(wdog_id); + break; + case WDOG_DBG_DIS: + wdt_en = false; + (void) prcmu_disable_a9wdog(wdog_id); + break; + } + + return count; +} + +static int wdog_dbg_read(struct seq_file *s, void *p) +{ + enum wdog_dbg v = (enum wdog_dbg)s->private; + + switch(v) { + case WDOG_DBG_CONFIG: + seq_printf(s,"wdog is on id %d, auto off on sleep: %s\n", + (int)wdog_id, + wdt_auto_off ? "enabled": "disabled"); + break; + case WDOG_DBG_LOAD: + /* In 1s */ + seq_printf(s, "wdog load is: %d s\n", + timeout); + break; + case WDOG_DBG_KICK: + break; + case WDOG_DBG_EN: + case WDOG_DBG_DIS: + seq_printf(s, "wdog is %sabled\n", + wdt_en ? "en" : "dis"); + break; + } + return 0; +} + +static int wdog_dbg_open(struct inode *inode, + struct file *file) +{ + return single_open(file, wdog_dbg_read, inode->i_private); +} + +static const struct file_operations wdog_dbg_fops = { + .open = wdog_dbg_open, + .write = wdog_dbg_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int __init wdog_dbg_init(void) +{ + struct dentry *wdog_dir; + + wdog_dir = debugfs_create_dir("wdog", NULL); + if (IS_ERR_OR_NULL(wdog_dir)) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_u8("id", + S_IWUGO | S_IRUGO, wdog_dir, + &wdog_id))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("config", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_CONFIG, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("load", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_LOAD, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("kick", + S_IWUGO, wdog_dir, + (void *)WDOG_DBG_KICK, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("enable", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_EN, + &wdog_dbg_fops))) + goto fail; + + if (IS_ERR_OR_NULL(debugfs_create_file("disable", + S_IWUGO | S_IRUGO, wdog_dir, + (void *)WDOG_DBG_DIS, + &wdog_dbg_fops))) + goto fail; + + return 0; +fail: + pr_err("u8500:wdog: Failed to initialize wdog dbg\n"); + debugfs_remove_recursive(wdog_dir); + + return -EFAULT; +} + +#else +static inline int __init wdog_dbg_init(void) +{ + return 0; +} +#endif + static int __init u8500_wdt_probe(struct platform_device *pdev) { int ret; @@ -201,9 +362,16 @@ static int __init u8500_wdt_probe(struct platform_device *pdev) return ret; } + ret = wdog_dbg_init(); + if (ret < 0) + goto fail; + dev_info(&pdev->dev, "initialized\n"); return 0; +fail: + misc_deregister(&u8500_wdt_miscdev); + return ret; } static int __exit u8500_wdt_remove(struct platform_device *dev) -- cgit v1.2.3 From 7c543a279b88ffc9887e04b2da25f6224a2e6033 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 13:51:49 +0200 Subject: ARM: ux500: config: Use u8500_wdt Enalbe the u8500 watchdog including debug for u8500 builds. Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32090 --- arch/arm/configs/u8500_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index a9cea178317..6b508e785d4 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -203,6 +203,9 @@ CONFIG_SENSORS_AB5500=y CONFIG_SENSORS_DBX500=y CONFIG_SENSORS_LSM303DLH=y CONFIG_SENSORS_L3G4200D=y +CONFIG_WATCHDOG=y +CONFIG_WATCHDOG_CORE=y +CONFIG_U8500_WATCHDOG_DEBUG=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y CONFIG_MFD_DB8500_PRCMU=y -- cgit v1.2.3 From d85ca3fe791b45b5aacb744713ee6df28273b2da Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Fri, 23 Sep 2011 10:34:33 +0200 Subject: ARM: config: u8500: Enable DB8500 regulators Change-Id: Ieb69b0092465a71e7233cc63bddb149998f96410 Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32110 --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 6b508e785d4..4a96720c995 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -217,6 +217,7 @@ CONFIG_REGULATOR_DUMMY=y CONFIG_REGULATOR_FIXED_VOLTAGE=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y +CONFIG_REGULATOR_DB8500_PRCMU=y CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y # CONFIG_MEDIA_TUNER_CUSTOMISE is not set -- cgit v1.2.3 From 707b277d3228866f7d1c0879f2779cfed194faa5 Mon Sep 17 00:00:00 2001 From: Mian Yousaf Kaukab Date: Wed, 21 Sep 2011 11:06:57 +0200 Subject: serial: amba-pl011: delay driver initialization During pl011 probe() On u8500 platform, amba bus driver tries to access regulator driver and fails as the regulator driver is also at arch_init level. Moving serial driver to the next level but still as early as possible. Change-Id: I29d23067df9465b3c25f4010dca8af253fe6d070 Signed-off-by: Mian Yousaf Kaukab Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32121 Reviewed-by: Jonas ABERG Tested-by: Jonas ABERG --- drivers/tty/serial/amba-pl011.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index 9b6b7e22bfc..7035910de79 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -2352,7 +2352,7 @@ static void __exit pl011_exit(void) * While this can be a module, if builtin it's most likely the console * So let's leave module_exit but move module_init to an earlier place */ -arch_initcall(pl011_init); +subsys_initcall(pl011_init); module_exit(pl011_exit); MODULE_AUTHOR("ARM Ltd/Deep Blue Solutions Ltd"); -- cgit v1.2.3 From ea79a4214e03bcf6ba142329e248e809e2cfa22e Mon Sep 17 00:00:00 2001 From: Venkata Biswanath Date: Wed, 28 Sep 2011 18:12:09 +0530 Subject: u5500: defconfig: enable new PRCMU driver Change-Id: I89fc75a7d42708e742e6da02dd6110232dad82fb Signed-off-by: Venkata Biswanath Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32451 Reviewed-by: Jonas ABERG Tested-by: Jonas ABERG --- arch/arm/configs/u8500_defconfig | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 4a96720c995..aec0f163ca6 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -40,6 +40,12 @@ CONFIG_UX500_CPUIDLE=y # CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 CONFIG_UX500_CPUIDLE_DEBUG=y +CONFIG_UX500_SOC_DB5500=y +CONFIG_MACH_U5500=y +CONFIG_UX500_DEBUG_UART=0 +CONFIG_U5500_MLOADER=y +CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y +CONFIG_DISPLAY_SONY_ACX424AKP_DSI_PRIMARY=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_DBG=y CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y @@ -200,7 +206,6 @@ CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=n CONFIG_AB5500_BM=y CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL=y CONFIG_SENSORS_AB5500=y -CONFIG_SENSORS_DBX500=y CONFIG_SENSORS_LSM303DLH=y CONFIG_SENSORS_L3G4200D=y CONFIG_WATCHDOG=y @@ -212,12 +217,16 @@ CONFIG_MFD_DB8500_PRCMU=y CONFIG_TPS6105X=y CONFIG_AB5500_CORE=y CONFIG_AB8500_CORE=y +CONFIG_MFD_DB5500_PRCMU=y +CONFIG_REGULATOR=y CONFIG_REGULATOR_DEBUG=y CONFIG_REGULATOR_DUMMY=y CONFIG_REGULATOR_FIXED_VOLTAGE=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y CONFIG_REGULATOR_DB8500_PRCMU=y +CONFIG_REGULATOR_AB5500=y +CONFIG_REGULATOR_DB5500_PRCMU=y CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y # CONFIG_MEDIA_TUNER_CUSTOMISE is not set -- cgit v1.2.3 From c461d85e4306de735be558e10572481481b87765 Mon Sep 17 00:00:00 2001 From: Christian Nilsson Date: Thu, 29 Sep 2011 07:42:58 +0200 Subject: u8500_defconfig: Enable ip rule support Added support for multiple routing tables used for Dial Up Networking and enabled the usage of ip rule command to configure them. ST-Ericsson ID: 339665 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I3a570cdf0dbdbea2b2279428fa10d8a1fc63f659 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32428 Reviewed-by: Christian NILSSON Tested-by: Christian NILSSON Reviewed-by: Jonas ABERG --- arch/arm/configs/u8500_defconfig | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index aec0f163ca6..95267534226 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -76,7 +76,14 @@ CONFIG_PM_RUNTIME=y CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y +CONFIG_NET_KEY=y +# CONFIG_NET_KEY_MIGRATE is not set CONFIG_INET=y +CONFIG_IP_ADVANCED_ROUTER=y +# CONFIG_IP_FIB_TRIE_STATS is not set +CONFIG_IP_MULTIPLE_TABLES=y +# CONFIG_IP_ROUTE_MULTIPATH is not set +# CONFIG_IP_ROUTE_VERBOSE is not set CONFIG_IP_PNP=y CONFIG_IP_PNP_DHCP=y # CONFIG_INET_LRO is not set @@ -86,12 +93,17 @@ CONFIG_IPV6=y # CONFIG_INET6_XFRM_MODE_BEET is not set # CONFIG_IPV6_SIT is not set CONFIG_NETFILTER=y +CONFIG_NETFILTER_NETLINK=y +CONFIG_NETFILTER_NETLINK_QUEUE=y +CONFIG_NETFILTER_NETLINK_LOG=y CONFIG_NF_CONNTRACK=y +CONFIG_NF_CT_NETLINK=y CONFIG_NETFILTER_XT_MATCH_MULTIPORT=y CONFIG_NETFILTER_XT_MATCH_STATE=y CONFIG_NF_CONNTRACK_IPV4=y CONFIG_IP_NF_IPTABLES=y CONFIG_IP_NF_FILTER=y +CONFIG_IP_NF_TARGET_ULOG=y CONFIG_NF_NAT=y CONFIG_IP_NF_TARGET_MASQUERADE=y CONFIG_PHONET=y @@ -108,6 +120,7 @@ CONFIG_BT_HIDP=y CONFIG_AVERAGE=y #WIRELESS +CONFIG_FIB_RULES=y CONFIG_WIRELESS=y CONFIG_COMPAT_WIRELESS=y CONFIG_COMPAT_WIRELESS_MODULES=y @@ -168,6 +181,18 @@ CONFIG_SMSC911X=y # CONFIG_NETDEV_1000 is not set # CONFIG_NETDEV_10000 is not set # CONFIG_WLAN is not set +CONFIG_PPP=y +# CONFIG_PPP_MULTILINK is not set +# CONFIG_PPP_FILTER is not set +CONFIG_PPP_ASYNC=y +# CONFIG_PPP_SYNC_TTY is not set +# CONFIG_PPP_DEFLATE is not set +# CONFIG_PPP_BSDCOMP is not set +CONFIG_PPP_MPPE=y +# CONFIG_PPPOE is not set +CONFIG_PPPOLAC=y +CONFIG_PPPOPNS=y +CONFIG_SLHC=y # CONFIG_INPUT_MOUSEDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set -- cgit v1.2.3 From c167e308e4c2d82be401eee0c29d821d772b1562 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 30 Sep 2011 23:34:55 +0200 Subject: config: Update the u8500 def config Change-Id: I6f0959ab3d5496d54d35cdf74354b27d616ba8cf Signed-off-by: Robert Marklund --- arch/arm/configs/u8500_defconfig | 139 ++++++--------------------------------- 1 file changed, 19 insertions(+), 120 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 95267534226..f17c1a0c796 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -23,41 +23,17 @@ CONFIG_ARCH_U8500=y CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_HREFV60=y CONFIG_MACH_SNOWBALL=y -CONFIG_U8500_REGULATOR_DEBUG=y +CONFIG_DBX500_PRCMU_DEBUG=y CONFIG_DB8500_MLOADER=y -CONFIG_DISPLAY_GENERIC_PRIMARY=y -CONFIG_DISPLAY_GENERIC_DSI_PRIMARY=y CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y -CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_ROTATION_90=y -CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_ROTATION_ANGLE=90 CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y -CONFIG_DBX500_PRCMU_QOS_POWER=y -CONFIG_DBX500_PRCMU_DEBUG=y -CONFIG_CPU_IDLE=y -CONFIG_UX500_CPUIDLE=y -# CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set -CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 -CONFIG_UX500_CPUIDLE_DEBUG=y -CONFIG_UX500_SOC_DB5500=y -CONFIG_MACH_U5500=y -CONFIG_UX500_DEBUG_UART=0 -CONFIG_U5500_MLOADER=y -CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y -CONFIG_DISPLAY_SONY_ACX424AKP_DSI_PRIMARY=y CONFIG_UX500_SUSPEND=y -CONFIG_UX500_SUSPEND_DBG=y -CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y CONFIG_UX500_SUSPEND_STANDBY=y CONFIG_UX500_SUSPEND_MEM=y -CONFIG_UX500_CONTEXT=y -CONFIG_CPU_FREQ=y -CONFIG_CPU_FREQ_STAT_DETAILS=y -CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y -CONFIG_CPU_FREQ_GOV_POWERSAVE=y -CONFIG_CPU_FREQ_GOV_USERSPACE=y -CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_UX500_SUSPEND_DBG=y +CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y @@ -68,6 +44,15 @@ CONFIG_HIGHMEM=y CONFIG_CMDLINE="root=/dev/ram0 init=init rw console=ttyAMA2,115200n8 mem=256M initrd=0x800000,72M" CONFIG_KEXEC=y CONFIG_CRASH_DUMP=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT_DETAILS=y +CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_IDLE=y +CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 +CONFIG_UX500_CPUIDLE_DEBUG=y CONFIG_FPE_NWFPE=y CONFIG_VFP=y CONFIG_NEON=y @@ -77,13 +62,9 @@ CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y CONFIG_NET_KEY=y -# CONFIG_NET_KEY_MIGRATE is not set CONFIG_INET=y CONFIG_IP_ADVANCED_ROUTER=y -# CONFIG_IP_FIB_TRIE_STATS is not set CONFIG_IP_MULTIPLE_TABLES=y -# CONFIG_IP_ROUTE_MULTIPATH is not set -# CONFIG_IP_ROUTE_VERBOSE is not set CONFIG_IP_PNP=y CONFIG_IP_PNP_DHCP=y # CONFIG_INET_LRO is not set @@ -93,7 +74,6 @@ CONFIG_IPV6=y # CONFIG_INET6_XFRM_MODE_BEET is not set # CONFIG_IPV6_SIT is not set CONFIG_NETFILTER=y -CONFIG_NETFILTER_NETLINK=y CONFIG_NETFILTER_NETLINK_QUEUE=y CONFIG_NETFILTER_NETLINK_LOG=y CONFIG_NF_CONNTRACK=y @@ -108,7 +88,6 @@ CONFIG_NF_NAT=y CONFIG_IP_NF_TARGET_MASQUERADE=y CONFIG_PHONET=y CONFIG_NET_SCHED=y -# CONFIG_IPV6 is not set CONFIG_BT_L2CAP=y CONFIG_BT_SCO=y CONFIG_BT_RFCOMM=y @@ -117,40 +96,11 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y -CONFIG_AVERAGE=y - -#WIRELESS -CONFIG_FIB_RULES=y -CONFIG_WIRELESS=y -CONFIG_COMPAT_WIRELESS=y -CONFIG_COMPAT_WIRELESS_MODULES=y CONFIG_CFG80211=y -CONFIG_COMPAT_MAC80211_RC_DEFAULT="minstrel_ht" -CONFIG_MAC80211_RC_DEFAULT_MINSTREL=y -CONFIG_COMPAT_RFKILL=y CONFIG_NL80211_TESTMODE=y -CONFIG_CFG80211_DEFAULT_PS=y CONFIG_CFG80211_REG_DEBUG=y -CONFIG_MAC80211_RC_PID=y -CONFIG_MAC80211_RC_MINSTREL=y -CONFIG_MAC80211_RC_MINSTREL_HT=y -CONFIG_MAC80211_LEDS=y -CONFIG_MAC80211_MESH=y -# CONFIG_CFG80211_DEVELOPER_WARNINGS is not set -# CONFIG_CFG80211_DEBUGFS is not set -# CONFIG_CFG80211_INTERNAL_REGDB is not set # CONFIG_CFG80211_WEXT is not set -# CONFIG_LIB80211 is not set -# CONFIG_MAC80211 is not set -# CONFIG_MAC80211_HAS_RC is not set -# CONFIG_MAC80211_RC_DEFAULT_PID is not set -# CONFIG_MAC80211_RC_DEFAULT is not set -# CONFIG_MAC80211_DEBUGFS is not set -# CONFIG_MAC80211_DEBUG_MENU is not set -# CONFIG_WIMAX is not set CONFIG_RFKILL=y -CONFIG_RFKILL_PM=y -CONFIG_RFKILL_LEDS=y CONFIG_RFKILL_INPUT=y CONFIG_NET_9P=y CONFIG_CAIF=y @@ -163,9 +113,8 @@ CONFIG_MISC_DEVICES=y CONFIG_AB8500_PWM=y CONFIG_SENSORS_BH1780=y CONFIG_STE_TRACE_MODEM=y -CONFIG_U8500_SIM_DETECT=y -# CONFIG_STM_TRACE=y CONFIG_DISPDEV=y +CONFIG_U8500_SIM_DETECT=y # CONFIG_STE_AUDIO_IO_DEV is not set CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y @@ -182,17 +131,8 @@ CONFIG_SMSC911X=y # CONFIG_NETDEV_10000 is not set # CONFIG_WLAN is not set CONFIG_PPP=y -# CONFIG_PPP_MULTILINK is not set -# CONFIG_PPP_FILTER is not set CONFIG_PPP_ASYNC=y -# CONFIG_PPP_SYNC_TTY is not set -# CONFIG_PPP_DEFLATE is not set -# CONFIG_PPP_BSDCOMP is not set CONFIG_PPP_MPPE=y -# CONFIG_PPPOE is not set -CONFIG_PPPOLAC=y -CONFIG_PPPOPNS=y -CONFIG_SLHC=y # CONFIG_INPUT_MOUSEDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set @@ -225,60 +165,33 @@ CONFIG_GPIO_SYSFS=y CONFIG_GPIO_TC3589X=y CONFIG_GPIO_AB8500=y CONFIG_POWER_SUPPLY=y -CONFIG_SENSORS_AB8500=y CONFIG_AB8500_BM=y -CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL=n -CONFIG_AB5500_BM=y -CONFIG_AB5500_BATTERY_THERM_ON_BATCTRL=y -CONFIG_SENSORS_AB5500=y +CONFIG_SENSORS_AB8500=y CONFIG_SENSORS_LSM303DLH=y CONFIG_SENSORS_L3G4200D=y CONFIG_WATCHDOG=y -CONFIG_WATCHDOG_CORE=y CONFIG_U8500_WATCHDOG_DEBUG=y +CONFIG_TPS6105X=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y -CONFIG_MFD_DB8500_PRCMU=y -CONFIG_TPS6105X=y CONFIG_AB5500_CORE=y CONFIG_AB8500_CORE=y -CONFIG_MFD_DB5500_PRCMU=y -CONFIG_REGULATOR=y +CONFIG_MFD_DB8500_PRCMU=y CONFIG_REGULATOR_DEBUG=y -CONFIG_REGULATOR_DUMMY=y -CONFIG_REGULATOR_FIXED_VOLTAGE=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y CONFIG_REGULATOR_DB8500_PRCMU=y -CONFIG_REGULATOR_AB5500=y -CONFIG_REGULATOR_DB5500_PRCMU=y CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y # CONFIG_MEDIA_TUNER_CUSTOMISE is not set # CONFIG_VIDEO_CAPTURE_DRIVERS is not set CONFIG_RADIO_CG2900=y -CONFIG_GPU_MALI=y -CONFIG_GPU_MALI_DEBUG=y CONFIG_FB=y -CONFIG_FB_SYS_FILLRECT=y -CONFIG_FB_SYS_COPYAREA=y -CONFIG_FB_SYS_IMAGEBLIT=y -CONFIG_FB_SYS_FOPS=y CONFIG_FB_MCDE=y -CONFIG_MCDE_DISPLAY_GENERIC_DSI=y -CONFIG_MCDE_DISPLAY_AV8100=y # CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE is not set +CONFIG_AV8100_HWTRIG_I2SDAT3=y CONFIG_FB_B2R2=y CONFIG_B2R2_PLUG_CONF=y -CONFIG_B2R2_OPSIZE_64=y -CONFIG_B2R2_CHSIZE_128=y -CONFIG_B2R2_MGSIZE_128=y -CONFIG_B2R2_PGSIZE_256=y -CONFIG_B2R2_GENERIC=y -CONFIG_B2R2_GENERIC_FALLBACK=y -CONFIG_AV8100=y -CONFIG_AV8100_HWTRIG_I2SDAT3=y -CONFIG_DUMMY_CONSOLE=y CONFIG_SOUND=y CONFIG_SND=y CONFIG_SND_USB_AUDIO=y @@ -293,27 +206,19 @@ CONFIG_USB=y CONFIG_USB_SUSPEND=y # CONFIG_USB_OTG_WHITELIST is not set CONFIG_USB_MON=y -# CONFIG_SND_ARM is not set -# CONFIG_SND_SPI is not set -CONFIG_SND_SOC=y -CONFIG_SND_SOC_UX500=y -CONFIG_SND_SOC_U5500=y -CONFIG_SND_SOC_UX500_AB5500=y CONFIG_USB_MUSB_HDRC=y CONFIG_USB_MUSB_UX500=y +CONFIG_USB_MUSB_OTG=y CONFIG_USB_GADGET_MUSB_HDRC=y CONFIG_USB_ACM=y CONFIG_USB_STORAGE=y CONFIG_USB_LIBUSUAL=y -CONFIG_MUSB_PIO_ONLY=y -CONFIG_USB_MUSB_DEBUG=y CONFIG_USB_GADGET=y CONFIG_USB_GADGET_VBUS_DRAW=500 CONFIG_AB8500_USB=y CONFIG_MMC=y CONFIG_MMC_UNSAFE_RESUME=y CONFIG_MMC_CLKGATE=y -CONFIG_MMC_PARANOID_SD_INIT=y # CONFIG_MMC_BLOCK_BOUNCE is not set CONFIG_MMC_ARMMMCI=y CONFIG_NEW_LEDS=y @@ -323,7 +228,6 @@ CONFIG_LEDS_LP5521=y CONFIG_LEDS_PWM=y CONFIG_LEDS_TRIGGERS=y CONFIG_LEDS_TRIGGER_TIMER=y -CONFIG_SWITCH=y CONFIG_RTC_CLASS=y CONFIG_RTC_DRV_AB=y CONFIG_RTC_DRV_AB8500=y @@ -347,7 +251,6 @@ CONFIG_MODEM=y CONFIG_MODEM_U8500=y CONFIG_U8500_SHRM=y CONFIG_U8500_SHRM_MODEM_SILENT_RESET=y -CONFIG_U5500_MMIO=y CONFIG_EXT2_FS=y CONFIG_EXT2_FS_XATTR=y CONFIG_EXT2_FS_POSIX_ACL=y @@ -400,15 +303,11 @@ CONFIG_DEBUG_INFO=y CONFIG_SYSCTL_SYSCALL_CHECK=y CONFIG_FUNCTION_TRACER=y CONFIG_DEBUG_USER=y -CONFIG_DEBUG_LL=y -CONFIG_EARLY_PRINTK=y CONFIG_KEYS=y CONFIG_CRYPTO_MD5=m -CONFIG_CRYPTO_SHA1=m -CONFIG_CRYPTO_ARC4=y CONFIG_CRYPTO_TWOFISH=y # CONFIG_CRYPTO_ANSI_CPRNG is not set CONFIG_CRYPTO_DEV_UX500=y -# CONFIG_CRYPTO_DEV_UX500_CRYP is not set CONFIG_CRC7=y CONFIG_LIBCRC32C=m +CONFIG_AVERAGE=y -- cgit v1.2.3 From a4d9ba48c70e37b5a9244bf9cadc2abe5c372f6d Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Wed, 28 Sep 2011 09:48:38 +0200 Subject: hwmon: Make all ux500 mach build at once Alter the hwmon driver so we can build both the MACH_U5500 and the U8500 at the same time. Change-Id: I2ff9665427c1c33181552e7e51074c5eca3f1473 Signed-off-by: Robert Marklund --- drivers/hwmon/ab5500.c | 3 ++- drivers/hwmon/ab8500.c | 3 ++- drivers/hwmon/abx500.c | 7 ++++++- drivers/hwmon/abx500.h | 3 ++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/ab5500.c b/drivers/hwmon/ab5500.c index 7352e07ffde..0122f315ac6 100644 --- a/drivers/hwmon/ab5500.c +++ b/drivers/hwmon/ab5500.c @@ -25,6 +25,7 @@ #include #include #include "abx500.h" +#include /* AB5500 driver monitors GPADC - XTAL_TEMP, PCB_TEMP, * BTEMP_BALL, BAT_CTRL and DIE_TEMP @@ -159,7 +160,7 @@ static int ab5500_temp_irq_handler(int irq, struct abx500_temp *data) return 0; } -int __init abx500_hwmon_init(struct abx500_temp *data) +int __init ab5500_hwmon_init(struct abx500_temp *data) { int err; diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c index 9e81445322b..c6694206b4d 100644 --- a/drivers/hwmon/ab8500.c +++ b/drivers/hwmon/ab8500.c @@ -25,6 +25,7 @@ #include #include #include "abx500.h" +#include #define DEFAULT_POWER_OFF_DELAY 10000 @@ -136,7 +137,7 @@ static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) return 0; } -int __init abx500_hwmon_init(struct abx500_temp *data) +int __init ab8500_hwmon_init(struct abx500_temp *data) { data->ab8500_gpadc = ab8500_gpadc_get(); if (IS_ERR(data->ab8500_gpadc)) diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c index b26a13e8438..de4e6280b4b 100644 --- a/drivers/hwmon/abx500.c +++ b/drivers/hwmon/abx500.c @@ -36,6 +36,8 @@ #include #include #include +#include + #include "abx500.h" #define DEFAULT_MONITOR_DELAY 1000 @@ -610,7 +612,10 @@ static int __devinit abx500_temp_probe(struct platform_device *pdev) mutex_init(&data->lock); /* Chip specific initialization */ - err = abx500_hwmon_init(data); + if (!machine_is_u5500()) + err = ab8500_hwmon_init(data); + else + err = ab5500_hwmon_init(data); if (err < 0) { dev_err(&pdev->dev, "abx500 init failed"); goto exit; diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h index 65a9a8238ba..9fe28dac28f 100644 --- a/drivers/hwmon/abx500.h +++ b/drivers/hwmon/abx500.h @@ -89,6 +89,7 @@ struct abx500_temp { int monitored_sensors; }; -int abx500_hwmon_init(struct abx500_temp *data) __init; +int ab8500_hwmon_init(struct abx500_temp *data) __init; +int ab5500_hwmon_init(struct abx500_temp *data) __init; #endif /* _ABX500_H */ -- cgit v1.2.3 From 8dcb55aa5448e14aade346f3e47dcebb11b6c568 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 30 Sep 2011 10:27:32 +0200 Subject: smsc911x: Add regulator support Add some regulator support, there can be nessesary to add moreregulators to suite all power save needs. But this is a start. Also add a wait for the chip to be ready after the regulators are enabled, this was a bug in the old implementation. Change-Id: I5ed6732f9f86f0305ac6428bdb984836aac8b1f1 Signed-off-by: Robert Marklund --- drivers/net/smsc911x.c | 113 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/drivers/net/smsc911x.c b/drivers/net/smsc911x.c index c90ddb61cc5..3aa0cccc9fa 100644 --- a/drivers/net/smsc911x.c +++ b/drivers/net/smsc911x.c @@ -54,6 +54,8 @@ #include #include #include +#include + #include "smsc911x.h" #define SMSC_CHIPNAME "smsc911x" @@ -134,6 +136,10 @@ struct smsc911x_data { /* register access functions */ const struct smsc911x_ops *ops; + + /* regulators */ + struct regulator *regulator_vddvario; + struct regulator *regulator_vdd33a; }; /* Easy access to information */ @@ -358,6 +364,81 @@ out: spin_unlock_irqrestore(&pdata->dev_lock, flags); } +/* Enable resources(clocks and regulators) */ +static int smsc911x_enable_resources(struct platform_device *pdev, bool enable) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct smsc911x_data *pdata = netdev_priv(ndev); + int err = 0; + + /* enable/diable regulator for vddvario */ + if (pdata->regulator_vddvario) { + if (enable) { + err = regulator_enable(pdata->regulator_vddvario); + if (err < 0) { + netdev_err(ndev, "%s: regulator_enable failed '%s'\n", + __func__, "vddvario"); + } + } else + err = regulator_disable(pdata->regulator_vdd33a); + } + + /* enable/diableregulator for vdd33a */ + if (pdata->regulator_vdd33a) { + if (enable) { + err = regulator_enable(pdata->regulator_vdd33a); + if (err < 0) { + netdev_err(ndev, "%s: regulator_enable failed '%s'\n", + __func__, "vdd33a"); + } + } else + err = regulator_disable(pdata->regulator_vdd33a); + } + return err; +} + + +/* Request resources(clocks and regulators) */ +static int smsc911x_request_resources(struct platform_device *pdev, + bool request) +{ + struct net_device *ndev = platform_get_drvdata(pdev); + struct smsc911x_data *pdata = netdev_priv(ndev); + int err = 0; + + /* Request regulator for vddvario */ + if (request && !pdata->regulator_vddvario) { + pdata->regulator_vddvario = regulator_get(&pdev->dev, + "vddvario"); + if (IS_ERR(pdata->regulator_vddvario)) { + netdev_warn(ndev, + "%s: Failed to get regulator '%s'\n", + __func__, "vddvario"); + pdata->regulator_vddvario = NULL; + } + } else if (!request && pdata->regulator_vddvario) { + regulator_put(pdata->regulator_vddvario); + pdata->regulator_vddvario = NULL; + } + + /* Request regulator for vdd33a */ + if (request && !pdata->regulator_vddvario) { + pdata->regulator_vdd33a = regulator_get(&pdev->dev, + "vdd33a"); + if (IS_ERR(pdata->regulator_vdd33a)) { + netdev_warn(ndev, + "%s: Failed to get regulator '%s'\n", + __func__, "vdd33a"); + pdata->regulator_vdd33a = NULL; + } + } else if (!request && pdata->regulator_vdd33a) { + regulator_put(pdata->regulator_vdd33a); + pdata->regulator_vdd33a = NULL; + } + + return err; +} + /* waits for MAC not busy, with timeout. Only called by smsc911x_mac_read * and smsc911x_mac_write, so assumes mac_lock is held */ static int smsc911x_mac_complete(struct smsc911x_data *pdata) @@ -2049,6 +2130,7 @@ static int __devexit smsc911x_drv_remove(struct platform_device *pdev) struct net_device *dev; struct smsc911x_data *pdata; struct resource *res; + int retval; dev = platform_get_drvdata(pdev); BUG_ON(!dev); @@ -2076,6 +2158,12 @@ static int __devexit smsc911x_drv_remove(struct platform_device *pdev) iounmap(pdata->ioaddr); + if (smsc911x_enable_resources(pdev, false)) + pr_warn("Could not disable resource\n"); + + retval = smsc911x_request_resources(pdev, false); + /* ignore not all have regulators */ + free_netdev(dev); return 0; @@ -2106,6 +2194,7 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev) unsigned int intcfg = 0; int res_size, irq_flags; int retval; + int to = 100; pr_info("Driver version %s\n", SMSC_DRV_VERSION); @@ -2160,6 +2249,17 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev) pdata->dev = dev; pdata->msg_enable = ((1 << debug) - 1); + platform_set_drvdata(pdev, dev); + + retval = smsc911x_request_resources(pdev, true); + /* ignore not all have regulators */ + + retval = smsc911x_enable_resources(pdev, true); + if (retval) { + pr_warn("Could not enable resource\n"); + goto out_0; + } + if (pdata->ioaddr == NULL) { SMSC_WARN(pdata, probe, "Error smsc911x base address invalid"); retval = -ENOMEM; @@ -2172,6 +2272,18 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev) if (config->shift) pdata->ops = &shifted_smsc911x_ops; + /* poll the READY bit in PMT_CTRL. Any other access to the device is + * forbidden while this bit isn't set. Try for 100ms + */ + while (!(smsc911x_reg_read(pdata, PMT_CTRL) & PMT_CTRL_READY_) && --to) + udelay(1000); + + if (to == 0) { + pr_err("Device not READY in 100ms aborting\n"); + goto out_0; + } + + retval = smsc911x_init(dev); if (retval < 0) goto out_unmap_io_3; @@ -2264,6 +2376,7 @@ out_0: return retval; } + #ifdef CONFIG_PM /* This implementation assumes the devices remains powered on its VDDVARIO * pins during suspend. */ -- cgit v1.2.3 From c5d9c8ff3d3d4bc491f8ab39ffccb592a75734f7 Mon Sep 17 00:00:00 2001 From: Dmitry Tarnyagin Date: Mon, 10 Oct 2011 12:28:49 +0200 Subject: u8500: Enable cw1200 in defconfig Change-Id: Ifbc169cdb432cdda7b2efba3550cb703f007e6da Signed-off-by: Dmitry Tarnyagin Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33544 Reviewed-by: Bartosz MARKOWSKI Reviewed-by: Philippe LANGLAIS --- arch/arm/configs/u8500_defconfig | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index f17c1a0c796..ee524c9a1d8 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -96,10 +96,10 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y -CONFIG_CFG80211=y +CONFIG_CFG80211=m CONFIG_NL80211_TESTMODE=y CONFIG_CFG80211_REG_DEBUG=y -# CONFIG_CFG80211_WEXT is not set +CONFIG_CFG80211_WEXT=y CONFIG_RFKILL=y CONFIG_RFKILL_INPUT=y CONFIG_NET_9P=y @@ -244,6 +244,11 @@ CONFIG_CG2900_UART=y CONFIG_CG2900_AUDIO=y CONFIG_CG2900_TEST=y CONFIG_BT_CG2900=y +CONFIG_CW1200=m +# CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES is not set +CONFIG_CW1200_USE_GPIO_IRQ=y +CONFIG_CW1200_WOW=y +CONFIG_CW1200_DEBUGFS=y CONFIG_U8500_MMIO=y CONFIG_U8500_CM=y CONFIG_U8500_FLASH=y -- cgit v1.2.3 From 6ac90ec698b6e11b3eac7842741eb62216ffc10c Mon Sep 17 00:00:00 2001 From: John Fredriksson Date: Wed, 12 Oct 2011 15:15:17 +0200 Subject: u8500_defconfig: Enable support for x11. Make it possible to use x11 window system. Signed-off-by: John Fredriksson Change-Id: I2b916c72ac1a9f337dac8da59a9ca43ee3803bd2 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33812 Reviewed-by: Philippe LANGLAIS Tested-by: Philippe LANGLAIS --- arch/arm/configs/u8500_defconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index ee524c9a1d8..1ef71d5a302 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -155,7 +155,6 @@ CONFIG_SERIAL_AMBA_PL011_CONSOLE=y CONFIG_SERIAL_AMBA_PL011_CLOCK_CONTROL=y CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM_NOMADIK=y -CONFIG_I2C=y CONFIG_I2C_CHARDEV=y CONFIG_I2C_NOMADIK=y CONFIG_SPI=y @@ -186,9 +185,11 @@ CONFIG_VIDEO_DEV=y # CONFIG_MEDIA_TUNER_CUSTOMISE is not set # CONFIG_VIDEO_CAPTURE_DRIVERS is not set CONFIG_RADIO_CG2900=y +CONFIG_DRM=y +CONFIG_GPU_MALI=y CONFIG_FB=y CONFIG_FB_MCDE=y -# CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE is not set +CONFIG_MCDE_FB_AVOID_REALLOC=y CONFIG_AV8100_HWTRIG_I2SDAT3=y CONFIG_FB_B2R2=y CONFIG_B2R2_PLUG_CONF=y -- cgit v1.2.3 From 55c4890c7fcc3863399d6572844d95188141d522 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 14 Oct 2011 15:28:36 +0200 Subject: config: enable usb modules Change-Id: I9cefa3c2089df1b0cc70e21f28a7550dae16e8fd Signed-off-by: Robert Marklund Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/34109 Reviewed-by: Philippe LANGLAIS --- arch/arm/configs/u8500_defconfig | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 1ef71d5a302..f7fb3a29192 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -96,10 +96,8 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y -CONFIG_CFG80211=m CONFIG_NL80211_TESTMODE=y CONFIG_CFG80211_REG_DEBUG=y -CONFIG_CFG80211_WEXT=y CONFIG_RFKILL=y CONFIG_RFKILL_INPUT=y CONFIG_NET_9P=y @@ -216,6 +214,13 @@ CONFIG_USB_STORAGE=y CONFIG_USB_LIBUSUAL=y CONFIG_USB_GADGET=y CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_USB_ZERO=m +CONFIG_USB_ETH=m +CONFIG_USB_MASS_STORAGE=m +CONFIG_USB_G_SERIAL=m +CONFIG_USB_CDC_COMPOSITE=m +CONFIG_USB_G_MULTI=m +# CONFIG_USB_G_MULTI_RNDIS is not set CONFIG_AB8500_USB=y CONFIG_MMC=y CONFIG_MMC_UNSAFE_RESUME=y @@ -246,9 +251,7 @@ CONFIG_CG2900_AUDIO=y CONFIG_CG2900_TEST=y CONFIG_BT_CG2900=y CONFIG_CW1200=m -# CONFIG_CW1200_NON_POWER_OF_TWO_BLOCKSIZES is not set CONFIG_CW1200_USE_GPIO_IRQ=y -CONFIG_CW1200_WOW=y CONFIG_CW1200_DEBUGFS=y CONFIG_U8500_MMIO=y CONFIG_U8500_CM=y @@ -316,4 +319,3 @@ CONFIG_CRYPTO_TWOFISH=y CONFIG_CRYPTO_DEV_UX500=y CONFIG_CRC7=y CONFIG_LIBCRC32C=m -CONFIG_AVERAGE=y -- cgit v1.2.3 From 19476d6563b6fdf28e85ce2d27272ec0dee2f15f Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 14 Oct 2011 15:56:48 +0200 Subject: config: enable dynamic debug Change-Id: I89dc5e064002cf47d0c1b65adec2e58655ace572 Signed-off-by: Robert Marklund Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/34117 Reviewed-by: Philippe LANGLAIS --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index f7fb3a29192..e78e8bb36cc 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -311,6 +311,7 @@ CONFIG_DEBUG_SPINLOCK_SLEEP=y CONFIG_DEBUG_INFO=y CONFIG_SYSCTL_SYSCALL_CHECK=y CONFIG_FUNCTION_TRACER=y +CONFIG_DYNAMIC_DEBUG=y CONFIG_DEBUG_USER=y CONFIG_KEYS=y CONFIG_CRYPTO_MD5=m -- cgit v1.2.3 From 484681fa5ff4beddaae78d4fdebc20dcf2b0497b Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Fri, 14 Oct 2011 18:26:08 +0200 Subject: config: Enable usb HID and FS Change-Id: I6767174545255e89131d159421606ecc1fc082bb Signed-off-by: Robert Marklund Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/34134 Reviewed-by: Philippe LANGLAIS --- arch/arm/configs/u8500_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index e78e8bb36cc..ff3704b24eb 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -216,11 +216,13 @@ CONFIG_USB_GADGET=y CONFIG_USB_GADGET_VBUS_DRAW=500 CONFIG_USB_ZERO=m CONFIG_USB_ETH=m +CONFIG_USB_FILE_STORAGE=m CONFIG_USB_MASS_STORAGE=m CONFIG_USB_G_SERIAL=m CONFIG_USB_CDC_COMPOSITE=m CONFIG_USB_G_MULTI=m # CONFIG_USB_G_MULTI_RNDIS is not set +CONFIG_USB_G_HID=m CONFIG_AB8500_USB=y CONFIG_MMC=y CONFIG_MMC_UNSAFE_RESUME=y -- cgit v1.2.3 From 44f1638cd852068575e59e6f74f102afdd903aba Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 18 Oct 2011 20:49:50 +0100 Subject: DocBook: add input documentations generation Signed-off-by: Philippe Langlais --- Documentation/DocBook/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile index 3f118e4defb..91f06ffcfdc 100644 --- a/Documentation/DocBook/Makefile +++ b/Documentation/DocBook/Makefile @@ -15,7 +15,10 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ 80211.xml debugobjects.xml sh.xml regulator.xml \ alsa-driver-api.xml writing-an-alsa-driver.xml \ tracepoint.xml drm.xml media_api.xml \ - stmpe.xml + stmpe.xml \ + touchp.xml tc_keypad.xml \ + synaptics_rmi4_touchp.xml db5500_keypad.xml \ + ste_ff_vibra.xml include $(srctree)/Documentation/DocBook/media/Makefile -- cgit v1.2.3 From f25e00dde705b9996886eabd06e425344daee0b5 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 19 Oct 2011 09:40:44 +0200 Subject: stmpe: Fix warning Signed-off-by: Philippe Langlais --- include/linux/mfd/stmpe.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/mfd/stmpe.h b/include/linux/mfd/stmpe.h index be1af7c42e5..54046d1d6b5 100644 --- a/include/linux/mfd/stmpe.h +++ b/include/linux/mfd/stmpe.h @@ -107,7 +107,7 @@ struct matrix_keymap_data; * @no_autorepeat: disable key autorepeat */ struct stmpe_keypad_platform_data { - struct matrix_keymap_data *keymap_data; + const struct matrix_keymap_data *keymap_data; unsigned int debounce_ms; unsigned int scan_count; bool no_autorepeat; -- cgit v1.2.3 From e4cdd1ef2c9f5ea46dff051b9f213e2f9c28d51b Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 19 Oct 2011 11:24:34 +0200 Subject: mach-ux500: add ab8500 bm support Signed-off-by: Philippe Langlais --- arch/arm/mach-ux500/board-mop500-bm.c | 411 ++++++++++++++++++++++++++++++++++ arch/arm/mach-ux500/board-mop500-bm.h | 24 ++ 2 files changed, 435 insertions(+) create mode 100644 arch/arm/mach-ux500/board-mop500-bm.c create mode 100644 arch/arm/mach-ux500/board-mop500-bm.h diff --git a/arch/arm/mach-ux500/board-mop500-bm.c b/arch/arm/mach-ux500/board-mop500-bm.c new file mode 100644 index 00000000000..031d8721f4a --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-bm.c @@ -0,0 +1,411 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U8500 board specific charger and battery initialization parameters. + * + * Author: Johan Palsson for ST-Ericsson. + * Author: Johan Gardsmark for ST-Ericsson. + * + */ + +#include +#include +#include "board-mop500-bm.h" + +#ifdef AB8500_BATTERY_THERM_ON_BATCTRL +/* + * These are the defined batteries that uses a NTC and ID resistor placed + * inside of the battery pack. + * Note that the res_to_temp table must be strictly sorted by falling resistance + * values to work. + */ +static struct res_to_temp temp_tbl_A[] = { + {-5, 53407}, + { 0, 48594}, + { 5, 43804}, + {10, 39188}, + {15, 34870}, + {20, 30933}, + {25, 27422}, + {30, 24347}, + {35, 21694}, + {40, 19431}, + {45, 17517}, + {50, 15908}, + {55, 14561}, + {60, 13437}, + {65, 12500}, +}; +static struct res_to_temp temp_tbl_B[] = { + {-5, 165418}, + { 0, 159024}, + { 5, 151921}, + {10, 144300}, + {15, 136424}, + {20, 128565}, + {25, 120978}, + {30, 113875}, + {35, 107397}, + {40, 101629}, + {45, 96592}, + {50, 92253}, + {55, 88569}, + {60, 85461}, + {65, 82869}, +}; +static struct v_to_cap cap_tbl_A[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; +static struct v_to_cap cap_tbl_B[] = { + {4161, 100}, + {4124, 98}, + {4044, 90}, + {4003, 85}, + {3966, 80}, + {3933, 75}, + {3888, 67}, + {3849, 60}, + {3813, 55}, + {3787, 47}, + {3772, 30}, + {3751, 25}, + {3718, 20}, + {3681, 16}, + {3660, 14}, + {3589, 10}, + {3546, 7}, + {3495, 4}, + {3404, 2}, + {3250, 0}, +}; +#endif +static struct v_to_cap cap_tbl[] = { + {4186, 100}, + {4163, 99}, + {4114, 95}, + {4068, 90}, + {3990, 80}, + {3926, 70}, + {3898, 65}, + {3866, 60}, + {3833, 55}, + {3812, 50}, + {3787, 40}, + {3768, 30}, + {3747, 25}, + {3730, 20}, + {3705, 15}, + {3699, 14}, + {3684, 12}, + {3672, 9}, + {3657, 7}, + {3638, 6}, + {3556, 4}, + {3424, 2}, + {3317, 1}, + {3094, 0}, +}; + +/* + * Note that the res_to_temp table must be strictly sorted by falling + * resistance values to work. + */ +static struct res_to_temp temp_tbl[] = { + {-5, 214834}, + { 0, 162943}, + { 5, 124820}, + {10, 96520}, + {15, 75306}, + {20, 59254}, + {25, 47000}, + {30, 37566}, + {35, 30245}, + {40, 24520}, + {45, 20010}, + {50, 16432}, + {55, 13576}, + {60, 11280}, + {65, 9425}, +}; + +static const struct battery_type bat_type[] = { + [BATTERY_UNKNOWN] = { + /* First element always represent the UNKNOWN battery */ + .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, + .resis_high = 0, + .resis_low = 0, + .battery_resistance = 300, + .charge_full_design = 612, + .nominal_voltage = 3700, + .termination_vol = 4050, + .termination_curr = 200, + .normal_cur_lvl = 400, + .normal_vol_lvl = 4100, + .maint_a_cur_lvl = 400, + .maint_a_vol_lvl = 4050, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 400, + .maint_b_vol_lvl = 4000, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + +#ifdef AB8500_BATTERY_THERM_ON_BATCTRL + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 53407, + .resis_low = 12500, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_A), + .r_to_t_tbl = temp_tbl_A, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_A), + .v_to_cap_tbl = cap_tbl_A, + + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 165418, + .resis_low = 82869, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_B), + .r_to_t_tbl = temp_tbl_B, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_B), + .v_to_cap_tbl = cap_tbl_B, + }, +#else +/* + * These are the batteries that doesn't have an internal NTC resistor to measure + * its temperature. The temperature in this case is measure with a NTC placed + * near the battery but on the PCB. + */ + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 76000, + .resis_low = 53000, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 30000, + .resis_low = 10000, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 95000, + .resis_low = 76001, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + }, +#endif +}; + +static char *ab8500_charger_supplied_to[] = { + "ab8500_chargalg", + "ab8500_fg", + "ab8500_btemp", +}; + +static char *ab8500_btemp_supplied_to[] = { + "ab8500_chargalg", + "ab8500_fg", +}; + +static char *ab8500_fg_supplied_to[] = { + "ab8500_chargalg", +}; + +static char *ab8500_chargalg_supplied_to[] = { + "ab8500_fg", +}; + +struct ab8500_charger_platform_data ab8500_charger_plat_data = { + .supplied_to = ab8500_charger_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_charger_supplied_to), +}; + +struct ab8500_btemp_platform_data ab8500_btemp_plat_data = { + .supplied_to = ab8500_btemp_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_btemp_supplied_to), +}; + +struct ab8500_fg_platform_data ab8500_fg_plat_data = { + .supplied_to = ab8500_fg_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_fg_supplied_to), +}; + +struct ab8500_chargalg_platform_data ab8500_chargalg_plat_data = { + .supplied_to = ab8500_chargalg_supplied_to, + .num_supplicants = ARRAY_SIZE(ab8500_chargalg_supplied_to), +}; + +static const struct ab8500_bm_capacity_levels cap_levels = { + .critical = 2, + .low = 10, + .normal = 70, + .high = 95, + .full = 100, +}; + +static const struct ab8500_fg_parameters fg = { + .recovery_sleep_timer = 10, + .recovery_total_time = 100, + .init_timer = 1, + .init_discard_time = 5, + .init_total_time = 40, + .high_curr_time = 60, + .accu_charging = 30, + .accu_high_curr = 30, + .high_curr_threshold = 50, + .lowbat_threshold = 3100, +}; + +static const struct ab8500_maxim_parameters maxi_params = { + .ena_maxi = true, + .chg_curr = 910, + .wait_cycles = 10, + .charger_curr_step = 100, +}; + +static const struct ab8500_bm_charger_parameters chg = { + .usb_volt_max = 5500, + .usb_curr_max = 1500, + .ac_volt_max = 7500, + .ac_curr_max = 1500, +}; + +struct ab8500_bm_data ab8500_bm_data = { + .temp_under = 3, + .temp_low = 8, + .temp_high = 55, + .temp_over = 60, + .main_safety_tmr_h = 4, + .usb_safety_tmr_h = 4, + .bkup_bat_v = BUP_VCH_SEL_2P6V, + .bkup_bat_i = BUP_ICH_SEL_150UA, +#ifdef AB8500_BATTERY_THERM_ON_BATCTRL + .adc_therm = ADC_THERM_BATCTRL, +#else + .adc_therm = ADC_THERM_BATTEMP, +#endif + .chg_unknown_bat = false, + .enable_overshoot = false, + .fg_res = 10, + .cap_levels = &cap_levels, + .bat_type = bat_type, + .n_btypes = ARRAY_SIZE(bat_type), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .maxi = &maxi_params, + .chg_params = &chg, + .fg_params = &fg, +}; diff --git a/arch/arm/mach-ux500/board-mop500-bm.h b/arch/arm/mach-ux500/board-mop500-bm.h new file mode 100644 index 00000000000..eb2450f1ab5 --- /dev/null +++ b/arch/arm/mach-ux500/board-mop500-bm.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) ST-Ericsson SA 2011 + * + * License terms: GNU General Public License (GPL), version 2 + * + * U8500 board specific charger and battery initialization parameters. + * + * Author: Johan Palsson for ST-Ericsson. + * Author: Johan Gardsmark for ST-Ericsson. + * + */ + +#ifndef __BOARD_MOP500_BM_H +#define __BOARD_MOP500_BM_H + +#include + +extern struct ab8500_charger_platform_data ab8500_charger_plat_data; +extern struct ab8500_btemp_platform_data ab8500_btemp_plat_data; +extern struct ab8500_fg_platform_data ab8500_fg_plat_data; +extern struct ab8500_chargalg_platform_data ab8500_chargalg_plat_data; +extern struct ab8500_bm_data ab8500_bm_data; + +#endif -- cgit v1.2.3 From c48073086685e61649c7f3ae1c24478f402894f9 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 19 Oct 2011 11:25:00 +0200 Subject: ux500: Fix CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL configuration Remove the temporary AB8500_BATTERY_THERM_ON_BATCTRL flag and use CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL instead ST-Ericsson Linux next: - ST-Ericsson Change-ID: ER330310 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ib4328deaa4fafe887f9082fc8a1e8e0c618e0d61 Signed-off-by: Mikael HEDEN Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/19014 Reviewed-by: Johan PALSSON Reviewed-by: Johan GARDSMARK Reviewed-by: QATOOLS Reviewed-by: Jonas ABERG Conflicts: arch/arm/mach-ux500/Makefile --- arch/arm/mach-ux500/board-mop500-bm.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500-bm.c b/arch/arm/mach-ux500/board-mop500-bm.c index 031d8721f4a..945964f3300 100644 --- a/arch/arm/mach-ux500/board-mop500-bm.c +++ b/arch/arm/mach-ux500/board-mop500-bm.c @@ -14,7 +14,7 @@ #include #include "board-mop500-bm.h" -#ifdef AB8500_BATTERY_THERM_ON_BATCTRL +#ifdef CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL /* * These are the defined batteries that uses a NTC and ID resistor placed * inside of the battery pack. @@ -176,7 +176,7 @@ static const struct battery_type bat_type[] = { .v_to_cap_tbl = cap_tbl, }, -#ifdef AB8500_BATTERY_THERM_ON_BATCTRL +#ifdef CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL { .name = POWER_SUPPLY_TECHNOLOGY_LIPO, .resis_high = 53407, @@ -390,7 +390,7 @@ struct ab8500_bm_data ab8500_bm_data = { .usb_safety_tmr_h = 4, .bkup_bat_v = BUP_VCH_SEL_2P6V, .bkup_bat_i = BUP_ICH_SEL_150UA, -#ifdef AB8500_BATTERY_THERM_ON_BATCTRL +#ifdef CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL .adc_therm = ADC_THERM_BATCTRL, #else .adc_therm = ADC_THERM_BATTEMP, -- cgit v1.2.3 From 91f860d3ad73302b14c7c9b2ea56ee3896f31ad2 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 12 Oct 2011 11:20:48 +0200 Subject: mach-ux500: power: ab8500_bm: Removal of maintenance charging This patch makes it possible to remove the maintenance A and B charging states and replaces them with a state that restarts the charging when the battery voltage level dropped below a certain level. The maintenance charging can now be turned of by setting a flag in the board configuration. ST-Ericsson Linux Next: - ST-Ericsson ID: CR333019 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ie5e8ab20a3b57a1028d544371b10ffbb1fd77660 Signed-off-by: Kalle Komierowski Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/19916 Reviewed-by: Johan GARDSMARK Reviewed-by: Johan PALSSON Reviewed-by: Jonas ABERG Tested-by: Karl KOMIEROWSKI Conflicts: drivers/power/ab8500_chargalg.c drivers/power/ab8500_fg.c include/linux/mfd/ab8500/bm.h --- arch/arm/mach-ux500/board-mop500-bm.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm/mach-ux500/board-mop500-bm.c b/arch/arm/mach-ux500/board-mop500-bm.c index 945964f3300..66ced8e088e 100644 --- a/arch/arm/mach-ux500/board-mop500-bm.c +++ b/arch/arm/mach-ux500/board-mop500-bm.c @@ -160,6 +160,7 @@ static const struct battery_type bat_type[] = { .nominal_voltage = 3700, .termination_vol = 4050, .termination_curr = 200, + .recharge_vol = 3990, .normal_cur_lvl = 400, .normal_vol_lvl = 4100, .maint_a_cur_lvl = 400, @@ -186,6 +187,7 @@ static const struct battery_type bat_type[] = { .nominal_voltage = 3600, .termination_vol = 4150, .termination_curr = 80, + .recharge_vol = 4130, .normal_cur_lvl = 700, .normal_vol_lvl = 4200, .maint_a_cur_lvl = 600, @@ -211,6 +213,7 @@ static const struct battery_type bat_type[] = { .nominal_voltage = 3600, .termination_vol = 4150, .termination_curr = 80, + .recharge_vol = 4130, .normal_cur_lvl = 700, .normal_vol_lvl = 4200, .maint_a_cur_lvl = 600, @@ -241,6 +244,7 @@ static const struct battery_type bat_type[] = { .nominal_voltage = 3700, .termination_vol = 4150, .termination_curr = 100, + .recharge_vol = 4130, .normal_cur_lvl = 700, .normal_vol_lvl = 4200, .maint_a_cur_lvl = 600, @@ -265,6 +269,7 @@ static const struct battery_type bat_type[] = { .nominal_voltage = 3700, .termination_vol = 4150, .termination_curr = 100, + .recharge_vol = 4130, .normal_cur_lvl = 700, .normal_vol_lvl = 4200, .maint_a_cur_lvl = 600, @@ -289,6 +294,7 @@ static const struct battery_type bat_type[] = { .nominal_voltage = 3700, .termination_vol = 4150, .termination_curr = 100, + .recharge_vol = 4130, .normal_cur_lvl = 700, .normal_vol_lvl = 4200, .maint_a_cur_lvl = 600, @@ -390,6 +396,7 @@ struct ab8500_bm_data ab8500_bm_data = { .usb_safety_tmr_h = 4, .bkup_bat_v = BUP_VCH_SEL_2P6V, .bkup_bat_i = BUP_ICH_SEL_150UA, + .no_maintenance = false, #ifdef CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL .adc_therm = ADC_THERM_BATCTRL, #else -- cgit v1.2.3 From d7e56b0c435af5e35630a91a6256727caddda15f Mon Sep 17 00:00:00 2001 From: Johan Palsson Date: Thu, 7 Jul 2011 11:45:03 +0200 Subject: power: ab8500_bm: Measure battery temperature when not charging Battery temperature is now measured even when no charger is connected. ST-Ericsson ID: 342846 ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ia861437635bcdcdc32b259aff05d889b449ee682 Signed-off-by: Johan Palsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/26608 Reviewed-by: QATEST Reviewed-by: Mattias WALLIN --- arch/arm/mach-ux500/board-mop500-bm.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/mach-ux500/board-mop500-bm.c b/arch/arm/mach-ux500/board-mop500-bm.c index 66ced8e088e..86be12cad3b 100644 --- a/arch/arm/mach-ux500/board-mop500-bm.c +++ b/arch/arm/mach-ux500/board-mop500-bm.c @@ -393,6 +393,8 @@ struct ab8500_bm_data ab8500_bm_data = { .temp_high = 55, .temp_over = 60, .main_safety_tmr_h = 4, + .temp_interval_chg = 20, + .temp_interval_nochg = 120, .usb_safety_tmr_h = 4, .bkup_bat_v = BUP_VCH_SEL_2P6V, .bkup_bat_i = BUP_ICH_SEL_150UA, -- cgit v1.2.3 From 7a5c4ec740885346085f45423f1ad5f6557c3d03 Mon Sep 17 00:00:00 2001 From: Andrew Lynn Date: Thu, 18 Aug 2011 15:08:21 +0100 Subject: ab8500-bm: Ground lift compensation for Tbat Measure Ibat and remove the resulting voltage between Vbat- and Gnd from Vntc readings. ST-Ericsson ID: 350661 ST-Ericsson FOSS-OUT ID: Trivial ST-Ericsson Linux next: NA Change-Id: I232b9fc7da3ea69e2062feda98776ded33d6253b Signed-off-by: Andrew Lynn Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/27869 Reviewed-by: QATOOLS Reviewed-by: QABUILD Reviewed-by: QATEST Reviewed-by: Johan PALSSON Reviewed-by: Jonas ABERG Reviewed-by: Karl KOMIEROWSKI --- arch/arm/mach-ux500/board-mop500-bm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mach-ux500/board-mop500-bm.c b/arch/arm/mach-ux500/board-mop500-bm.c index 86be12cad3b..1d8730408fa 100644 --- a/arch/arm/mach-ux500/board-mop500-bm.c +++ b/arch/arm/mach-ux500/board-mop500-bm.c @@ -414,6 +414,7 @@ struct ab8500_bm_data ab8500_bm_data = { .interval_charging = 5, .interval_not_charging = 120, .temp_hysteresis = 3, + .gnd_lift_resistance = 34, .maxi = &maxi_params, .chg_params = &chg, .fg_params = &fg, -- cgit v1.2.3 From f9a51e792c53fe980b4bb757209eea7412de1c68 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Mon, 17 Oct 2011 10:34:46 +0200 Subject: mach-ux500: power: ab8500_charger: Lower VBUS input current when below threshold Due to a bug in AB8500 the input current to the VBUS charger needs to be lowered when the battery voltage is below 3,7 V. Otherwise there is a risk that a higher than allowed current is drawn from the USB host. Signed-off-by: Kalle Komierowski --- arch/arm/mach-ux500/board-mop500-bm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/mach-ux500/board-mop500-bm.c b/arch/arm/mach-ux500/board-mop500-bm.c index 1d8730408fa..78f20626cab 100644 --- a/arch/arm/mach-ux500/board-mop500-bm.c +++ b/arch/arm/mach-ux500/board-mop500-bm.c @@ -326,6 +326,7 @@ static char *ab8500_btemp_supplied_to[] = { static char *ab8500_fg_supplied_to[] = { "ab8500_chargalg", + "ab8500_usb", }; static char *ab8500_chargalg_supplied_to[] = { -- cgit v1.2.3 From b814e2a650836500976acffa996860033414e2ed Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Wed, 19 Oct 2011 16:04:48 +0200 Subject: mfd: fix dependency problem with PRCMU Signed-off-by: Philippe Langlais --- drivers/mfd/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 432a259e444..66226efb07a 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -621,7 +621,7 @@ config AB8500_CORE config AB8500_I2C_CORE bool "AB8500 register access via PRCMU I2C" - depends on AB8500_CORE && MFD_DB8500_PRCMU + depends on AB8500_CORE default y help This enables register access to the AB8500 chip via PRCMU I2C. -- cgit v1.2.3 From bd084990f590260ba13a4c4648c3b8d5efb21723 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 20 Oct 2011 10:21:27 +0200 Subject: Documentation: Update DocBook makefile to generate all ux500 specific driver doc Signed-off-by: Philippe Langlais --- Documentation/DocBook/Makefile | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Documentation/DocBook/Makefile b/Documentation/DocBook/Makefile index 91f06ffcfdc..80c42603ff2 100644 --- a/Documentation/DocBook/Makefile +++ b/Documentation/DocBook/Makefile @@ -15,10 +15,11 @@ DOCBOOKS := z8530book.xml mcabook.xml device-drivers.xml \ 80211.xml debugobjects.xml sh.xml regulator.xml \ alsa-driver-api.xml writing-an-alsa-driver.xml \ tracepoint.xml drm.xml media_api.xml \ - stmpe.xml \ - touchp.xml tc_keypad.xml \ + i2s.xml msp.xml shrm.xml stmpe.xml touchp.xml \ + tc_keypad.xml prcmu-fw-api.xml cg2900_fm_radio.xml \ synaptics_rmi4_touchp.xml db5500_keypad.xml \ - ste_ff_vibra.xml + u5500_LogicalMailbox.xml cg2900.xml \ + lsm303dlh.xml ste_ff_vibra.xml ux500_usb.xml include $(srctree)/Documentation/DocBook/media/Makefile -- cgit v1.2.3 From 599cf838f69240ae65a82a1f0689b67afc07b38c Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 20 Oct 2011 15:56:07 +0200 Subject: mfd: abx500: input: Add accessory detection driver Add driver for the accessory detection block of abx500 PMIC. Signed-off-by: Virupax Sadashivpetimath Signed-off-by: Philippe Langlais --- include/linux/mfd/ab8500.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/mfd/ab8500.h b/include/linux/mfd/ab8500.h index 13924d0c861..eb613588110 100644 --- a/include/linux/mfd/ab8500.h +++ b/include/linux/mfd/ab8500.h @@ -173,6 +173,7 @@ struct ab8500 { struct regulator_reg_init; struct regulator_init_data; +struct ab8500_accdet_platform_data; struct ab8500_denc_platform_data; struct ab8500_audio_platform_data; struct ab8500_gpio_platform_data; @@ -189,6 +190,7 @@ struct ab8500_gpio_platform_data; * @regulator_reg_init: regulator init registers * @num_regulator: number of regulators * @regulator: machine-specific constraints for regulators + * @accdet: machine-specific Accessory detection data * @battery: machine-specific battery management data * @charger: machine-specific charger data * @btemp: machine-specific battery temp data @@ -204,6 +206,7 @@ struct ab8500_platform_data { struct ab8500_regulator_reg_init *regulator_reg_init; int num_regulator; struct regulator_init_data *regulator; + struct abx500_accdet_platform_data *accdet; struct ab8500_bm_data *battery; struct ab8500_denc_platform_data *denc; struct ab8500_audio_platform_data *audio; -- cgit v1.2.3 From d8e15145c61bb2e54fb8c9243fb99bc5fa4c0435 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 20 Oct 2011 15:13:35 +0200 Subject: config: Update u8500 defconfig Signed-off-by: Philippe Langlais --- arch/arm/configs/u8500_defconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index ff3704b24eb..b82ec33f84d 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -239,7 +239,6 @@ CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_RTC_CLASS=y CONFIG_RTC_DRV_AB=y CONFIG_RTC_DRV_AB8500=y -CONFIG_RTC_DRV_PL031=y CONFIG_DMADEVICES=y CONFIG_STE_DMA40=y CONFIG_STAGING=y @@ -320,5 +319,6 @@ CONFIG_CRYPTO_MD5=m CONFIG_CRYPTO_TWOFISH=y # CONFIG_CRYPTO_ANSI_CPRNG is not set CONFIG_CRYPTO_DEV_UX500=y +CONFIG_CRYPTO_DEV_UX500_HASH=y CONFIG_CRC7=y CONFIG_LIBCRC32C=m -- cgit v1.2.3 From ddd54e678a39d389ef9e2e05386268977e10dc80 Mon Sep 17 00:00:00 2001 From: Virupax Sadashivpetimath Date: Mon, 26 Sep 2011 21:19:13 +0530 Subject: input:misc: Remove TVout support in accessory driver The TVout hardware block is not used, so remove the related code. ST-Ericsson ID: 355539 ST-Ericsson Linux next: 344984 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I0f0f39b13f5b48de6096d076b9dd5253a3c14346 Signed-off-by: Virupax Sadashivpetimath Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32003 Reviewed-by: Srinidhi KASAGAR --- drivers/mfd/ab5500-core.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/drivers/mfd/ab5500-core.c b/drivers/mfd/ab5500-core.c index eac3818f70d..0d39a0caff0 100755 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -808,7 +808,7 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { [AB5500_DEVID_ACCDET] = { .name = "ab5500-acc-det", .id = AB5500_DEVID_ACCDET, - .num_resources = 10, + .num_resources = 8, .resources = (struct resource[]) { { .name = "acc_detedt22db_rising", @@ -858,18 +858,6 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { .start = AB5500_IRQ(3, 0), .end = AB5500_IRQ(3, 0), }, - { - .name = "plugTVdet", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(13, 7), - .end = AB5500_IRQ(13, 7), - }, - { - .name = "plugTVdet_removal", - .flags = IORESOURCE_IRQ, - .start = AB5500_IRQ(23, 2), - .end = AB5500_IRQ(23, 2), - }, }, }, }; -- cgit v1.2.3 From 7b883dde5b7c9dcd8b70c52f46c384b5e2d4532e Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Fri, 30 Sep 2011 09:43:41 +0200 Subject: ARM: u8500: configs: Enable regulator suspend force ST-Ericsson Linux next: - ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I26ba2f655d4fb65708913cdeda632e2ca35fea0a Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32671 Reviewed-by: Bengt JONSSON --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index b82ec33f84d..85dfede4aa9 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -177,6 +177,7 @@ CONFIG_MFD_DB8500_PRCMU=y CONFIG_REGULATOR_DEBUG=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y +CONFIG_REGULATOR_AB8500_DEBUG=y CONFIG_REGULATOR_DB8500_PRCMU=y CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y -- cgit v1.2.3 From 2e59b6d22540cbcdc0bf62867ad09052514aee33 Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Fri, 30 Sep 2011 10:26:39 +0530 Subject: u5500: enable cpuidle debug ST-Ericsson ID: 361450 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I809d2d8e76532f6930f27764b76748110c7a492c Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32582 --- arch/arm/configs/u8500_defconfig | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 85dfede4aa9..6184ecb1645 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -51,7 +51,6 @@ CONFIG_CPU_FREQ_GOV_POWERSAVE=y CONFIG_CPU_FREQ_GOV_USERSPACE=y CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y CONFIG_CPU_IDLE=y -CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 CONFIG_UX500_CPUIDLE_DEBUG=y CONFIG_FPE_NWFPE=y CONFIG_VFP=y -- cgit v1.2.3 From c6a93cc7f87848aa5320725f2dc440321fb3c30f Mon Sep 17 00:00:00 2001 From: Rabin Vincent Date: Fri, 30 Sep 2011 10:26:39 +0530 Subject: u5500: cpuidle: enable ApIdle ST-Ericsson ID: 361450 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I75dc4afafc493412a128071b10da3a8b773b3bf5 Signed-off-by: Rabin Vincent Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32727 Reviewed-by: Srinidhi KASAGAR --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 6184ecb1645..85dfede4aa9 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -51,6 +51,7 @@ CONFIG_CPU_FREQ_GOV_POWERSAVE=y CONFIG_CPU_FREQ_GOV_USERSPACE=y CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y CONFIG_CPU_IDLE=y +CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 CONFIG_UX500_CPUIDLE_DEBUG=y CONFIG_FPE_NWFPE=y CONFIG_VFP=y -- cgit v1.2.3 From c886c5a3d04dde01a06eb358078899ee26ed6ec5 Mon Sep 17 00:00:00 2001 From: Per Fransson Date: Fri, 23 Sep 2011 14:11:24 +0200 Subject: ux500: Start crash dump through SW reset When using kexec/kdump without an crash kernel image loaded: * clean the caches and write the crash_notes * perform a restart ST-Ericsson ID: 340331 Change-Id: I1ae34ed2b5e43da4849650a8a7d2f1e453dcbe93 Signed-off-by: Per Fransson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32678 Reviewed-by: Jonas ABERG Reviewed-by: Srinidhi KASAGAR --- arch/arm/Kconfig | 7 +++++++ arch/arm/kernel/machine_kexec.c | 10 ++++++++++ include/linux/kexec.h | 1 + kernel/kexec.c | 10 +++++++++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 959fc3738d2..0ec3f620d71 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1895,6 +1895,13 @@ config KEXEC initially work for you. It may help to enable device hotplugging support. +config CRASH_SWRESET + bool "Perform a software reset at a panic (EXPERIMENTAL)" + depends on EXPERIMENTAL + depends on KEXEC + help + If no crash kernel has been loaded, perform a SW reset as plan B. + config ATAGS_PROC bool "Export atags in procfs" depends on KEXEC diff --git a/arch/arm/kernel/machine_kexec.c b/arch/arm/kernel/machine_kexec.c index ae1d73a15d3..2a9c3430115 100644 --- a/arch/arm/kernel/machine_kexec.c +++ b/arch/arm/kernel/machine_kexec.c @@ -123,3 +123,13 @@ void machine_kexec(struct kimage *image) flush_cache_all(); cpu_reset(reboot_code_buffer_phys); } + +void machine_crash_swreset(void) +{ + printk(KERN_INFO "Software reset on panic!\n"); + + flush_cache_all(); + outer_flush_all(); + outer_disable(); + arm_pm_restart(0, NULL); +} diff --git a/include/linux/kexec.h b/include/linux/kexec.h index 76b4db78ec1..f36d95a8223 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -110,6 +110,7 @@ struct kimage { /* kexec interface functions */ extern void machine_kexec(struct kimage *image); +extern void machine_crash_swreset(void); extern int machine_kexec_prepare(struct kimage *image); extern void machine_kexec_cleanup(struct kimage *image); extern asmlinkage long sys_kexec_load(unsigned long entry, diff --git a/kernel/kexec.c b/kernel/kexec.c index cfb421ddae2..1d6121de24f 100644 --- a/kernel/kexec.c +++ b/kernel/kexec.c @@ -1069,6 +1069,7 @@ asmlinkage long compat_sys_kexec_load(unsigned long entry, void crash_kexec(struct pt_regs *regs) { + struct pt_regs fixed_regs; /* Take the kexec_mutex here to prevent sys_kexec_load * running on one cpu from replacing the crash kernel * we are using after a panic on a different cpu. @@ -1079,7 +1080,6 @@ void crash_kexec(struct pt_regs *regs) */ if (mutex_trylock(&kexec_mutex)) { if (kexec_crash_image) { - struct pt_regs fixed_regs; kmsg_dump(KMSG_DUMP_KEXEC); @@ -1088,6 +1088,14 @@ void crash_kexec(struct pt_regs *regs) machine_crash_shutdown(&fixed_regs); machine_kexec(kexec_crash_image); } +#ifdef CONFIG_CRASH_SWRESET + else { + crash_setup_regs(&fixed_regs, regs); + crash_save_vmcoreinfo(); + machine_crash_shutdown(&fixed_regs); + machine_crash_swreset(); + } +#endif mutex_unlock(&kexec_mutex); } } -- cgit v1.2.3 From 19c7a8aee6c577a3188a685ae306fe906d6dc3c9 Mon Sep 17 00:00:00 2001 From: Shreshtha Kumar Sahu Date: Wed, 20 Jul 2011 20:58:00 +0530 Subject: u5500: leds: add ab5500v2 HVLED blink feature This patch adds support for AB5500 v2.0 HVLED hardware blink feature in existing driver. ST-Ericsson Linux next: ER 336280 ST-Ericsson FOSS-OUT ID: Trivial ST-Ericsson ID: ER 364964 Change-Id:I73e1caeac14774b7d3e03d1e7c5e4bd16fc7d06a Signed-off-by: Shreshtha Kumar Sahu Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/27547 Reviewed-by: Srinidhi KASAGAR Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32746 Reviewed-by: Naga RADHESH Y Tested-by: Naga RADHESH Y --- drivers/leds/leds-ab5500.c | 505 ++++++++++++++++++++++++++++++++++++++------ include/linux/leds-ab5500.h | 14 +- 2 files changed, 451 insertions(+), 68 deletions(-) diff --git a/drivers/leds/leds-ab5500.c b/drivers/leds/leds-ab5500.c index 94db3a6feea..294551b1962 100644 --- a/drivers/leds/leds-ab5500.c +++ b/drivers/leds/leds-ab5500.c @@ -1,13 +1,47 @@ /* + * leds-ab5500.c - driver for High Voltage (HV) LED in ST-Ericsson AB5500 chip + * * Copyright (C) 2011 ST-Ericsson SA. * * License Terms: GNU General Public License v2 * - * Driver for LED in ST-Ericsson AB5500 v1.0 Analog baseband Controller - * * Author: Shreshtha Kumar SAHU */ +/* + * Driver for HVLED in ST-Ericsson AB5500 analog baseband controller + * + * This chip can drive upto 3 leds, of upto 40mA of led sink current. + * These leds can be programmed to blink between two intensities with + * fading delay of half, one or two seconds. + * + * Leds can be controlled via sysfs entries in + * "/sys/class/leds/< red | green | blue >" + * + * For each led, + * + * Modes of operation: + * - manual: echo 0 > fade_auto (default, no auto blinking) + * - auto: echo 1 > fade_auto + * + * Soft scaling delay between two intensities: + * - 1/2 sec: echo 1 > fade_delay + * - 1 sec: echo 2 > fade_delay + * - 2 sec: echo 3 > fade_delay + * + * Possible sequence of operation: + * - continuous glow: set brightness (brt) + * - blink between LED_OFF and LED_FULL: + * set fade delay -> set fade auto + * - blink between previous two brightness (only for LED-1): + * set brt1 -> set brt2 -> set fade auto + * + * Delay can be set in any step, its affect will be seen on switching mode. + * + * Note: Blink/Fade feature is supported in AB5500 v2 onwards + * + */ + #include #include #include @@ -18,45 +52,67 @@ #include #include +#include + #define AB5500LED_NAME "ab5500-leds" +#define AB5500_LED_MAX 0x03 /* Register offsets */ #define AB5500_LED_REG_ENABLE 0x03 -#define AB5500_LED_FADE_CTL 0x0D +#define AB5500_LED_FADE_CTRL 0x0D -/* LED-0 */ +/* LED-0 Register Addr. Offsets */ #define AB5500_LED0_PWM_DUTY 0x01 #define AB5500_LED0_PWMFREQ 0x02 #define AB5500_LED0_SINKCTL 0x0A +#define AB5500_LED0_FADE_HI 0x11 +#define AB5500_LED0_FADE_LO 0x17 -/* LED-1 */ +/* LED-1 Register Addr. Offsets */ #define AB5500_LED1_PWM_DUTY 0x05 #define AB5500_LED1_PWMFREQ 0x06 #define AB5500_LED1_SINKCTL 0x0B +#define AB5500_LED1_FADE_HI 0x13 +#define AB5500_LED1_FADE_LO 0x19 -/* LED-2 */ +/* LED-2 Register Addr. Offsets */ #define AB5500_LED2_PWM_DUTY 0x08 #define AB5500_LED2_PWMFREQ 0x09 #define AB5500_LED2_SINKCTL 0x0C +#define AB5500_LED2_FADE_HI 0x15 +#define AB5500_LED2_FADE_LO 0x1B + +/* led-0/1/2 enable bit */ +#define AB5500_LED_ENABLE_MASK 0x04 -/* pwm duty cycle */ -#define AB5500_LED_PWMDUTY_OFF 0x0 -#define AB5500_LED_PWMDUTY_MAX 0x3FF -#define AB5500_LED_PWMDUTY_STEP (AB5500_LED_PWMDUTY_MAX/LED_FULL) +/* led intensity */ +#define AB5500_LED_INTENSITY_OFF 0x0 +#define AB5500_LED_INTENSITY_MAX 0x3FF +#define AB5500_LED_INTENSITY_STEP (AB5500_LED_INTENSITY_MAX/LED_FULL) /* pwm frequency */ -#define AB5500_LED_PWMFREQ_MAX 0x0F /* 373.39 @sysclk=26MHz */ +#define AB5500_LED_PWMFREQ_MAX 0x0F /* 373.39 @sysclk=26MHz */ #define AB5500_LED_PWMFREQ_SHIFT 4 /* LED sink current control */ -#define AB5500_LED_SINKCURR_MAX 0x0F /* 40mA */ +#define AB5500_LED_SINKCURR_MAX 0x0F /* 40mA MAX */ #define AB5500_LED_SINKCURR_SHIFT 4 +/* fade Control shift and masks */ +#define AB5500_FADE_DELAY_SHIFT 0x00 +#define AB5500_FADE_MODE_MASK 0x80 +#define AB5500_FADE_DELAY_MASK 0x03 +#define AB5500_FADE_START_MASK 0x04 +#define AB5500_FADE_ON_MASK 0x70 +#define AB5500_LED_FADE_ENABLE(ledid) (0x40 >> (ledid)) + struct ab5500_led { u8 id; u8 max_current; u16 brt_val; - enum ab5500_led_status status; + u16 fade_hi; + u16 fade_lo; + bool led_on; struct led_classdev led_cdev; struct work_struct led_work; }; @@ -66,26 +122,41 @@ struct ab5500_hvleds { struct device *dev; struct ab5500_hvleds_platform_data *pdata; struct ab5500_led leds[AB5500_HVLEDS_MAX]; + bool hw_fade; + bool fade_auto; + enum ab5500_fade_delay fade_delay; }; -static u8 ab5500_led_pwmduty_reg[] = { +static u8 ab5500_led_pwmduty_reg[AB5500_LED_MAX] = { AB5500_LED0_PWM_DUTY, AB5500_LED1_PWM_DUTY, AB5500_LED2_PWM_DUTY, }; -static u8 ab5500_led_pwmfreq_reg[] = { +static u8 ab5500_led_pwmfreq_reg[AB5500_LED_MAX] = { AB5500_LED0_PWMFREQ, AB5500_LED1_PWMFREQ, AB5500_LED2_PWMFREQ, }; -static u8 ab5500_led_sinkctl_reg[] = { +static u8 ab5500_led_sinkctl_reg[AB5500_LED_MAX] = { AB5500_LED0_SINKCTL, AB5500_LED1_SINKCTL, AB5500_LED2_SINKCTL }; +static u8 ab5500_led_fade_hi_reg[AB5500_LED_MAX] = { + AB5500_LED0_FADE_HI, + AB5500_LED1_FADE_HI, + AB5500_LED2_FADE_HI, +}; + +static u8 ab5500_led_fade_lo_reg[AB5500_LED_MAX] = { + AB5500_LED0_FADE_LO, + AB5500_LED1_FADE_LO, + AB5500_LED2_FADE_LO, +}; + #define to_led(_x) container_of(_x, struct ab5500_led, _x) static inline struct ab5500_hvleds *led_to_hvleds(struct ab5500_led *led) @@ -93,19 +164,74 @@ static inline struct ab5500_hvleds *led_to_hvleds(struct ab5500_led *led) return container_of(led, struct ab5500_hvleds, leds[led->id]); } +static int ab5500_led_enable(struct ab5500_hvleds *hvleds, + unsigned int led_id) +{ + int ret; + + ret = abx500_mask_and_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id], + AB5500_LED_ENABLE_MASK, + AB5500_LED_ENABLE_MASK); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmduty_reg[led_id], ret); + + return ret; + +} + +static int ab5500_led_start_manual(struct ab5500_hvleds *hvleds) +{ + int ret; + + mutex_lock(&hvleds->lock); + + ret = abx500_mask_and_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_FADE_CTRL, AB5500_FADE_START_MASK, + AB5500_FADE_START_MASK); + if (ret < 0) + dev_err(hvleds->dev, "update reg 0x%x failed - %d\n", + AB5500_LED_FADE_CTRL, ret); + + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_disable(struct ab5500_hvleds *hvleds, + unsigned int led_id) +{ + int ret; + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id] - 1, 0); + ret |= abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + ab5500_led_pwmduty_reg[led_id], 0); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + ab5500_led_pwmduty_reg[led_id], ret); + + return ret; +} + static int ab5500_led_pwmduty_write(struct ab5500_hvleds *hvleds, unsigned int led_id, u16 val) { int ret; - int val_lsb = val & 0xFF; - int val_msb = (val & 0x300) >> 8; + u8 val_lsb = val & 0xFF; + u8 val_msb = (val & 0x300) >> 8; mutex_lock(&hvleds->lock); dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val = %d\n" - "reg[%d] w val = %d\n", - ab5500_led_pwmduty_reg[led_id] - 1, val_lsb, - ab5500_led_pwmduty_reg[led_id], val_msb); + "reg[%d] w val = %d\n", + ab5500_led_pwmduty_reg[led_id] - 1, val_lsb, + ab5500_led_pwmduty_reg[led_id], val_msb); ret = abx500_set_register_interruptible( hvleds->dev, AB5500_BANK_LED, @@ -116,6 +242,7 @@ static int ab5500_led_pwmduty_write(struct ab5500_hvleds *hvleds, if (ret < 0) dev_err(hvleds->dev, "reg[%d] w failed: %d\n", ab5500_led_pwmduty_reg[led_id], ret); + mutex_unlock(&hvleds->lock); return ret; @@ -139,6 +266,7 @@ static int ab5500_led_pwmfreq_write(struct ab5500_hvleds *hvleds, if (ret < 0) dev_err(hvleds->dev, "reg[%d] w failed: %d\n", ab5500_led_pwmfreq_reg[led_id], ret); + mutex_unlock(&hvleds->lock); return ret; @@ -149,19 +277,58 @@ static int ab5500_led_sinkctl_write(struct ab5500_hvleds *hvleds, { int ret; - val = (val & 0x0F) << AB5500_LED_SINKCURR_SHIFT; + if (val > AB5500_LED_SINKCURR_MAX) + val = AB5500_LED_SINKCURR_MAX; - mutex_lock(&hvleds->lock); + val = (val << AB5500_LED_SINKCURR_SHIFT); dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val=%d\n", ab5500_led_sinkctl_reg[led_id], val); + mutex_lock(&hvleds->lock); + ret = abx500_set_register_interruptible( hvleds->dev, AB5500_BANK_LED, ab5500_led_sinkctl_reg[led_id], val); if (ret < 0) dev_err(hvleds->dev, "reg[%d] w failed: %d\n", ab5500_led_sinkctl_reg[led_id], ret); + + mutex_unlock(&hvleds->lock); + + return ret; +} + +static int ab5500_led_fade_write(struct ab5500_hvleds *hvleds, + unsigned int led_id, bool on, u16 val) +{ + int ret; + int val_lsb = val & 0xFF; + int val_msb = (val & 0x300) >> 8; + u8 *fade_reg; + + if (on) + fade_reg = ab5500_led_fade_hi_reg; + else + fade_reg = ab5500_led_fade_lo_reg; + + dev_dbg(hvleds->dev, "ab5500-leds: reg[%d] w val = %d\n" + "reg[%d] w val = %d\n", + fade_reg[led_id] - 1, val_lsb, + fade_reg[led_id], val_msb); + + mutex_lock(&hvleds->lock); + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + fade_reg[led_id] - 1, val_lsb); + ret |= abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + fade_reg[led_id], val_msb); + if (ret < 0) + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + fade_reg[led_id], ret); + mutex_unlock(&hvleds->lock); return ret; @@ -174,6 +341,7 @@ static int ab5500_led_sinkctl_read(struct ab5500_hvleds *hvleds, u8 val; mutex_lock(&hvleds->lock); + ret = abx500_get_register_interruptible( hvleds->dev, AB5500_BANK_LED, ab5500_led_sinkctl_reg[led_id], &val); @@ -183,7 +351,9 @@ static int ab5500_led_sinkctl_read(struct ab5500_hvleds *hvleds, mutex_unlock(&hvleds->lock); return ret; } + val = (val & 0xF0) >> AB5500_LED_SINKCURR_SHIFT; + mutex_unlock(&hvleds->lock); return val; @@ -196,7 +366,8 @@ static void ab5500_led_brightness_set(struct led_classdev *led_cdev, /* adjust LED_FULL to 10bit range */ brt_val &= LED_FULL; - led->brt_val = brt_val * AB5500_LED_PWMDUTY_STEP; + led->brt_val = brt_val * AB5500_LED_INTENSITY_STEP; + schedule_work(&led->led_work); } @@ -205,8 +376,13 @@ static void ab5500_led_work(struct work_struct *led_work) struct ab5500_led *led = to_led(led_work); struct ab5500_hvleds *hvleds = led_to_hvleds(led); - if (led->status == AB5500_LED_ON) + if (led->led_on == true) { ab5500_led_pwmduty_write(hvleds, led->id, led->brt_val); + if (hvleds->hw_fade && led->brt_val) { + ab5500_led_enable(hvleds, led->id); + ab5500_led_start_manual(hvleds); + } + } } static ssize_t ab5500_led_show_current(struct device *dev, @@ -248,9 +424,122 @@ static ssize_t ab5500_led_store_current(struct device *dev, return len; } +static ssize_t ab5500_led_store_fade_auto(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int ret; + u8 fade_ctrl = 0; + unsigned long fade_auto; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + if (strict_strtoul(buf, 0, &fade_auto)) + return -EINVAL; + + if (fade_auto > 1) { + dev_err(hvleds->dev, "invalid mode\n"); + return -EINVAL; + } + + mutex_lock(&hvleds->lock); + + ret = abx500_get_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_FADE_CTRL, &fade_ctrl); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + AB5500_LED_FADE_CTRL, ret); + goto unlock_and_return; + } + + /* manual mode */ + if (fade_auto == false) { + fade_ctrl &= ~(AB5500_LED_FADE_ENABLE(led->id)); + if (!(fade_ctrl & AB5500_FADE_ON_MASK)) + fade_ctrl = 0; + + ret = ab5500_led_disable(hvleds, led->id); + if (ret < 0) + goto unlock_and_return; + } else { + /* set led auto enable bit */ + fade_ctrl |= AB5500_FADE_MODE_MASK; + fade_ctrl |= AB5500_LED_FADE_ENABLE(led->id); + + /* set fade delay */ + fade_ctrl &= ~AB5500_FADE_DELAY_MASK; + fade_ctrl |= hvleds->fade_delay << AB5500_FADE_DELAY_SHIFT; + + /* set fade start manual */ + fade_ctrl |= AB5500_FADE_START_MASK; + + /* enble corresponding led */ + ret = ab5500_led_enable(hvleds, led->id); + if (ret < 0) + goto unlock_and_return; + + } + + ret = abx500_set_register_interruptible( + hvleds->dev, AB5500_BANK_LED, + AB5500_LED_FADE_CTRL, fade_ctrl); + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + AB5500_LED_FADE_CTRL, ret); + goto unlock_and_return; + } + + hvleds->fade_auto = fade_auto; + + ret = len; + +unlock_and_return: + mutex_unlock(&hvleds->lock); + + return ret; +} + +static ssize_t ab5500_led_show_fade_auto(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + return sprintf(buf, "%d\n", hvleds->fade_auto); +} + +static ssize_t ab5500_led_store_fade_delay(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + unsigned long fade_delay; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ab5500_led *led = to_led(led_cdev); + struct ab5500_hvleds *hvleds = led_to_hvleds(led); + + if (strict_strtoul(buf, 0, &fade_delay)) + return -EINVAL; + + if (fade_delay > AB5500_FADE_DELAY_TWOSEC) { + dev_err(hvleds->dev, "invalid mode\n"); + return -EINVAL; + } + + hvleds->fade_delay = fade_delay; + + return len; +} + /* led class device attributes */ static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, ab5500_led_show_current, ab5500_led_store_current); +static DEVICE_ATTR(fade_auto, S_IRUGO | S_IWUGO, + ab5500_led_show_fade_auto, ab5500_led_store_fade_auto); +static DEVICE_ATTR(fade_delay, S_IRUGO | S_IWUGO, + NULL, ab5500_led_store_fade_delay); static int ab5500_led_init_registers(struct ab5500_hvleds *hvleds) { @@ -258,30 +547,52 @@ static int ab5500_led_init_registers(struct ab5500_hvleds *hvleds) unsigned int led_id; /* fade - manual : dur mid : pwm duty mid */ - ret = abx500_set_register_interruptible( + if (!hvleds->hw_fade) { + ret = abx500_set_register_interruptible( hvleds->dev, AB5500_BANK_LED, AB5500_LED_REG_ENABLE, true); - if (ret < 0) { - dev_err(hvleds->dev, "reg[%d] w failed: %d\n", - AB5500_LED_REG_ENABLE, ret); - return ret; + if (ret < 0) { + dev_err(hvleds->dev, "reg[%d] w failed: %d\n", + AB5500_LED_REG_ENABLE, ret); + return ret; + } } for (led_id = 0; led_id < AB5500_HVLEDS_MAX; led_id++) { - /* Set pwm freq. and sink current to mid values */ - ret = ab5500_led_pwmfreq_write( - hvleds, led_id, AB5500_LED_PWMFREQ_MAX); - if (ret < 0) - return ret; + if (hvleds->leds[led_id].led_on == false) + continue; ret = ab5500_led_sinkctl_write( - hvleds, led_id, AB5500_LED_SINKCURR_MAX); + hvleds, led_id, + hvleds->leds[led_id].max_current); if (ret < 0) return ret; + if (hvleds->hw_fade) { + ret = ab5500_led_pwmfreq_write( + hvleds, led_id, + AB5500_LED_PWMFREQ_MAX / 2); + if (ret < 0) + return ret; + + /* fade high intensity */ + ret = ab5500_led_fade_write( + hvleds, led_id, true, + hvleds->leds[led_id].fade_hi); + if (ret < 0) + return ret; + + /* fade low intensity */ + ret = ab5500_led_fade_write( + hvleds, led_id, false, + hvleds->leds[led_id].fade_lo); + if (ret < 0) + return ret; + } + /* init led off */ - ret = ab5500_led_pwmduty_write( - hvleds, led_id, AB5500_LED_PWMDUTY_OFF); + ret |= ab5500_led_pwmduty_write( + hvleds, led_id, AB5500_LED_INTENSITY_OFF); if (ret < 0) return ret; } @@ -294,12 +605,18 @@ static int ab5500_led_register_leds(struct device *dev, struct ab5500_hvleds *hvleds) { int i_led; - int err; + int ret = 0; struct ab5500_led_conf *pled; struct ab5500_led *led; hvleds->dev = dev; hvleds->pdata = pdata; + + if (abx500_get_chip_id(dev) == AB5500_2_0) + hvleds->hw_fade = true; + else + hvleds->hw_fade = false; + for (i_led = 0; i_led < AB5500_HVLEDS_MAX; i_led++) { pled = &pdata->leds[i_led]; led = &hvleds->leds[i_led]; @@ -308,84 +625,137 @@ static int ab5500_led_register_leds(struct device *dev, led->id = pled->led_id; led->max_current = pled->max_current; - led->status = pled->status; + led->led_on = pled->led_on; led->led_cdev.name = pled->name; led->led_cdev.brightness_set = ab5500_led_brightness_set; - err = led_classdev_register(dev, &led->led_cdev); - if (err < 0) { - dev_err(dev, "Register led class failed: %d\n", err); + /* Provide interface only for enabled LEDs */ + if (led->led_on == false) + continue; + + if (hvleds->hw_fade) { + led->fade_hi = (pled->fade_hi & LED_FULL); + led->fade_hi *= AB5500_LED_INTENSITY_STEP; + led->fade_lo = (pled->fade_lo & LED_FULL); + led->fade_lo *= AB5500_LED_INTENSITY_STEP; + } + + ret = led_classdev_register(dev, &led->led_cdev); + if (ret < 0) { + dev_err(dev, "Register led class failed: %d\n", ret); goto bailout1; } - err = device_create_file(led->led_cdev.dev, + ret = device_create_file(led->led_cdev.dev, &dev_attr_led_current); - if (err < 0) { - dev_err(dev, "sysfs device creation failed: %d\n", err); + if (ret < 0) { + dev_err(dev, "sysfs device creation failed: %d\n", ret); goto bailout2; } + + if (hvleds->hw_fade) { + ret = device_create_file(led->led_cdev.dev, + &dev_attr_fade_auto); + if (ret < 0) { + dev_err(dev, "sysfs device " + "creation failed: %d\n", ret); + goto bailout3; + } + + ret = device_create_file(led->led_cdev.dev, + &dev_attr_fade_delay); + if (ret < 0) { + dev_err(dev, "sysfs device " + "creation failed: %d\n", ret); + goto bailout4; + } + } } - return err; + return ret; for (; i_led >= 0; i_led--) { - device_remove_file(led->led_cdev.dev, &dev_attr_led_current); + if (hvleds->leds[i_led].led_on == false) + continue; + + if (hvleds->hw_fade) { + device_remove_file(hvleds->leds[i_led].led_cdev.dev, + &dev_attr_fade_delay); +bailout4: + device_remove_file(hvleds->leds[i_led].led_cdev.dev, + &dev_attr_fade_auto); + } +bailout3: + device_remove_file(hvleds->leds[i_led].led_cdev.dev, + &dev_attr_led_current); bailout2: led_classdev_unregister(&hvleds->leds[i_led].led_cdev); bailout1: cancel_work_sync(&hvleds->leds[i_led].led_work); } - return err; + return ret; } static int __devinit ab5500_hvleds_probe(struct platform_device *pdev) { struct ab5500_hvleds_platform_data *pdata = pdev->dev.platform_data; struct ab5500_hvleds *hvleds = NULL; - int err = 0, i; + int ret = 0, i; if (pdata == NULL) { dev_err(&pdev->dev, "platform data required\n"); - err = -ENODEV; + ret = -ENODEV; goto err_out; } hvleds = kzalloc(sizeof(struct ab5500_hvleds), GFP_KERNEL); if (hvleds == NULL) { - err = -ENOMEM; + ret = -ENOMEM; goto err_out; } mutex_init(&hvleds->lock); /* init leds data and register led_classdev */ - err = ab5500_led_register_leds(&pdev->dev, pdata, hvleds); - if (err < 0) { - dev_err(&pdev->dev, "leds registeration failed\n"); + ret = ab5500_led_register_leds(&pdev->dev, pdata, hvleds); + if (ret < 0) { + dev_err(&pdev->dev, "leds registration failed\n"); goto err_out; } /* init device registers and set initial led current */ - err = ab5500_led_init_registers(hvleds); - if (err < 0) { - dev_err(&pdev->dev, "reg init failed: %d\n", err); + ret = ab5500_led_init_registers(hvleds); + if (ret < 0) { + dev_err(&pdev->dev, "reg init failed: %d\n", ret); goto err_reg_init; } - dev_info(&pdev->dev, "enabled\n"); + if (hvleds->hw_fade) + dev_info(&pdev->dev, "v2 enabled\n"); + else + dev_info(&pdev->dev, "v1 enabled\n"); - return err; + return ret; err_reg_init: for (i = 0; i < AB5500_HVLEDS_MAX; i++) { struct ab5500_led *led = &hvleds->leds[i]; - led_classdev_unregister(&led->led_cdev); + if (led->led_on == false) + continue; + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); + if (hvleds->hw_fade) { + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_auto); + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_delay); + } + led_classdev_unregister(&led->led_cdev); cancel_work_sync(&led->led_work); } err_out: kfree(hvleds); - return err; + return ret; } static int __devexit ab5500_hvleds_remove(struct platform_device *pdev) @@ -396,8 +766,17 @@ static int __devexit ab5500_hvleds_remove(struct platform_device *pdev) for (i = 0; i < AB5500_HVLEDS_MAX; i++) { struct ab5500_led *led = &hvleds->leds[i]; - led_classdev_unregister(&led->led_cdev); + if (led->led_on == false) + continue; + device_remove_file(led->led_cdev.dev, &dev_attr_led_current); + if (hvleds->hw_fade) { + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_auto); + device_remove_file(led->led_cdev.dev, + &dev_attr_fade_delay); + } + led_classdev_unregister(&led->led_cdev); cancel_work_sync(&led->led_work); } kfree(hvleds); diff --git a/include/linux/leds-ab5500.h b/include/linux/leds-ab5500.h index 2db6ffd188e..9ba9ac61d90 100644 --- a/include/linux/leds-ab5500.h +++ b/include/linux/leds-ab5500.h @@ -13,19 +13,23 @@ #define AB5500_HVLED2 2 #define AB5500_HVLEDS_MAX 3 -enum ab5500_led_status { - AB5500_LED_OFF = 0x00, - AB5500_LED_ON, +enum ab5500_fade_delay { + AB5500_FADE_DELAY_BYPASS = 0, + AB5500_FADE_DELAY_HALFSEC, + AB5500_FADE_DELAY_ONESEC, + AB5500_FADE_DELAY_TWOSEC }; struct ab5500_led_conf { char *name; u8 led_id; - enum ab5500_led_status status; u8 max_current; + u8 fade_hi; + u8 fade_lo; + bool led_on; }; struct ab5500_hvleds_platform_data { - bool hw_blink; + bool hw_fade; struct ab5500_led_conf leds[AB5500_HVLEDS_MAX]; }; -- cgit v1.2.3 From c03cd0ec0d3ff6ab91caa2811580d0934aac00ec Mon Sep 17 00:00:00 2001 From: Pierre Peiffer Date: Wed, 28 Sep 2011 10:35:21 +0200 Subject: U8500: enable again the STM trace driver This driver has been disabled because of some updates required on user side. Now that the user lib are fixed, enable it again. ST-Ericsson ID: 363600 ST-Ericsson FOSS-OUT ID: Trivial ST-Ericsson Linux next: - Signed-off-by: Pierre Peiffer Depends-On: I38a2d1ffff4def425d50564de689fb949fe5d6e7, Ib303ad17367e11921471b4124563239d6c26223a, Iad3a2e06c252e4c9dcb323ae3d6a5d51bac579ef Change-Id: I458d4565a5d1dedf84fbbc0c614daf80a9810693 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32438 Reviewed-by: Pierre PEIFFER Tested-by: Pierre PEIFFER --- arch/arm/configs/u8500_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 85dfede4aa9..9984d371f48 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -111,6 +111,8 @@ CONFIG_MISC_DEVICES=y CONFIG_AB8500_PWM=y CONFIG_SENSORS_BH1780=y CONFIG_STE_TRACE_MODEM=y +CONFIG_U8500_SIM_DETECT=y +CONFIG_STM_TRACE=y CONFIG_DISPDEV=y CONFIG_U8500_SIM_DETECT=y # CONFIG_STE_AUDIO_IO_DEV is not set -- cgit v1.2.3 From d8c256434bea52ccd4027052cf8f5f32918c2adb Mon Sep 17 00:00:00 2001 From: Per Fransson Date: Thu, 29 Sep 2011 10:46:44 +0200 Subject: defconfig: enable SW reset feature When there is no crash kernel image loaded, perform a SW reset at a panic. ST-Ericsson ID: 340331 Change-Id: I61e0026c248f4a4f1078a988b0dcd395140e5510 Signed-off-by: Per Fransson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32679 Reviewed-by: Srinidhi KASAGAR --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 9984d371f48..484ec1acb9e 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -43,6 +43,7 @@ CONFIG_AEABI=y CONFIG_HIGHMEM=y CONFIG_CMDLINE="root=/dev/ram0 init=init rw console=ttyAMA2,115200n8 mem=256M initrd=0x800000,72M" CONFIG_KEXEC=y +CONFIG_CRASH_SWRESET=y CONFIG_CRASH_DUMP=y CONFIG_CPU_FREQ=y CONFIG_CPU_FREQ_STAT_DETAILS=y -- cgit v1.2.3 From e4c7200e3f063ea4e79cba8a2a2b1a4483274ddf Mon Sep 17 00:00:00 2001 From: Shyam Krishnan M Date: Mon, 3 Oct 2011 13:27:14 +0530 Subject: defconfig: enable cg2900 This patch sets configuration of cg2900. ST-Ericsson Linux next: NA ST-Ericsson ID: 335714 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ie114065582f53b3af429660768d17456edfa70d6 Signed-off-by: Shyam Krishnan M Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32800 Reviewed-by: Srinidhi KASAGAR --- arch/arm/configs/u8500_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 484ec1acb9e..8f3de06bd29 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -188,6 +188,8 @@ CONFIG_VIDEO_DEV=y # CONFIG_VIDEO_CAPTURE_DRIVERS is not set CONFIG_RADIO_CG2900=y CONFIG_DRM=y +CONFIG_SND_SOC_UX500_CG29XX=y +CONFIG_SND_SOC_CG29XX=y CONFIG_GPU_MALI=y CONFIG_FB=y CONFIG_FB_MCDE=y -- cgit v1.2.3 From 0c1ad2dd2384a777e33c0bde7fd9979553aec932 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Fri, 30 Sep 2011 12:59:59 +0530 Subject: power: ab5500-bm: handle divide by zero warning samples obtained from the fuel gauge is used to calculate the average current. Calculation of average current include division by samples, which ends up with division by zero in case samples is zero. Hence if samples is zero, average current is not calaulated and previous value is used. ST-Ericsson Linux next: NA ST-Ericsson ID: 363829 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I04dd205a7af07cfc7d2ff369fd29cdd1363f6208 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32674 Reviewed-by: Rabin VINCENT --- drivers/power/ab5500_fg.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/power/ab5500_fg.c b/drivers/power/ab5500_fg.c index 0975c7417a4..62710680d1e 100644 --- a/drivers/power/ab5500_fg.c +++ b/drivers/power/ab5500_fg.c @@ -533,8 +533,12 @@ static void ab5500_fg_acc_cur_work(struct work_struct *work) di->fg_samples = (cnt_low | (cnt_high << 8)); val = (low | (med << 8) | (high << 16)); - di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10)/10000; - di->avg_curr = (val * FG_LSB_IN_MA) / (di->fg_samples * 1000); + if (di->fg_samples) { + di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10)/10000; + di->avg_curr = (val * FG_LSB_IN_MA) / (di->fg_samples * 1000); + } else + dev_err(di->dev, + "samples is zero, using previous calculated average current\n"); di->flags.conv_done = true; mutex_unlock(&di->cc_lock); -- cgit v1.2.3 From bad0572ea0e66c3039bd538bd85f0f5140420055 Mon Sep 17 00:00:00 2001 From: Bartosz Markowski Date: Tue, 4 Oct 2011 14:34:17 +0200 Subject: u8500: Enable IP multicast Enables CONFIG_IP_MULTICAST in u8500_defconfig ST-Ericsson Linux next: Not tested ST-Ericsson ID: 355020, 353690 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Id652d38efceef6585fbb2505750edbacbc9e82a9 Signed-off-by: Bartosz Markowski Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32958 Reviewed-by: Srinidhi KASAGAR --- arch/arm/configs/u8500_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 8f3de06bd29..2dee688e902 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -64,6 +64,8 @@ CONFIG_PACKET=y CONFIG_UNIX=y CONFIG_NET_KEY=y CONFIG_INET=y +CONFIG_IP_MULTICAST=y +# CONFIG_IP_MROUTE is not set CONFIG_IP_ADVANCED_ROUTER=y CONFIG_IP_MULTIPLE_TABLES=y CONFIG_IP_PNP=y -- cgit v1.2.3 From de998a7b1f60746a338b89bb81fc386b18765210 Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Tue, 4 Oct 2011 11:18:22 +0200 Subject: misc: audio_io: Remove obsolete and unused driver audio_io is superseded by alsa. ST-Ericsson Linux next: - ST-Ericsson ID: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I4641468e1d67e8596b1fcd9af69b0b81b686a0a0 Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32904 Reviewed-by: Ola LILJA2 --- arch/arm/configs/u8500_defconfig | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 2dee688e902..63bdb56275a 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -117,8 +117,6 @@ CONFIG_STE_TRACE_MODEM=y CONFIG_U8500_SIM_DETECT=y CONFIG_STM_TRACE=y CONFIG_DISPDEV=y -CONFIG_U8500_SIM_DETECT=y -# CONFIG_STE_AUDIO_IO_DEV is not set CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y CONFIG_MD=y -- cgit v1.2.3 From 4c7cafa745afbe4da8c1a90769e0b7fb0e28aef5 Mon Sep 17 00:00:00 2001 From: Mark Godfrey Date: Tue, 4 Oct 2011 09:58:45 +0100 Subject: rtc: ab8500: Add calibration attribute to AB8500 RealTimeClock The rtc_calibration attribute allows user-space to get and set the AB8500's RtcCalibration register. The AB8500 will then use the value in this register to compensate for RTC drift every 60 seconds. ST Ericsson ID: 362204 Change-Id: I32a4bda5d6cc0812dcb53d721088e8b844c25b46 Signed-off-by: Mark Godfrey Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32922 Reviewed-by: Linus WALLEIJ --- .../sysfs-class-rtc-rtc0-device-rtc_calibration | 13 +++ drivers/rtc/rtc-ab8500.c | 109 +++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-rtc-rtc0-device-rtc_calibration diff --git a/Documentation/ABI/testing/sysfs-class-rtc-rtc0-device-rtc_calibration b/Documentation/ABI/testing/sysfs-class-rtc-rtc0-device-rtc_calibration new file mode 100644 index 00000000000..ae217d4a3a6 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-rtc-rtc0-device-rtc_calibration @@ -0,0 +1,13 @@ +What: Attribute for calibrating ST-Ericsson AB8500 Real Time Clock +Date: Oct 2011 +KernelVersion: 3.0 +Contact: Mark Godfrey +Description: The rtc_calibration attribute allows the userspace to + calibrate the AB8500.s 32KHz Real Time Clock. + Every 60 seconds the AB8500 will correct the RTC's value + by adding to it the value of this attribute. + The range of the attribute is -127 to +127 in units of + 30.5 micro-seconds (half-parts-per-million of the 32KHz clock) +Users: The /vendor/st-ericsson/base_utilities/core/rtc_calibration + daemon uses this interface. + diff --git a/drivers/rtc/rtc-ab8500.c b/drivers/rtc/rtc-ab8500.c index 7323a5b5850..3160d9b5613 100644 --- a/drivers/rtc/rtc-ab8500.c +++ b/drivers/rtc/rtc-ab8500.c @@ -269,6 +269,106 @@ static int ab8500_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) return ab8500_rtc_irq_enable(dev, alarm->enabled); } + +static int ab8500_rtc_set_calibration(struct device *dev, int calibration) +{ + int retval; + u8 rtccal = 0; + + /* + * Check that the calibration value (which is in units of 0.5 parts-per-million) + * is in the AB8500's range for RtcCalibration register. + */ + if ((calibration < -127) || (calibration > 127)) { + dev_err(dev, "RtcCalibration value outside permitted range\n"); + return -EINVAL; + } + + /* + * The AB8500 uses sign (in bit7) and magnitude (in bits0-7) + * so need to convert to this sort of representation before writing + * into RtcCalibration register... + */ + if (calibration >= 0) + rtccal = 0x7F & calibration; + else + rtccal = ~(calibration -1) | 0x80; + + retval = abx500_set_register_interruptible(dev, AB8500_RTC, + AB8500_RTC_CALIB_REG, rtccal); + + return retval; +} + +static int ab8500_rtc_get_calibration(struct device *dev, int *calibration) +{ + int retval; + u8 rtccal = 0; + + retval = abx500_get_register_interruptible(dev, AB8500_RTC, + AB8500_RTC_CALIB_REG, &rtccal); + if (retval >= 0) { + /* + * The AB8500 uses sign (in bit7) and magnitude (in bits0-7) + * so need to convert value from RtcCalibration register into + * a two's complement signed value... + */ + if (rtccal & 0x80) + *calibration = 0 - (rtccal & 0x7F); + else + *calibration = 0x7F & rtccal; + } + + return retval; +} + +static ssize_t ab8500_sysfs_store_rtc_calibration(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int retval; + int calibration = 0; + + if (sscanf(buf, " %i ", &calibration) != 1) { + dev_err(dev, "Failed to store RTC calibration attribute\n"); + return -EINVAL; + } + + retval = ab8500_rtc_set_calibration(dev, calibration); + + return retval ? retval : count; +} + +static ssize_t ab8500_sysfs_show_rtc_calibration(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int retval = 0; + int calibration = 0; + + retval = ab8500_rtc_get_calibration(dev, &calibration); + if (retval < 0) { + dev_err(dev, "Failed to read RTC calibration attribute\n"); + sprintf(buf, "0\n"); + return retval; + } + + return sprintf(buf, "%d\n", calibration); +} + +static DEVICE_ATTR(rtc_calibration, S_IRUGO | S_IWUSR, + ab8500_sysfs_show_rtc_calibration, + ab8500_sysfs_store_rtc_calibration); + +static int ab8500_sysfs_rtc_register(struct device *dev) +{ + return device_create_file(dev, &dev_attr_rtc_calibration); +} + +static void ab8500_sysfs_rtc_unregister(struct device *dev) +{ + device_remove_file(dev, &dev_attr_rtc_calibration); +} + static irqreturn_t rtc_alarm_handler(int irq, void *data) { struct rtc_device *rtc = data; @@ -338,6 +438,13 @@ static int __devinit ab8500_rtc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, rtc); + + err = ab8500_sysfs_rtc_register(&pdev->dev); + if (err) { + dev_err(&pdev->dev, "sysfs RTC failed to register\n"); + return err; + } + return 0; } @@ -346,6 +453,8 @@ static int __devexit ab8500_rtc_remove(struct platform_device *pdev) struct rtc_device *rtc = platform_get_drvdata(pdev); int irq = platform_get_irq_byname(pdev, "ALARM"); + ab8500_sysfs_rtc_unregister(&pdev->dev); + free_irq(irq, rtc); rtc_device_unregister(rtc); platform_set_drvdata(pdev, NULL); -- cgit v1.2.3 From a458e3789f1acb700fee1e0dbd20a65b907f8003 Mon Sep 17 00:00:00 2001 From: Kalle Komierowski Date: Tue, 4 Oct 2011 13:51:16 +0200 Subject: arm: mach-ux500: Lower the upper battery temperature limits This is to make it easier to test the temperature limits during battery charging. Customers will change the values to their own preferences. ST-Ericsson ID: 341012 ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I4b6078a82816876d49eccc8c892e7ea2487939a1 Signed-off-by: Kalle Komierowski Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32955 Reviewed-by: Jonas ABERG --- arch/arm/mach-ux500/board-mop500-bm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/arm/mach-ux500/board-mop500-bm.c b/arch/arm/mach-ux500/board-mop500-bm.c index 78f20626cab..91fb887d4f5 100644 --- a/arch/arm/mach-ux500/board-mop500-bm.c +++ b/arch/arm/mach-ux500/board-mop500-bm.c @@ -391,8 +391,8 @@ static const struct ab8500_bm_charger_parameters chg = { struct ab8500_bm_data ab8500_bm_data = { .temp_under = 3, .temp_low = 8, - .temp_high = 55, - .temp_over = 60, + .temp_high = 43, + .temp_over = 48, .main_safety_tmr_h = 4, .temp_interval_chg = 20, .temp_interval_nochg = 120, -- cgit v1.2.3 From b7a391f88c46f07aad1375d3b39f29ac3316b1c3 Mon Sep 17 00:00:00 2001 From: Chris Kimber Date: Wed, 28 Sep 2011 13:27:30 +0100 Subject: hwmon: dbx500: Stop db max alarms from shutting down the platform Setting the critical alarm causes the PRCMU to shutdown the platform, the _max alarm is used by the platform thermal service to try and take actions to prevent the critical shutdown occuring. ST-Ericsson ID: 364370 ST-Ericsson Linux next: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I04671800872aba8318f461fb5a710a72aea4280a Signed-off-by: Chris Kimber Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32400 Reviewed-by: Duncan PATERSON Reviewed-by: Andrew LYNN --- drivers/hwmon/dbx500.c | 63 +------------------------------------------------- 1 file changed, 1 insertion(+), 62 deletions(-) diff --git a/drivers/hwmon/dbx500.c b/drivers/hwmon/dbx500.c index cd44e78030c..be8ba79c9a8 100644 --- a/drivers/hwmon/dbx500.c +++ b/drivers/hwmon/dbx500.c @@ -26,13 +26,6 @@ #include #include -/* - * If DBX500 warm interrupt is set, user space will be notified. - * If user space doesn't shut down the platform within this time - * frame, this driver will. Time unit is ms. - */ -#define DEFAULT_POWER_OFF_DELAY 10000 - /* * Default measure period to 0xFF x cycle32k */ @@ -50,51 +43,9 @@ struct dbx500_temp { unsigned char min_alarm[NUM_SENSORS]; unsigned char max_alarm[NUM_SENSORS]; unsigned short measure_time; - struct delayed_work power_off_work; struct mutex lock; - /* Delay (ms) before power off */ - unsigned long power_off_delay; }; -static void thermal_power_off(struct work_struct *work) -{ - struct dbx500_temp *data = container_of(work, struct dbx500_temp, - power_off_work.work); - - dev_warn(&data->pdev->dev, "Power off due to DBX500 thermal warning\n"); - pm_power_off(); -} - -static ssize_t set_temp_power_off_delay(struct device *dev, - struct device_attribute *devattr, - const char *buf, size_t count) -{ - int res; - unsigned long delay_in_s; - struct dbx500_temp *data = dev_get_drvdata(dev); - - res = strict_strtoul(buf, 10, &delay_in_s); - if (res < 0) { - dev_warn(&data->pdev->dev, "Set power_off_delay wrong\n"); - return res; - } - - mutex_lock(&data->lock); - data->power_off_delay = delay_in_s * 1000; - mutex_unlock(&data->lock); - - return count; -} - -static ssize_t show_temp_power_off_delay(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - struct dbx500_temp *data = dev_get_drvdata(dev); - /* return time in s, not ms */ - return sprintf(buf, "%lu\n", (data->power_off_delay) / 1000); -} - /* HWMON sysfs interface */ static ssize_t show_name(struct device *dev, struct device_attribute *devattr, char *buf) @@ -264,11 +215,6 @@ static ssize_t show_max_alarm(struct device *dev, return sprintf(buf, "%d\n", data->max_alarm[attr->index - 1]); } -/*These node are not included in the kernel hwmon sysfs interface */ -static SENSOR_DEVICE_ATTR(temp_power_off_delay, S_IRUGO | S_IWUSR, - show_temp_power_off_delay, - set_temp_power_off_delay, 0); - /* Chip name, required by hwmon*/ static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); static SENSOR_DEVICE_ATTR(temp1_start, S_IWUSR, NULL, start_temp, 1); @@ -282,7 +228,6 @@ static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); static struct attribute *dbx500_temp_attributes[] = { - &sensor_dev_attr_temp_power_off_delay.dev_attr.attr, &sensor_dev_attr_name.dev_attr.attr, &sensor_dev_attr_temp1_start.dev_attr.attr, &sensor_dev_attr_temp1_stop.dev_attr.attr, @@ -315,7 +260,6 @@ static irqreturn_t prcmu_hotmon_low_irq_handler(int irq, void *irq_data) static irqreturn_t prcmu_hotmon_high_irq_handler(int irq, void *irq_data) { - unsigned long delay_in_jiffies; struct platform_device *pdev = irq_data; struct dbx500_temp *data = platform_get_drvdata(pdev); @@ -325,10 +269,7 @@ static irqreturn_t prcmu_hotmon_high_irq_handler(int irq, void *irq_data) hwmon_notify(data->max_alarm[0], NULL); sysfs_notify(&pdev->dev.kobj, NULL, "temp1_max_alarm"); - dev_dbg(&pdev->dev, "DBX500 thermal warning, power off in %lu s\n", - (data->power_off_delay) / 1000); - delay_in_jiffies = msecs_to_jiffies(data->power_off_delay); - schedule_delayed_work(&data->power_off_work, delay_in_jiffies); + return IRQ_HANDLED; } @@ -394,10 +335,8 @@ static int __devinit dbx500_temp_probe(struct platform_device *pdev) } mutex_init(&data->lock); - INIT_DELAYED_WORK(&data->power_off_work, thermal_power_off); data->pdev = pdev; - data->power_off_delay = DEFAULT_POWER_OFF_DELAY; data->measure_time = DEFAULT_MEASURE_TIME; platform_set_drvdata(pdev, data); -- cgit v1.2.3 From 4f76d7cac051c5cf03b319070c5df70d3cb7e105 Mon Sep 17 00:00:00 2001 From: Yann Gautier Date: Thu, 6 Oct 2011 10:49:02 +0200 Subject: arm:config: Update u9500_defconfig with 8500 updates Here is the list of changes: * misc: audio_io: Remove obsolete and unused driver * u8500: Enable IP multicast * defconfig: enable SW reset feature * U8500: enable again the STM trace driver * ARM: u8500: configs: Enable regulator suspend force * u8500_defconfig: Enable ip rule support (339665) * ARM: config: u8500: Enable DB8500 regulators * ARM: ux500: Use u8500_wdt * u8500: watchdog: Reset to mainline status * cpuidle: only go to ApIdle * drivers:cpuidle U8500 support * ARM: ux500: suspend_dbg: Add debug information * ARM: ux500: suspend: Add wake on uart * ARM: ux500: Add suspend support * ARM: u8500: config: Enable cpufreq * ARM: ux500: pm: Added context and pm * ARM: ux500: Add prcmu-debug * ARM: ux500: Add prcmu qos power * ARM: ux500: Replace mach prcmu driver with mainlined version * ARM: ux500: Remove powersave * arm: u8500: Disable HDMI fb auto creation (364125) * ARM: u8500: Not every u8500 has a TPS6105 ST-Ericsson Linux next: - ST-Ericsson ID: 339665, 364125, 363600, 340331, 355020, 353690 ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ic6a568411a70ed40e7ca3a14a4dfef0e17fc8543 Original Change-Ids: I4641468e1d67e8596b1fcd9af69b0b81b686a0a0 Id652d38efceef6585fbb2505750edbacbc9e82a9 I61e0026c248f4a4f1078a988b0dcd395140e5510 I458d4565a5d1dedf84fbbc0c614daf80a9810693 I26ba2f655d4fb65708913cdeda632e2ca35fea0a I3a570cdf0dbdbea2b2279428fa10d8a1fc63f659 Ieb69b0092465a71e7233cc63bddb149998f96410 I48b2a181e091c3095299646be376b7953a7d849c I14fd6f9990e3fe58671ff256baa8e6dfa0376b1f I952f7521284a41af56698cd8fc72b833210ea30e I792d4c1b4d78f3cf67cc0c0150f0c93341d81979 I48792f56b4edc3fb93897d89ad18402a27652a4b Ia9ae6894333ee8d696a2b946b565496e1c26554f I039557aed52693dfad9b76ea0f2f746193aa4316 I6d29a32251144e3ff33841516ea265ea9964f46e I1cfccc33d6e412dfcaaf1793ce19ea27c4b3b724 I810ae725fe1914ff605cf9704f8bdfb1a07b8f9b Ic803dcddb6656c4c4be82b49b11530624942012d I0c8d97679ee186ca8205aeeafa0030568f540d2e I8ceaa65e5e0905fcd85bf8e793b4a5ab2d2df64c I4098a7640a91c7f2695748a7a7ddf96f16e7981b Iae2433a2060f2415dfcd30dd7a16338728b91ec2 Change-Id: Ic6a568411a70ed40e7ca3a14a4dfef0e17fc8543 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32735 Reviewed-by: Derek MORTON Reviewed-by: Yann GAUTIER Tested-by: Yann GAUTIER Reviewed-by: Christophe GUIBOUT Reviewed-by: Andrew LYNN --- arch/arm/configs/u8500_defconfig | 59 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 63bdb56275a..656448337bd 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -24,16 +24,31 @@ CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_HREFV60=y CONFIG_MACH_SNOWBALL=y CONFIG_DBX500_PRCMU_DEBUG=y +CONFIG_U8500_REGULATOR_DEBUG=y CONFIG_DB8500_MLOADER=y CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y +CONFIG_DBX500_PRCMU_QOS_POWER=y +CONFIG_DBX500_PRCMU_DEBUG=y +CONFIG_CPU_IDLE=y +CONFIG_UX500_CPUIDLE=y +# CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set +CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 +CONFIG_UX500_CPUIDLE_DEBUG=y CONFIG_UX500_SUSPEND=y -CONFIG_UX500_SUSPEND_STANDBY=y -CONFIG_UX500_SUSPEND_MEM=y CONFIG_UX500_SUSPEND_DBG=y CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y +CONFIG_UX500_SUSPEND_STANDBY=y +CONFIG_UX500_SUSPEND_MEM=y +CONFIG_UX500_CONTEXT=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_STAT_DETAILS=y +CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y @@ -63,11 +78,16 @@ CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y CONFIG_NET_KEY=y +# CONFIG_NET_KEY_MIGRATE is not set CONFIG_INET=y CONFIG_IP_MULTICAST=y # CONFIG_IP_MROUTE is not set CONFIG_IP_ADVANCED_ROUTER=y CONFIG_IP_MULTIPLE_TABLES=y +# CONFIG_IP_FIB_TRIE_STATS is not set +CONFIG_IP_MULTIPLE_TABLES=y +# CONFIG_IP_ROUTE_MULTIPATH is not set +# CONFIG_IP_ROUTE_VERBOSE is not set CONFIG_IP_PNP=y CONFIG_IP_PNP_DHCP=y # CONFIG_INET_LRO is not set @@ -77,6 +97,7 @@ CONFIG_IPV6=y # CONFIG_INET6_XFRM_MODE_BEET is not set # CONFIG_IPV6_SIT is not set CONFIG_NETFILTER=y +CONFIG_NETFILTER_NETLINK=y CONFIG_NETFILTER_NETLINK_QUEUE=y CONFIG_NETFILTER_NETLINK_LOG=y CONFIG_NF_CONNTRACK=y @@ -99,6 +120,18 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y + +CONFIG_AVERAGE=y + +#WIRELESS +CONFIG_FIB_RULES=y +CONFIG_WIRELESS=y +CONFIG_COMPAT_WIRELESS=y +CONFIG_COMPAT_WIRELESS_MODULES=y +CONFIG_CFG80211=y +CONFIG_COMPAT_MAC80211_RC_DEFAULT="minstrel_ht" +CONFIG_MAC80211_RC_DEFAULT_MINSTREL=y +CONFIG_COMPAT_RFKILL=y CONFIG_NL80211_TESTMODE=y CONFIG_CFG80211_REG_DEBUG=y CONFIG_RFKILL=y @@ -134,6 +167,21 @@ CONFIG_SMSC911X=y CONFIG_PPP=y CONFIG_PPP_ASYNC=y CONFIG_PPP_MPPE=y +CONFIG_CAIF_TTY=m +# CONFIG_CAIF_SPI_SLAVE is not set +CONFIG_CAIF_HSI=m +CONFIG_PPP=y +# CONFIG_PPP_MULTILINK is not set +# CONFIG_PPP_FILTER is not set +CONFIG_PPP_ASYNC=y +# CONFIG_PPP_SYNC_TTY is not set +# CONFIG_PPP_DEFLATE is not set +# CONFIG_PPP_BSDCOMP is not set +CONFIG_PPP_MPPE=y +# CONFIG_PPPOE is not set +CONFIG_PPPOLAC=y +CONFIG_PPPOPNS=y +CONFIG_SLHC=y # CONFIG_INPUT_MOUSEDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set @@ -172,8 +220,12 @@ CONFIG_SENSORS_L3G4200D=y CONFIG_WATCHDOG=y CONFIG_U8500_WATCHDOG_DEBUG=y CONFIG_TPS6105X=y +CONFIG_WATCHDOG_CORE=y +CONFIG_U8500_WATCHDOG_DEBUG=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y +CONFIG_MFD_DB8500_PRCMU=y +CONFIG_TPS6105X=y CONFIG_AB5500_CORE=y CONFIG_AB8500_CORE=y CONFIG_MFD_DB8500_PRCMU=y @@ -195,6 +247,9 @@ CONFIG_FB=y CONFIG_FB_MCDE=y CONFIG_MCDE_FB_AVOID_REALLOC=y CONFIG_AV8100_HWTRIG_I2SDAT3=y +CONFIG_MCDE_DISPLAY_GENERIC_DSI=y +CONFIG_MCDE_DISPLAY_AV8100=y +# CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE is not set CONFIG_FB_B2R2=y CONFIG_B2R2_PLUG_CONF=y CONFIG_SOUND=y -- cgit v1.2.3 From 5e99374d6b715cd5c7d15b6bd07c7e11c4dead51 Mon Sep 17 00:00:00 2001 From: Arun Murthy Date: Thu, 22 Sep 2011 13:09:55 +0530 Subject: power: ab5500-bm: set default charging current In case of standard host, current that can be drawn for charging is to be notified by the usb driver. Until this notification battery driver can go ahead charging by consuming 100mA as per the specs. Change-Id: I2687afa5dca39d0e800011848b38f2822d0bc7e7 Signed-off-by: Arun Murthy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/31934 Reviewed-by: Praveena NADAHALLY Reviewed-by: Karl KOMIEROWSKI Reviewed-by: Rabin VINCENT --- drivers/power/ab5500_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/ab5500_charger.c b/drivers/power/ab5500_charger.c index b4ac1ac6bb7..4ac35fcb1e8 100644 --- a/drivers/power/ab5500_charger.c +++ b/drivers/power/ab5500_charger.c @@ -313,7 +313,7 @@ static int ab5500_charger_max_usb_curr(struct ab5500_charger *di, case USB_STAT_STD_HOST_C_S: dev_dbg(di->dev, "USB Type - Standard host is " "detected through USB driver\n"); - ret = -1; + di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09; break; case USB_STAT_HOST_CHG_HS_CHIRP: di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; -- cgit v1.2.3 From c6a48a3a5dbe659ed86d0917a975fb40d7ea7249 Mon Sep 17 00:00:00 2001 From: Naga Radhesh Date: Mon, 10 Oct 2011 14:21:05 +0530 Subject: mfd: add irq_wake for TC3589x add enable_irq_wake during suspend and disable_irq_wake during resume for TC3589x keypad ST-Ericsson ID: 365245 ST-Ericsson Linux next: ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Naga Radhesh Change-Id: If9d2f272f4db70048220095c9749fd05245010aa Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33409 Reviewed-by: QABUILD Reviewed-by: Rabin VINCENT --- drivers/mfd/tc3589x.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/mfd/tc3589x.c b/drivers/mfd/tc3589x.c index 05e96d03108..7bbcc6e332c 100644 --- a/drivers/mfd/tc3589x.c +++ b/drivers/mfd/tc3589x.c @@ -451,6 +451,8 @@ static int tc3589x_suspend(struct device *dev) ret = tc3589x_reg_write(tc3589x, TC3589x_CLKMODE, TC3589x_CLKMODE_MODCTL_SLEEP); + } else { + enable_irq_wake(client->irq); } out: return ret; @@ -490,6 +492,8 @@ static int tc3589x_resume(struct device *dev) if (ret < 0) break; } + } else { + disable_irq_wake(client->irq); } out: return ret; -- cgit v1.2.3 From 6bd67c92f654c96cde012202be6f419272fb3185 Mon Sep 17 00:00:00 2001 From: Joseph V P Date: Fri, 7 Oct 2011 16:10:39 +0530 Subject: arm: u5500: Enable HDMI display For supporting HDMI by default, HDMI configurations are enabled. ST-Ericsson ID: 362962 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I1ad6b80f56cbab53462e092eb45768b22ce55a04 Signed-off-by: Joseph V P Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33337 Reviewed-by: Per PERSSON Reviewed-by: Jimmy RUBIN Reviewed-by: Jayarami REDDY Reviewed-by: Rabin VINCENT --- arch/arm/configs/u8500_defconfig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 656448337bd..8277219b6b8 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -37,6 +37,9 @@ CONFIG_UX500_CPUIDLE=y # CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 CONFIG_UX500_CPUIDLE_DEBUG=y +CONFIG_DISPLAY_SONY_ACX424AKP_DSI_PRIMARY=y +CONFIG_DISPLAY_AV8100_TERTIARY=y +CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y CONFIG_UX500_SUSPEND=y CONFIG_UX500_SUSPEND_DBG=y CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y @@ -246,6 +249,10 @@ CONFIG_GPU_MALI=y CONFIG_FB=y CONFIG_FB_MCDE=y CONFIG_MCDE_FB_AVOID_REALLOC=y +CONFIG_MCDE_DISPLAY_DSI=y +CONFIG_MCDE_DISPLAY_AV8100=y +# CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE is not set +CONFIG_AV8100=y CONFIG_AV8100_HWTRIG_I2SDAT3=y CONFIG_MCDE_DISPLAY_GENERIC_DSI=y CONFIG_MCDE_DISPLAY_AV8100=y -- cgit v1.2.3 From 94d7e004d402ab5227ebe2600cfe2180ec62dcb2 Mon Sep 17 00:00:00 2001 From: Marcus Lorentzon Date: Mon, 10 Oct 2011 13:11:16 +0200 Subject: ux500: mcde: Include S6D16D0 driver by default ST-Ericsson ID: 365249 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I38bfb6a8d33a76af94b61b0ee94dbcce8cff901e Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33449 Reviewed-by: Marcus LORENTZON Tested-by: Marcus LORENTZON Reviewed-by: Jimmy RUBIN Reviewed-by: Per PERSSON Reviewed-by: QABUILD --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 8277219b6b8..432ef2ee0e6 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -254,6 +254,7 @@ CONFIG_MCDE_DISPLAY_AV8100=y # CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE is not set CONFIG_AV8100=y CONFIG_AV8100_HWTRIG_I2SDAT3=y +CONFIG_MCDE_DISPLAY_SAMSUNG_S6D16D0=y CONFIG_MCDE_DISPLAY_GENERIC_DSI=y CONFIG_MCDE_DISPLAY_AV8100=y # CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE is not set -- cgit v1.2.3 From f8117d0029f199df9c6687c776e14f3572989dba Mon Sep 17 00:00:00 2001 From: Bengt Jonsson Date: Tue, 4 Oct 2011 10:56:48 +0200 Subject: ab8500: Separate regulator and MFD platform data The ab8500 MFD should not have knowledge about regulator- specific platform data like number of regulators and regulator registers. As the regulator platform data is about to grow with external regulators, this information is moved to a new structure provided by the regulator driver. ST-Ericsson ID: 282517 ST-Ericsson Linux next: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I77d03ffcc3273b0659dea3cffd8191b8c94b83d2 Signed-off-by: Bengt Jonsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33615 Reviewed-by: QABUILD Reviewed-by: Yvan FILLION --- include/linux/mfd/ab8500.h | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/include/linux/mfd/ab8500.h b/include/linux/mfd/ab8500.h index eb613588110..8795f6b9f43 100644 --- a/include/linux/mfd/ab8500.h +++ b/include/linux/mfd/ab8500.h @@ -171,8 +171,7 @@ struct ab8500 { u8 oldmask[AB8500_NUM_IRQ_REGS]; }; -struct regulator_reg_init; -struct regulator_init_data; +struct ab8500_regulator_platform_data; struct ab8500_accdet_platform_data; struct ab8500_denc_platform_data; struct ab8500_audio_platform_data; @@ -186,9 +185,6 @@ struct ab8500_gpio_platform_data; * @thermal_time_out: Time out before the thermal alarm should be ignored * @irq_base: start of AB8500 IRQs, AB8500_NR_IRQS will be used * @init: board-specific initialization after detection of ab8500 - * @num_regulator_reg_init: number of regulator init registers - * @regulator_reg_init: regulator init registers - * @num_regulator: number of regulators * @regulator: machine-specific constraints for regulators * @accdet: machine-specific Accessory detection data * @battery: machine-specific battery management data @@ -202,10 +198,7 @@ struct ab8500_platform_data { long thermal_set_time_sec; long thermal_time_out; void (*init) (struct ab8500 *); - int num_regulator_reg_init; - struct ab8500_regulator_reg_init *regulator_reg_init; - int num_regulator; - struct regulator_init_data *regulator; + struct ab8500_regulator_platform_data *regulator; struct abx500_accdet_platform_data *accdet; struct ab8500_bm_data *battery; struct ab8500_denc_platform_data *denc; -- cgit v1.2.3 From 9ad30b0ae6f81aea7c27cfcfe4fb735397d86cd9 Mon Sep 17 00:00:00 2001 From: Bengt Jonsson Date: Tue, 4 Oct 2011 11:40:10 +0200 Subject: regulators: ab8500: Add support of low voltage battery Low voltage batteries have a wider voltage range with lower operating voltages. Some consumers in the platform may not work with the lower voltages and therefore need an extra regulator to boost the voltage in this case. This driver adds support for checking the consumers that need higher voltage (Vaux1, 2 and 3 regulators, 3 V SIM) and control the external buck/boost regulator accordingly. Note that to utilize the low voltage battery support, the battery voltage thresholds must be changed. This applies for the low battery voltage threshold of the battery manager and the OTP setting for the AB8500 BattOk levels. ST-Ericsson ID: 282517, 363432 ST-Ericsson Linux next: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ife07a622ec9748c027dbbd78b01e4ee7e92629ec Signed-off-by: Bengt Jonsson Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33616 Reviewed-by: QABUILD --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 432ef2ee0e6..4e6858d42e2 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -235,6 +235,7 @@ CONFIG_MFD_DB8500_PRCMU=y CONFIG_REGULATOR_DEBUG=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y +CONFIG_REGULATOR_AB8500_EXT=y CONFIG_REGULATOR_AB8500_DEBUG=y CONFIG_REGULATOR_DB8500_PRCMU=y CONFIG_MEDIA_SUPPORT=y -- cgit v1.2.3 From 51d5642458d33f11edf01fdb6c5fab55632a4ab5 Mon Sep 17 00:00:00 2001 From: Chris Kimber Date: Wed, 28 Sep 2011 16:19:10 +0100 Subject: hwmon: dbx500: Automatically setup temp monitoring When a value other than 0 is written to either the _min or _max attributes, automatically start the PRCMU monitoring. Also at driver init start monitoring the critical temperature to prevent overheating. ST-Ericsson ID: 364370 ST-Ericsson Linux next: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I8fefbf30eeb07d6caf4ee73af487b948fbd2fe7e Signed-off-by: Chris Kimber Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/32404 Reviewed-by: QABUILD Reviewed-by: Duncan PATERSON Reviewed-by: Andrew LYNN --- drivers/hwmon/dbx500.c | 101 ++++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/drivers/hwmon/dbx500.c b/drivers/hwmon/dbx500.c index be8ba79c9a8..e240f92ede8 100644 --- a/drivers/hwmon/dbx500.c +++ b/drivers/hwmon/dbx500.c @@ -31,6 +31,11 @@ */ #define DEFAULT_MEASURE_TIME 0xFF +/* + * Default critical sensor temperature + */ +#define DEFAULT_CRITICAL_TEMP 85 + /* This driver monitors DB thermal*/ #define NUM_SENSORS 1 @@ -43,9 +48,39 @@ struct dbx500_temp { unsigned char min_alarm[NUM_SENSORS]; unsigned char max_alarm[NUM_SENSORS]; unsigned short measure_time; + bool monitoring_active; struct mutex lock; }; +static inline void start_temp_monitoring(struct dbx500_temp *data, + const int index) +{ + unsigned int i; + + /* determine if there are any sensors worth monitoring */ + for (i = 0; i < NUM_SENSORS; i++) + if (data->min[i] || data->max[i]) + goto start_monitoring; + + return; + +start_monitoring: + /* kick off the monitor job */ + data->min_alarm[index] = 0; + data->max_alarm[index] = 0; + + (void) prcmu_start_temp_sense(data->measure_time); + data->monitoring_active = true; +} + +static inline void stop_temp_monitoring(struct dbx500_temp *data) +{ + if (data->monitoring_active) { + (void) prcmu_stop_temp_sense(); + data->monitoring_active = false; + } +} + /* HWMON sysfs interface */ static ssize_t show_name(struct device *dev, struct device_attribute *devattr, char *buf) @@ -77,8 +112,13 @@ static ssize_t set_min(struct device *dev, struct device_attribute *devattr, data->min[attr->index - 1] = val; - (void)prcmu_config_hotmon(data->min[attr->index - 1], + stop_temp_monitoring(data); + + (void) prcmu_config_hotmon(data->min[attr->index - 1], data->max[attr->index - 1]); + + start_temp_monitoring(data, (attr->index - 1)); + mutex_unlock(&data->lock); return count; } @@ -100,8 +140,13 @@ static ssize_t set_max(struct device *dev, struct device_attribute *devattr, data->max[attr->index - 1] = val; - (void)prcmu_config_hotmon(data->min[attr->index - 1], + stop_temp_monitoring(data); + + (void) prcmu_config_hotmon(data->min[attr->index - 1], data->max[attr->index - 1]); + + start_temp_monitoring(data, (attr->index - 1)); + mutex_unlock(&data->lock); return count; @@ -121,50 +166,12 @@ static ssize_t set_crit(struct device *dev, mutex_lock(&data->lock); val &= 0xFF; data->crit[attr->index - 1] = val; - (void)prcmu_config_hotdog(data->crit[attr->index - 1]); + (void) prcmu_config_hotdog(data->crit[attr->index - 1]); mutex_unlock(&data->lock); return count; } -/* start/stop temperature measurement */ -static ssize_t start_temp(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct dbx500_temp *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int res = strict_strtoul(buf, 10, &val); - if (res < 0) - return res; - - mutex_lock(&data->lock); - data->measure_time = val & 0xFFFF; - data->min_alarm[attr->index - 1] = 0; - data->max_alarm[attr->index - 1] = 0; - mutex_unlock(&data->lock); - - (void)prcmu_start_temp_sense(data->measure_time); - dev_dbg(&data->pdev->dev, "DBX500 thermal start measurement\n"); - - return count; -} - -static ssize_t stop_temp(struct device *dev, struct device_attribute *devattr, - const char *buf, size_t count) -{ - unsigned long val; - struct dbx500_temp *data = dev_get_drvdata(dev); - int res = strict_strtoul(buf, 10, &val); - if (res < 0) - return res; - - (void)prcmu_stop_temp_sense(); - dev_dbg(&data->pdev->dev, "DBX500 thermal stop measurement\n"); - - return count; -} - /* * show functions (RO nodes) * Notice that min/max/crit refer to degrees @@ -217,8 +224,6 @@ static ssize_t show_max_alarm(struct device *dev, /* Chip name, required by hwmon*/ static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); -static SENSOR_DEVICE_ATTR(temp1_start, S_IWUSR, NULL, start_temp, 1); -static SENSOR_DEVICE_ATTR(temp1_stop, S_IWUSR, NULL, stop_temp, 1); static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, @@ -229,8 +234,6 @@ static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); static struct attribute *dbx500_temp_attributes[] = { &sensor_dev_attr_name.dev_attr.attr, - &sensor_dev_attr_temp1_start.dev_attr.attr, - &sensor_dev_attr_temp1_stop.dev_attr.attr, &sensor_dev_attr_temp1_min.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr, &sensor_dev_attr_temp1_crit.dev_attr.attr, @@ -328,8 +331,8 @@ static int __devinit dbx500_temp_probe(struct platform_device *pdev) for (i = 0; i < NUM_SENSORS; i++) { data->min[i] = 0; - data->max[i] = 0xFF; - data->crit[i] = 0xFF; + data->max[i] = 0; + data->crit[i] = DEFAULT_CRITICAL_TEMP; data->min_alarm[i] = 0; data->max_alarm[i] = 0; } @@ -338,6 +341,10 @@ static int __devinit dbx500_temp_probe(struct platform_device *pdev) data->pdev = pdev; data->measure_time = DEFAULT_MEASURE_TIME; + data->monitoring_active = false; + + /* set PRCMU to disable platform when we get to the critical temp */ + (void) prcmu_config_hotdog(DEFAULT_CRITICAL_TEMP); platform_set_drvdata(pdev, data); -- cgit v1.2.3 From 3a0745aa3d4ac226533bc0cf21f987572ec62e12 Mon Sep 17 00:00:00 2001 From: Chris Kimber Date: Wed, 5 Oct 2011 16:30:40 +0100 Subject: hwmon: dbx500: Update to reflect new PRCMU location PRCMU has been moved, so fix Kconfig and header include path ST-Ericsson ID: 364370 ST-Ericsson Linux next: - ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I052cf03afa4481e1267c07acf2d16401fac6abc2 Signed-off-by: Chris Kimber Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33124 Reviewed-by: QABUILD Reviewed-by: Duncan PATERSON Reviewed-by: Andrew LYNN --- drivers/hwmon/Kconfig | 2 +- drivers/hwmon/dbx500.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index dc662015c13..b0e5152f7f5 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -66,7 +66,7 @@ config SENSORS_AB5500 config SENSORS_DBX500 tristate "DBX500 thermal monitoring" - depends on U8500_PRCMU || U5500_PRCMU + depends on MFD_DB8500_PRCMU || MFD_DB5500_PRCMU default n help If you say yes here you get support for the thermal sensor part diff --git a/drivers/hwmon/dbx500.c b/drivers/hwmon/dbx500.c index e240f92ede8..c034b48f8dd 100644 --- a/drivers/hwmon/dbx500.c +++ b/drivers/hwmon/dbx500.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include -- cgit v1.2.3 From 1a1801ad5d4e8bee0ace1d0f687752224abd1899 Mon Sep 17 00:00:00 2001 From: Jimmy Rubin Date: Tue, 11 Oct 2011 13:51:27 +0200 Subject: ux500: config: Enable Sony display for u8500 ST-Ericsson ID: 321190 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: Trivial Signed-off-by: Jimmy Rubin Change-Id: Idcf924f1e29b24f31349ee602cb86da1fa9e9065 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33681 Reviewed-by: Jimmy RUBIN Tested-by: Jimmy RUBIN Reviewed-by: QABUILD Reviewed-by: Marcus LORENTZON --- arch/arm/configs/u8500_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 4e6858d42e2..87cd82ad23a 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -26,6 +26,9 @@ CONFIG_MACH_SNOWBALL=y CONFIG_DBX500_PRCMU_DEBUG=y CONFIG_U8500_REGULATOR_DEBUG=y CONFIG_DB8500_MLOADER=y +CONFIG_DISPLAY_GENERIC_PRIMARY=y +CONFIG_DISPLAY_GENERIC_DSI_PRIMARY=y +CONFIG_DISPLAY_SONY_ACX424AKP_DSI_PRIMARY=y CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y CONFIG_DISPLAY_AV8100_TERTIARY=y -- cgit v1.2.3 From aa0abef0be71e4bcf8f23a430920be9db1956a9d Mon Sep 17 00:00:00 2001 From: Jonas Aaberg Date: Mon, 3 Oct 2011 07:40:11 +0200 Subject: drivers: i2c: nomadik: Coding style corrections Change-Id: Ib874ec36ebd2847aa3ab25fca119a82000ed6918 Signed-off-by: Jonas Aaberg Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33027 Reviewed-by: Srinidhi KASAGAR Reviewed-by: QABUILD --- drivers/i2c/busses/i2c-nomadik.c | 61 +++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/drivers/i2c/busses/i2c-nomadik.c b/drivers/i2c/busses/i2c-nomadik.c index 3cfad094612..7f9e4db1031 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -63,11 +63,11 @@ /* Master controller (MCR) register */ #define I2C_MCR_OP (0x1 << 0) /* Operation */ #define I2C_MCR_A7 (0x7f << 1) /* 7-bit address */ -#define I2C_MCR_EA10 (0x7 << 8) /* 10-bit Extended address */ +#define I2C_MCR_EA10 (0x7 << 8) /* 10-bit Extended address */ #define I2C_MCR_SB (0x1 << 11) /* Extended address */ #define I2C_MCR_AM (0x3 << 12) /* Address type */ -#define I2C_MCR_STOP (0x1 << 14) /* Stop condition */ -#define I2C_MCR_LENGTH (0x7ff << 15) /* Transaction length */ +#define I2C_MCR_STOP (0x1 << 14) /* Stop condition */ +#define I2C_MCR_LENGTH (0x7ff << 15) /* Transaction length */ /* Status register (SR) */ #define I2C_SR_OP (0x3 << 0) /* Operation */ @@ -77,7 +77,7 @@ #define I2C_SR_LENGTH (0x7ff << 9) /* Transfer length */ /* Interrupt mask set/clear (IMSCR) bits */ -#define I2C_IT_TXFE (0x1 << 0) +#define I2C_IT_TXFE (0x1 << 0) #define I2C_IT_TXFNE (0x1 << 1) #define I2C_IT_TXFF (0x1 << 2) #define I2C_IT_TXFOVR (0x1 << 3) @@ -151,15 +151,15 @@ struct i2c_nmk_client { */ struct nmk_i2c_dev { struct platform_device *pdev; - struct i2c_adapter adap; - int irq; + struct i2c_adapter adap; + int irq; void __iomem *virtbase; struct clk *clk; struct nmk_i2c_controller cfg; struct i2c_nmk_client cli; - int stop; + int stop; struct completion xfer_complete; - int result; + int result; struct regulator *regulator; bool busy; }; @@ -217,8 +217,9 @@ static int flush_i2c_fifo(struct nmk_i2c_dev *dev) } } - dev_err(&dev->pdev->dev, "flushing operation timed out " - "giving up after %d attempts", LOOP_ATTEMPTS); + dev_err(&dev->pdev->dev, + "flushing operation timed out giving up after %d attempts", + LOOP_ATTEMPTS); return -ETIMEDOUT; } @@ -270,7 +271,7 @@ exit: } /* enable peripheral, master mode operation */ -#define DEFAULT_I2C_REG_CR ((1 << 1) | I2C_CR_PE) +#define DEFAULT_I2C_REG_CR ((1 << 1) | I2C_CR_PE) /** * load_i2c_mcr_reg() - load the MCR register @@ -363,8 +364,8 @@ static void setup_i2c_controller(struct nmk_i2c_dev *dev) * and high speed (up to 3.4 Mb/s) */ if (dev->cfg.sm > I2C_FREQ_MODE_FAST) { - dev_err(&dev->pdev->dev, "do not support this mode " - "defaulting to std. mode\n"); + dev_err(&dev->pdev->dev, + "do not support this mode defaulting to std. mode\n"); brcr2 = i2c_clk/(100000 * 2) & 0xffff; writel((brcr1 | brcr2), dev->virtbase + I2C_BRCR); writel(I2C_FREQ_MODE_STANDARD << 4, @@ -556,8 +557,8 @@ static int nmk_i2c_xfer_one(struct nmk_i2c_dev *dev, u16 flags) if (((i2c_sr >> 2) & 0x3) == 0x3) { /* get the abort cause */ cause = (i2c_sr >> 4) & 0x7; - dev_err(&dev->pdev->dev, "%s\n", cause - >= ARRAY_SIZE(abort_causes) ? + dev_err(&dev->pdev->dev, "%s\n", + cause >= ARRAY_SIZE(abort_causes) ? "unknown reason" : abort_causes[cause]); } @@ -582,13 +583,13 @@ static int nmk_i2c_xfer_one(struct nmk_i2c_dev *dev, u16 flags) * * NOTE: * READ TRANSFER : We impose a restriction of the first message to be the - * index message for any read transaction. - * - a no index is coded as '0', - * - 2byte big endian index is coded as '3' - * !!! msg[0].buf holds the actual index. - * This is compatible with generic messages of smbus emulator - * that send a one byte index. - * eg. a I2C transation to read 2 bytes from index 0 + * index message for any read transaction. + * - a no index is coded as '0', + * - 2byte big endian index is coded as '3' + * !!! msg[0].buf holds the actual index. + * This is compatible with generic messages of smbus emulator + * that send a one byte index. + * eg. a I2C transation to read 2 bytes from index 0 * idx = 0; * msg[0].addr = client->addr; * msg[0].flags = 0x0; @@ -646,8 +647,8 @@ static int nmk_i2c_xfer(struct i2c_adapter *i2c_adap, for (i = 0; i < num_msgs; i++) { if (unlikely(msgs[i].flags & I2C_M_TEN)) { - dev_err(&dev->pdev->dev, "10 bit addressing" - "not supported\n"); + dev_err(&dev->pdev->dev, + "10 bit addressing not supported\n"); status = -EINVAL; goto out; @@ -791,8 +792,9 @@ static irqreturn_t i2c_irq_handler(int irq, void *arg) if (dev->cli.count) { dev->result = -EIO; - dev_err(&dev->pdev->dev, "%lu bytes still remain to be" - "xfered\n", dev->cli.count); + dev_err(&dev->pdev->dev, + "%lu bytes still remain to bexfered\n", + dev->cli.count); (void) init_hw(dev); } complete(&dev->xfer_complete); @@ -925,7 +927,7 @@ static int __devinit nmk_i2c_probe(struct platform_device *pdev) } if (request_mem_region(res->start, resource_size(res), - DRIVER_NAME "I/O region") == NULL) { + DRIVER_NAME "I/O region") == NULL) { ret = -EBUSY; goto err_no_region; } @@ -982,8 +984,9 @@ static int __devinit nmk_i2c_probe(struct platform_device *pdev) i2c_set_adapdata(adap, dev); - dev_info(&pdev->dev, "initialize %s on virtual " - "base %p\n", adap->name, dev->virtbase); + dev_info(&pdev->dev, + "initialize %s on virtual base %p\n", + adap->name, dev->virtbase); ret = i2c_add_numbered_adapter(adap); if (ret) { -- cgit v1.2.3 From 2ff89c7dabdb4470a8e0337e724d5bbbd4717f8e Mon Sep 17 00:00:00 2001 From: Shyam Krishnan M Date: Wed, 12 Oct 2011 18:27:13 +0530 Subject: audio: hdmi: Config changes for enabling audio This enables audio in u5500_defconfig ST-Ericsson ID: 362962 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I32d8a1b130fcbd962551f3f4d1aa22b7d414062e Signed-off-by: Shyam Krishnan M Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33805 Reviewed-by: QABUILD Reviewed-by: Ganesh V Tested-by: Ganesh V Reviewed-by: Basavaprabhu BANDI Reviewed-by: Srinidhi KASAGAR --- arch/arm/configs/u8500_defconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 87cd82ad23a..94e48e30eff 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -395,3 +395,6 @@ CONFIG_CRYPTO_DEV_UX500=y CONFIG_CRYPTO_DEV_UX500_HASH=y CONFIG_CRC7=y CONFIG_LIBCRC32C=m +CONFIG_SYS_SOC=y +CONFIG_SND_SOC_UX500_AV8100=y +CONFIG_SND_SOC_AV8100=y -- cgit v1.2.3 From 6af1eedeafca8698ea1a335c1d3dfabb58b25986 Mon Sep 17 00:00:00 2001 From: Kalle Komierowski Date: Thu, 13 Oct 2011 09:41:17 +0200 Subject: Power: ab8500_bm: Update battery capacity before notify Now the battery capacity calculations and the power_supply framework is updated with the new status when battery has been fully charged before clients polling the capacity sysfs node are being notified. ST-Ericsson ID: 358829 ST-Ericsson Linux next: Not tested ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Icd827863bc694e44495487f8536e95ab5fe0714f Signed-off-by: Kalle Komierowski Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33880 Reviewed-by: QABUILD Reviewed-by: Johan PALSSON --- drivers/power/ab8500_fg.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index ce7d9606667..e192893764a 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -1158,9 +1158,14 @@ static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init) } } - if (changed) + if (changed) { power_supply_changed(&di->fg_psy); - + if (di->flags.fully_charged) { + dev_dbg(di->dev, "Full, notifying..: %d\n", + di->flags.fully_charged); + sysfs_notify(&di->fg_kobject, NULL, "charge_full"); + } + } } static void ab8500_fg_charge_state_to(struct ab8500_fg *di, @@ -1794,8 +1799,6 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data) di->flags.fully_charged = true; /* Save current capacity as maximum */ di->bat_cap.max_mah = di->bat_cap.mah; - sysfs_notify(&di->fg_kobject, - NULL, "charge_full"); queue_work(di->fg_wq, &di->fg_work); break; case POWER_SUPPLY_STATUS_CHARGING: -- cgit v1.2.3 From 0ddde01764d23a58b0848e79c935461a507a66b0 Mon Sep 17 00:00:00 2001 From: Avinash A Date: Fri, 14 Oct 2011 16:13:15 +0530 Subject: u8500_defconfig: Enable the Cypress touchscreen ST-Ericsson ID: 366971 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: Trivial Change-Id: I4724b322f5fba1b4fff49abb525380b98e8801a5 Signed-off-by: Avinash A Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/34062 Reviewed-by: Avinash A Tested-by: Avinash A Reviewed-by: QABUILD Reviewed-by: Rabin VINCENT --- arch/arm/configs/u8500_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 94e48e30eff..ac632c2a90d 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -198,6 +198,8 @@ CONFIG_KEYBOARD_TC3589X=y # CONFIG_INPUT_MOUSE is not set CONFIG_INPUT_TOUCHSCREEN=y CONFIG_TOUCHSCREEN_BU21013=y +CONFIG_TOUCHSCREEN_CYTTSP_CORE=y +CONFIG_TOUCHSCREEN_CYTTSP_SPI=y CONFIG_INPUT_MISC=y CONFIG_INPUT_AB8500_ACCDET=y CONFIG_INPUT_AB8500_PONKEY=y -- cgit v1.2.3 From 28621b93db7c58ddffdc94e29c1a420bbd097eb6 Mon Sep 17 00:00:00 2001 From: Jayarami Reddy Date: Wed, 12 Oct 2011 16:57:22 +0530 Subject: arm: u5500: Enable 32bpp for primary display ST-Ericsson ID: 361885 ST-Ericsson Linux next: NA ST-Ericsson FOSS-OUT ID: Trivial Change-Id: Ic914e764ee4816fbfcfbba5129426e2ba51a88ab Signed-off-by: Jayarami Reddy Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/33780 Reviewed-by: Jimmy RUBIN --- arch/arm/configs/u8500_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index ac632c2a90d..8f7f7241eeb 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -40,6 +40,7 @@ CONFIG_UX500_CPUIDLE=y # CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 CONFIG_UX500_CPUIDLE_DEBUG=y +CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y CONFIG_DISPLAY_SONY_ACX424AKP_DSI_PRIMARY=y CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y -- cgit v1.2.3 From d892c105e4c5d1b0d09efd092c5781a702a7b584 Mon Sep 17 00:00:00 2001 From: Robert Marklund Date: Mon, 17 Oct 2011 17:19:39 +0200 Subject: config: minimize the defconfig again Change-Id: If872084fce0c37126f9196a70c0f76db1c4cf37c Signed-off-by: Robert Marklund --- arch/arm/configs/u8500_defconfig | 78 +++------------------------------------- 1 file changed, 5 insertions(+), 73 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 8f7f7241eeb..62f632cd74e 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -24,38 +24,17 @@ CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_HREFV60=y CONFIG_MACH_SNOWBALL=y CONFIG_DBX500_PRCMU_DEBUG=y -CONFIG_U8500_REGULATOR_DEBUG=y CONFIG_DB8500_MLOADER=y -CONFIG_DISPLAY_GENERIC_PRIMARY=y -CONFIG_DISPLAY_GENERIC_DSI_PRIMARY=y -CONFIG_DISPLAY_SONY_ACX424AKP_DSI_PRIMARY=y CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y CONFIG_DISPLAY_GENERIC_DSI_PRIMARY_VSYNC=y -CONFIG_DISPLAY_AV8100_TERTIARY=y -CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y -CONFIG_DBX500_PRCMU_QOS_POWER=y -CONFIG_DBX500_PRCMU_DEBUG=y -CONFIG_CPU_IDLE=y -CONFIG_UX500_CPUIDLE=y -# CONFIG_UX500_DEBUG_NO_LAUTERBACH is not set -CONFIG_U8500_CPUIDLE_DEEPEST_STATE=2 -CONFIG_UX500_CPUIDLE_DEBUG=y -CONFIG_MCDE_DISPLAY_PRIMARY_32BPP=y CONFIG_DISPLAY_SONY_ACX424AKP_DSI_PRIMARY=y CONFIG_DISPLAY_AV8100_TERTIARY=y CONFIG_DISPLAY_AV8100_TRIPPLE_BUFFER=y CONFIG_UX500_SUSPEND=y -CONFIG_UX500_SUSPEND_DBG=y -CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y CONFIG_UX500_SUSPEND_STANDBY=y CONFIG_UX500_SUSPEND_MEM=y -CONFIG_UX500_CONTEXT=y -CONFIG_CPU_FREQ=y -CONFIG_CPU_FREQ_STAT_DETAILS=y -CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y -CONFIG_CPU_FREQ_GOV_POWERSAVE=y -CONFIG_CPU_FREQ_GOV_USERSPACE=y -CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_UX500_SUSPEND_DBG=y +CONFIG_UX500_SUSPEND_DBG_WAKE_ON_UART=y CONFIG_NO_HZ=y CONFIG_HIGH_RES_TIMERS=y CONFIG_SMP=y @@ -85,16 +64,10 @@ CONFIG_NET=y CONFIG_PACKET=y CONFIG_UNIX=y CONFIG_NET_KEY=y -# CONFIG_NET_KEY_MIGRATE is not set CONFIG_INET=y CONFIG_IP_MULTICAST=y -# CONFIG_IP_MROUTE is not set CONFIG_IP_ADVANCED_ROUTER=y CONFIG_IP_MULTIPLE_TABLES=y -# CONFIG_IP_FIB_TRIE_STATS is not set -CONFIG_IP_MULTIPLE_TABLES=y -# CONFIG_IP_ROUTE_MULTIPATH is not set -# CONFIG_IP_ROUTE_VERBOSE is not set CONFIG_IP_PNP=y CONFIG_IP_PNP_DHCP=y # CONFIG_INET_LRO is not set @@ -104,7 +77,6 @@ CONFIG_IPV6=y # CONFIG_INET6_XFRM_MODE_BEET is not set # CONFIG_IPV6_SIT is not set CONFIG_NETFILTER=y -CONFIG_NETFILTER_NETLINK=y CONFIG_NETFILTER_NETLINK_QUEUE=y CONFIG_NETFILTER_NETLINK_LOG=y CONFIG_NF_CONNTRACK=y @@ -127,18 +99,7 @@ CONFIG_BT_BNEP=y CONFIG_BT_BNEP_MC_FILTER=y CONFIG_BT_BNEP_PROTO_FILTER=y CONFIG_BT_HIDP=y - -CONFIG_AVERAGE=y - -#WIRELESS -CONFIG_FIB_RULES=y -CONFIG_WIRELESS=y -CONFIG_COMPAT_WIRELESS=y -CONFIG_COMPAT_WIRELESS_MODULES=y CONFIG_CFG80211=y -CONFIG_COMPAT_MAC80211_RC_DEFAULT="minstrel_ht" -CONFIG_MAC80211_RC_DEFAULT_MINSTREL=y -CONFIG_COMPAT_RFKILL=y CONFIG_NL80211_TESTMODE=y CONFIG_CFG80211_REG_DEBUG=y CONFIG_RFKILL=y @@ -154,9 +115,9 @@ CONFIG_MISC_DEVICES=y CONFIG_AB8500_PWM=y CONFIG_SENSORS_BH1780=y CONFIG_STE_TRACE_MODEM=y +CONFIG_DISPDEV=y CONFIG_U8500_SIM_DETECT=y CONFIG_STM_TRACE=y -CONFIG_DISPDEV=y CONFIG_SCSI=y CONFIG_BLK_DEV_SD=y CONFIG_MD=y @@ -171,24 +132,11 @@ CONFIG_SMSC911X=y # CONFIG_NETDEV_1000 is not set # CONFIG_NETDEV_10000 is not set # CONFIG_WLAN is not set -CONFIG_PPP=y -CONFIG_PPP_ASYNC=y -CONFIG_PPP_MPPE=y CONFIG_CAIF_TTY=m -# CONFIG_CAIF_SPI_SLAVE is not set CONFIG_CAIF_HSI=m CONFIG_PPP=y -# CONFIG_PPP_MULTILINK is not set -# CONFIG_PPP_FILTER is not set CONFIG_PPP_ASYNC=y -# CONFIG_PPP_SYNC_TTY is not set -# CONFIG_PPP_DEFLATE is not set -# CONFIG_PPP_BSDCOMP is not set CONFIG_PPP_MPPE=y -# CONFIG_PPPOE is not set -CONFIG_PPPOLAC=y -CONFIG_PPPOPNS=y -CONFIG_SLHC=y # CONFIG_INPUT_MOUSEDEV is not set CONFIG_INPUT_EVDEV=y # CONFIG_KEYBOARD_ATKBD is not set @@ -229,42 +177,29 @@ CONFIG_SENSORS_L3G4200D=y CONFIG_WATCHDOG=y CONFIG_U8500_WATCHDOG_DEBUG=y CONFIG_TPS6105X=y -CONFIG_WATCHDOG_CORE=y -CONFIG_U8500_WATCHDOG_DEBUG=y CONFIG_MFD_STMPE=y CONFIG_MFD_TC3589X=y -CONFIG_MFD_DB8500_PRCMU=y -CONFIG_TPS6105X=y CONFIG_AB5500_CORE=y CONFIG_AB8500_CORE=y CONFIG_MFD_DB8500_PRCMU=y CONFIG_REGULATOR_DEBUG=y CONFIG_REGULATOR_VIRTUAL_CONSUMER=y CONFIG_REGULATOR_AB8500=y -CONFIG_REGULATOR_AB8500_EXT=y -CONFIG_REGULATOR_AB8500_DEBUG=y CONFIG_REGULATOR_DB8500_PRCMU=y +CONFIG_REGULATOR_AB8500_DEBUG=y CONFIG_MEDIA_SUPPORT=y CONFIG_VIDEO_DEV=y # CONFIG_MEDIA_TUNER_CUSTOMISE is not set # CONFIG_VIDEO_CAPTURE_DRIVERS is not set CONFIG_RADIO_CG2900=y CONFIG_DRM=y -CONFIG_SND_SOC_UX500_CG29XX=y -CONFIG_SND_SOC_CG29XX=y CONFIG_GPU_MALI=y CONFIG_FB=y CONFIG_FB_MCDE=y CONFIG_MCDE_FB_AVOID_REALLOC=y -CONFIG_MCDE_DISPLAY_DSI=y -CONFIG_MCDE_DISPLAY_AV8100=y -# CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE is not set -CONFIG_AV8100=y -CONFIG_AV8100_HWTRIG_I2SDAT3=y CONFIG_MCDE_DISPLAY_SAMSUNG_S6D16D0=y -CONFIG_MCDE_DISPLAY_GENERIC_DSI=y -CONFIG_MCDE_DISPLAY_AV8100=y # CONFIG_MCDE_DISPLAY_HDMI_FB_AUTO_CREATE is not set +CONFIG_AV8100_HWTRIG_I2SDAT3=y CONFIG_FB_B2R2=y CONFIG_B2R2_PLUG_CONF=y CONFIG_SOUND=y @@ -398,6 +333,3 @@ CONFIG_CRYPTO_DEV_UX500=y CONFIG_CRYPTO_DEV_UX500_HASH=y CONFIG_CRC7=y CONFIG_LIBCRC32C=m -CONFIG_SYS_SOC=y -CONFIG_SND_SOC_UX500_AV8100=y -CONFIG_SND_SOC_AV8100=y -- cgit v1.2.3 From 34b0c08703df4b0f6a4e34f80a39dad8740143ed Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Thu, 10 Nov 2011 09:23:10 +0100 Subject: config: Update u8500_defconfig vs new MUSB driver in 3.1 Signed-off-by: Philippe Langlais --- arch/arm/configs/u8500_defconfig | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 62f632cd74e..661e8e469be 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -214,17 +214,17 @@ CONFIG_SND_SOC_UX500_AV8100=y CONFIG_USB=y # CONFIG_USB_DEVICE_CLASS is not set CONFIG_USB_SUSPEND=y +CONFIG_USB_OTG=y # CONFIG_USB_OTG_WHITELIST is not set CONFIG_USB_MON=y CONFIG_USB_MUSB_HDRC=y CONFIG_USB_MUSB_UX500=y -CONFIG_USB_MUSB_OTG=y -CONFIG_USB_GADGET_MUSB_HDRC=y CONFIG_USB_ACM=y CONFIG_USB_STORAGE=y CONFIG_USB_LIBUSUAL=y CONFIG_USB_GADGET=y CONFIG_USB_GADGET_VBUS_DRAW=500 +CONFIG_USB_GADGET_MUSB_HDRC=m CONFIG_USB_ZERO=m CONFIG_USB_ETH=m CONFIG_USB_FILE_STORAGE=m @@ -314,12 +314,10 @@ CONFIG_NLS_ISO8859_1=y CONFIG_PRINTK_TIME=y CONFIG_MAGIC_SYSRQ=y CONFIG_UNUSED_SYMBOLS=y -CONFIG_DEBUG_KERNEL=y CONFIG_DETECT_HUNG_TASK=y CONFIG_SCHEDSTATS=y CONFIG_TIMER_STATS=y # CONFIG_DEBUG_PREEMPT is not set -CONFIG_DEBUG_SPINLOCK_SLEEP=y CONFIG_DEBUG_INFO=y CONFIG_SYSCTL_SYSCALL_CHECK=y CONFIG_FUNCTION_TRACER=y -- cgit v1.2.3 From 7038a53bf82eb1e3fa2dd9859bb7b089c7ca522c Mon Sep 17 00:00:00 2001 From: Mattias Wallin Date: Fri, 3 Dec 2010 13:19:13 +0100 Subject: irq: Allow threaded and nested irqs to be shared This patch will make the threaded nested handler run all the registered shared action handlers and not just the first registered. Signed-off-by: Mattias Wallin Change-Id: Ib484a2fa50186dbc05aedbcb9936518302ab801b Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/10494 Reviewed-by: Rabin VINCENT Reviewed-by: Jonas ABERG --- kernel/irq/chip.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kernel/irq/chip.c b/kernel/irq/chip.c index dc5114b4c16..383101aaa92 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -244,7 +244,7 @@ void handle_nested_irq(unsigned int irq) { struct irq_desc *desc = irq_to_desc(irq); struct irqaction *action; - irqreturn_t action_ret; + irqreturn_t action_ret = IRQ_NONE; might_sleep(); @@ -259,7 +259,11 @@ void handle_nested_irq(unsigned int irq) irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS); raw_spin_unlock_irq(&desc->lock); - action_ret = action->thread_fn(action->irq, action->dev_id); + do { + action_ret |= action->thread_fn(action->irq, action->dev_id); + action = action->next; + } while (action); + if (!noirqdebug) note_interrupt(irq, desc, action_ret); -- cgit v1.2.3