From e0f988e13c5704512bedd3cdef97f42a59087e31 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 a8a6682d6b5..301f31e5b5f 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 @@ -25,7 +29,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 struct clock_event_device __percpu **twd_evt; @@ -89,6 +95,52 @@ void twd_timer_stop(struct clock_event_device *clk) disable_percpu_irq(clk->irq); } +#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; @@ -140,6 +192,27 @@ static irqreturn_t twd_handler(int irq, void *dev_id) return IRQ_NONE; } +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. */ @@ -165,7 +238,13 @@ 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 | @@ -173,15 +252,14 @@ 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); this_cpu_clk = __this_cpu_ptr(twd_evt); *this_cpu_clk = clk; - clockevents_register_device(clk); + __get_cpu_var(twd_ce) = clk; + + clockevents_config_and_register(clk, twd_timer_rate, + 0xf, 0xffffffff); enable_percpu_irq(clk->irq, 0); } -- cgit v1.2.3 From 9605f83515173e3a875388678476f4a4ca4d351b 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 ef9ffba97ad..7ea2460d9b6 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; void twd_timer_setup(struct clock_event_device *); void twd_timer_stop(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 301f31e5b5f..be6af1dc4fa 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -33,6 +33,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 struct clock_event_device __percpu **twd_evt; static void twd_set_mode(enum clock_event_mode mode, @@ -263,3 +266,24 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) enable_percpu_irq(clk->irq, 0); } + +#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 f7b261af7aaa3d6e5e009cd87eff5cb1a0fee2f4 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 984014b9264..411a02fa4c9 100644 --- a/arch/arm/include/asm/system.h +++ b/arch/arm/include/asm/system.h @@ -103,6 +103,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 050bae8d769bd14f86748e45b486c2964082c039 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 ef5640b9e21..7c7d5e2d507 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -477,7 +477,7 @@ static void ipi_timer(void) } #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 20b50c839edae7b9d86dcaa0fba82feb87ea61ee 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 7c7d5e2d507..ddc7fd3db9c 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -321,8 +321,6 @@ asmlinkage void __cpuinit secondary_start_kernel(void) notify_cpu_starting(cpu); - calibrate_delay(); - smp_store_cpu_info(cpu); /* -- cgit v1.2.3 From ff5af6240ebe3fad58d6ef06e138c9dd83a13df0 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 bfeea9ba702..06d534f9312 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -18,11 +18,13 @@ * this program. If not, see . */ #include +#include #include #include #include #include #include +#include #define BH1780_REG_CONTROL 0x80 #define BH1780_REG_PARTID 0x8A @@ -40,6 +42,7 @@ struct bh1780_data { struct i2c_client *client; + struct regulator *regulator; int power_state; /* lock for sysfs operations */ struct mutex lock; @@ -72,6 +75,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; @@ -89,13 +95,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, @@ -104,7 +106,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); @@ -116,12 +118,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); @@ -131,7 +143,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[] = { @@ -153,21 +165,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)); @@ -176,11 +204,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; } @@ -216,6 +247,8 @@ static int bh1780_suspend(struct device *dev) if (ret < 0) return ret; + regulator_disable(ddata->regulator); + return 0; } @@ -230,11 +263,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 @@ -253,7 +291,7 @@ static struct i2c_driver bh1780_driver = { .driver = { .name = "bh1780", .pm = BH1780_PMOPS, -}, + }, }; static int __init bh1780_init(void) -- cgit v1.2.3 From c86b10a709f789499354f912299b7b71b8189616 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 82ccd36e2c9..c38cbf04d74 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; } @@ -447,16 +519,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 c830959d6bf2df1990d438cda1868a7577fbe427 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 | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index 5559b229919..3645fc51d95 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -918,6 +918,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; @@ -2171,6 +2177,9 @@ pl022_probe(struct amba_device *adev, const struct amba_id *id) printk(KERN_INFO "pl022: mapped registers from 0x%08x to %p\n", adev->res.start, pl022->virtbase); + 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); @@ -2284,6 +2293,7 @@ pl022_remove(struct amba_device *adev) clk_disable(pl022->clk); clk_unprepare(pl022->clk); clk_put(pl022->clk); + pm_runtime_disable(&adev->dev); iounmap(pl022->virtbase); amba_release_regions(adev); tasklet_disable(&pl022->pump_transfers); @@ -2393,6 +2403,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 2812199e5bf8188371d111aa49a61c00cdec1ebc 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 ddba41d1fcf..cac241a8415 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 449ff45f5d26430c36deb6f56ddd14b3bc413303 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 2fa0901219d..516666f7683 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -168,6 +168,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 dc7bc082928..ec7f6d0e663 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 17354e210f0a223abb378dac489487df23172db2 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 402a70b80255decfc68389728ee0e7b2387e3c5a 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 5267ab93d55..486f392825f 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -267,6 +267,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; } @@ -634,6 +637,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; @@ -1047,6 +1052,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 1c12e898e8955865677b82712504f497fa011e53 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 486f392825f..482176eb647 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -253,6 +253,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; @@ -433,9 +435,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; @@ -520,9 +523,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 e2a8957a8c2384f12d70cd3465606cdddf2e276f 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 482176eb647..f108c77d50d 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -253,8 +253,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; @@ -269,8 +267,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 bb8f8ec5e69e44eaaebe1d9864eeb2feb16e19db 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 f108c77d50d..b7b89aa3a32 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -253,6 +253,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 92b2e06c24fea305e355fd6045a1c40d06714896 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 b7b89aa3a32..f108c77d50d 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -253,8 +253,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 749d1c4bc2cde03e4c05bc51eecaa613edcd233b 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 cf2a563e5a968389b95cbe81a2a71f4c10f22a6f 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/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/ux500_chargalg.h | 38 + include/linux/mfd/abx500.h | 47 +- 25 files changed, 10173 insertions(+), 18 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 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 91be41f6080..68d11f4f0c6 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 8251ce8cd03..2d1aa398617 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_AD7314) += ad7314.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 f1391c21ef2..55feb0a6236 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -317,6 +317,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 @@ -604,6 +618,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 @@ -614,10 +636,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 MFD_DB8500_PRCMU bool "ST-Ericsson DB8500 Power Reset Control Management Unit" diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index b2292eb7524..572a41fa5c4 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 @@ -83,6 +84,7 @@ obj-$(CONFIG_AB5500_CORE) += ab5500-core.o obj-$(CONFIG_AB5500_DEBUG) += ab5500-debugfs.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/ab8500-core.c b/drivers/mfd/ab8500-core.c index d3d572b2317..791a92fd25c 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -705,6 +705,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 dedb7f65cea..5e73c002d77 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -11,6 +11,9 @@ #include #include #include +#include +#include +#include #include #include @@ -18,6 +21,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 @@ -50,7 +60,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, @@ -354,6 +364,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; @@ -515,6 +543,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; } @@ -546,17 +719,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; @@ -567,23 +774,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 e985d1701a8..6062c0a3c9a 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 f20feefac19..aa9fe4ef801 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -7,12 +7,29 @@ #include #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) || @@ -51,7 +68,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 2208a9d5262..f7ad9a48e61 100644 --- a/drivers/misc/ab8500-pwm.c +++ b/drivers/misc/ab8500-pwm.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -27,8 +28,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); @@ -67,9 +70,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); @@ -84,9 +95,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); @@ -116,6 +145,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 @@ -129,14 +160,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 9f88641e67f..dd92217bfc7 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -236,6 +236,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/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 9970337ff04..4288a91bf1a 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -152,6 +152,51 @@ struct abx500_init_settings { u8 setting; }; +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, @@ -189,6 +234,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 96761b9ac480f7dacda095f1b178cf68e7b4a09f 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 ce0e3c67ac4d756c3e8a4028c62d628affbd00f8 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 065d100fa63..0fff50f73b8 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 dc8c550e6cb..3a516edde47 100644 --- a/arch/arm/mm/mmu.c +++ b/arch/arm/mm/mmu.c @@ -281,6 +281,20 @@ static struct mem_type mem_types[] = { PMD_SECT_UNCACHED | PMD_SECT_XN, .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 f7afd9a130e33e6e72e5ad98b386ea82c7e6bfb6 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 3d0c6fb74ae..caead1019e2 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 daf113cf2f3a8d93d139f20eb0b5a84efd8b745e 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 b259c7c644e..3cffa9a41a1 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -904,7 +904,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 1411a4d6a864e0a46983dbb3e13d8e7297edefc0 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 e70a73731ea..32ece9e79a6 100644 --- a/arch/arm/mm/proc-v7.S +++ b/arch/arm/mm/proc-v7.S @@ -298,7 +298,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 22c61aaea4faae8e5a91b3b24e1fe1ad66d87c9d 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 5b0bce61eb6..f0c41295216 100644 --- a/arch/arm/kernel/armksyms.c +++ b/arch/arm/kernel/armksyms.c @@ -49,10 +49,6 @@ extern void __aeabi_ulcmp(void); extern void fpundefinstr(void); - /* 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 2db92efef1e0d839cbd84ab95bcf47c826d0d9e0 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 461e5fc433a6bad36ece907376c1bf532e036984 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 ff972f00857c3f2c5e4299cf697a064a4b28b0b1 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 8085417555d..0697db65efa 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 d6d9df6c6d1d77eb6fb790ee6b5d71832307f0d0 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 2d7b6e7b727..18483b6ad3c 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -93,6 +93,13 @@ CONFIG_STE_DMA40=y CONFIG_STAGING=y CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4=y CONFIG_HSEM_U8500=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 @@ -105,6 +112,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 8e90690451cee614510e841b9b8bdc2e1d2ee6c1 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 3645fc51d95..bdedf6c27d1 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -2403,14 +2403,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 454ab0889f685b5464382cc542e6f4b84af06fec 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 d9d56a194549fca3768c7b0b5da8324324f5c4af 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 06d534f9312..6cd9accd6a9 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -177,9 +177,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 67d0d6109558db4866d5238dfc09a4d80a547ba7 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 | 162 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 150 insertions(+), 12 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 18483b6ad3c..15b9186e0eb 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -1,27 +1,54 @@ 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_HREFV60=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_PM_RUNTIME=y @@ -29,15 +56,44 @@ 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 @@ -46,10 +102,26 @@ CONFIG_SMSC911X=y CONFIG_SMSC_PHY=y # 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 @@ -57,34 +129,76 @@ 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_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_AB5500_CORE=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_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 @@ -105,6 +219,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 @@ -112,15 +231,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 547d81e3309f84b499bfea5295fe78c7a8a485bc 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 fb72a8406f7b0a5af9ee57ab7a54f6ab55cf02d7 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 15b9186e0eb..1119e8f072b 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -176,8 +176,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 @@ -188,7 +188,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 cff94771fbd15a16d84aab27a2f7029bf41083a2 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 c38cbf04d74..018c1ffc7dc 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 56e5870348a04bcccac8365eaa31eb544fd5b745 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 1119e8f072b..7313e37de7d 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -24,9 +24,12 @@ CONFIG_UX500_SOC_DB8500=y CONFIG_MACH_HREFV60=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 @@ -56,6 +59,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 @@ -111,6 +116,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 @@ -169,7 +177,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 43d5be81a60826dc7b8417ba6b858acab70e17f1 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 6f9c4daabef3f67a6fe5c367abdf2c4ca5f16b43 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 7313e37de7d..0e40899f2d1 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 @@ -170,7 +171,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 dc39643671bac0a8b2c2ebcd8c65c666382fd230 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 0e40899f2d1..4896e7acf5e 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -120,7 +120,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 e3d6a6a06f25ac8aecd6276486c074c0e36efaee 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 2faaca100a2abcee1d7b72769be7fb4d65c9c8da 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 05be7b509a9010c98f4ca193d73633a1e3ff81a6 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 f108c77d50d..b3436b88b9f 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -267,7 +267,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 0ada8e3d647be42af72d04185997b8d97408dec5 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 6062c0a3c9a..a9d6be63864 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 8497f23d223cb986265e38d53a2b74f6cf6c9e43 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 b4875ae92f03ea9ee290a72406f34ed67145ed03 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 999453499d05c688e9150577628f2f1a806c44ef 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 d1fd3592923ffc13e5b82c2f0adb53504f89eb76 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 f7200a89326e1b3de2084701240886e1864f1ccb 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 adddd11c3b3..41a10fd5b26 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 b9d7aab0b01b2c096b0d9c38ca58a12d174d26bf 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 6cd9accd6a9..26964144baa 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -116,11 +116,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 4b48e887eb3c569e614ad6c14d3710430f2729e0 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 a800edb43238b68f540928815589a576cf284405 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 fb4d3987479ddbf4074a2ae2dd1af7092f9012d0 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 53eb4e55b28..17cd5c2932b 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 c0ced5838901458dfac109a240aa6021ffd52e38 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 8c55f810ebd7c33b06328789a6e7cad85de3a1ff 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 2db7b23a6ba65279351c79ebba2cc74e00e24cf2 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 6a90ac58e45a4c3c57cd7e9cba6dff853e224a10 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 e339e20356c1f6327fb94292e943aa05fa848079 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 5e73c002d77..6f71a59be58 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 @@ -18,6 +84,11 @@ #include #include +#ifdef CONFIG_DEBUG_FS +#include +#include +#endif + static u32 debug_bank; static u32 debug_address; @@ -52,6 +123,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 @@ -546,6 +636,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); @@ -692,6 +979,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, @@ -737,16 +1028,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; @@ -766,67 +1061,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 f8f83b5eee38b62526dd5339a9faf0972fbc41f8 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 --- 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 4288a91bf1a..262283fed6a 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -189,12 +189,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 757da20328f2245ddee84d819134d8a97ca724bd 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 3cffa9a41a1..9bdce07414e 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -72,6 +72,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 37d05a1b3def2a988a2c7b18c7d8ba793b43c372 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 00c4802e3b91e03962f54e594ed0934e2633aff4 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 b3436b88b9f..e6f2abba469 100644 --- a/drivers/i2c/busses/i2c-nomadik.c +++ b/drivers/i2c/busses/i2c-nomadik.c @@ -430,10 +430,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; @@ -518,10 +517,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 1f4580b11b97a5d97a67c3fa184daa5a7b4c97a4 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 f7ad9a48e61..7cae999889f 100644 --- a/drivers/misc/ab8500-pwm.c +++ b/drivers/misc/ab8500-pwm.c @@ -73,7 +73,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; @@ -161,7 +161,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 be92a946a7b4b5093cae3debb8dc445f56486470 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 262283fed6a..629efaff59a 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -226,6 +226,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 9d0bdbd2621509e302d875f2137fd6df8f07e7b5 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 --- drivers/mfd/Makefile | 2 +- drivers/mfd/ab5500-power.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 drivers/mfd/ab5500-power.c diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 572a41fa5c4..83183a40243 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-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"); -- cgit v1.2.3 From 0d6aaf34a3622c14d96aff968e98dc0dc351d662 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 ff203a42186..a09df711541 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 e4f6bf56888..5f28e852188 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 a70553bfbe0ddffaac67ffe97e0aa9fd821153b3 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 6f71a59be58..f44f202b9e6 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -775,9 +775,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 bd97054482fe09f0182fc015a36c39ccd3c3b658 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 | 53 ++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 4896e7acf5e..b16602fba45 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 @@ -88,12 +89,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 @@ -112,7 +112,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 @@ -120,15 +120,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 @@ -137,6 +132,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 @@ -213,11 +209,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_HSEM_U8500=y CONFIG_CG2900=y @@ -227,6 +226,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 @@ -237,16 +239,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 @@ -269,9 +288,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 3a16f61dd7909ea79dc653d35fafe84272216aff 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 b16602fba45..fc3241fd8ea 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -89,7 +89,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 3109b9f0d4d6fa62b1c8e39c08bfa7155585c16b 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 00233af1acc..11208bda7bd 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -1499,6 +1499,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 ac7eefe0db1b47ff962d13efb61d070a4a228145 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 fc3241fd8ea..382c20ed3b1 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -166,6 +166,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 ed18a63138acd6e1290d797e9a940907ba8d4602 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 382c20ed3b1..3a4e5381889 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -81,6 +81,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 @@ -89,6 +90,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 2d427a4154e4888907095942f9c923997fb89322 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-power.c | 1 - include/linux/mfd/abx500.h | 139 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 2 deletions(-) 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-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/include/linux/mfd/abx500.h b/include/linux/mfd/abx500.h index 629efaff59a..4720f2c471f 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -200,6 +200,145 @@ struct ab5500_platform_data { struct ab5500_regulator_platform_data *regulator; }; +/** + * + * 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, -- cgit v1.2.3 From b8e9214e176bc47bb8e459cac1ec44923b71e3e3 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 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 3a4e5381889..e9fde01f472 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_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_HREFV60=y CONFIG_MACH_U8500_SNOWBALL=y CONFIG_MACH_U5500=y CONFIG_DB8500_MLOADER=y @@ -178,9 +181,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 75d831565e7520448efdde3947bd9fa407067339 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 53426c66352..e76b9eae8e4 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); void (*resume)(void); }; @@ -81,6 +83,18 @@ static inline void outer_resume(void) outer_cache.resume(); } +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 db5abb288056648764b77af658ac4a304df39f82 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-power.c | 33 ++++++++++++++++++++++++++++----- include/linux/mfd/abx500.h | 9 +++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) 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 4720f2c471f..6984b68439f 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -327,6 +327,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 cca139b2d542c75b18323795f7ceec420e4b772d 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 1ebedfb6d46..08fd87766e0 100644 --- a/drivers/gpio/gpio-nomadik.c +++ b/drivers/gpio/gpio-nomadik.c @@ -359,7 +359,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 ec149f32eb3efd96641881df9ae3a8956ee002aa 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 cd26d6f1e4a0e055fb225907e337344dac9a8c2f 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 55feb0a6236..84c20ecd09f 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -331,6 +331,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 83183a40243..b9b217b4c76 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -90,6 +90,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 a3682ef504dcae865f373f195a20eafe0f85eea7 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 8ce0881c4c283ebfc80a095518b03d4233637c6a 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 ddcbfc484075575baf6cfae656702ccf9b2bc14a 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 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 465 insertions(+), 20 deletions(-) diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index f44f202b9e6..5dfffa4ec81 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -83,6 +83,7 @@ #include #include +#include #ifdef CONFIG_DEBUG_FS #include @@ -143,6 +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_REV_REG 0x80 @@ -671,6 +673,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. @@ -1038,6 +1416,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) { @@ -1048,14 +1427,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; } @@ -1063,42 +1442,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; -- cgit v1.2.3 From 742da6b0b8cccf84da90e06f005373cd9fa88929 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 e9fde01f472..3ed08285eb6 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -226,7 +226,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_HSEM_U8500=y -- cgit v1.2.3 From 4bc926e6133d46fcf276b0c9044fc7f6cda80241 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 3ed08285eb6..b188f127835 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -227,6 +227,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_HSEM_U8500=y -- cgit v1.2.3 From 168d48eb1caea755bdf718157882ceaa2b4738ff 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 b188f127835..98ebfefa895 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 @@ -84,7 +81,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 @@ -93,8 +89,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 @@ -120,6 +114,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 @@ -128,7 +126,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 @@ -172,7 +169,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 @@ -181,14 +177,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 @@ -213,6 +211,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 @@ -300,11 +299,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 84804972642f11be5cf0907d4763ee85f02e1513 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 98ebfefa895..da3e2c4057c 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -227,6 +227,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_HSEM_U8500=y -- cgit v1.2.3 From 0ebbca3bf0e29a8a6f1bb82d85fc24dc12e110b2 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 | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index da3e2c4057c..88e83632e7c 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 @@ -79,7 +74,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 @@ -89,6 +83,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 @@ -102,11 +98,6 @@ CONFIG_BLK_DEV_RAM_SIZE=73728 CONFIG_MISC_DEVICES=y CONFIG_AB8500_PWM=y CONFIG_SENSORS_BH1780=y -CONFIG_NETDEVICES=y -CONFIG_SMSC911X=y -CONFIG_SMSC_PHY=y -# 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 @@ -123,6 +114,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 @@ -175,9 +169,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 @@ -193,6 +187,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 @@ -209,8 +204,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 @@ -225,10 +220,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_HSEM_U8500=y CONFIG_CG2900=y @@ -238,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 @@ -290,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 90b94a13a0368094679beb5b5a21438c2a98b3ed 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 88e83632e7c..059618d3642 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 0e81aa1cfeac196016e93e5d6b9869bedb57ed93 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 059618d3642..1165871a783 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -116,9 +116,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 @@ -147,7 +145,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 @@ -215,6 +212,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 1f9dfb66f9da08cc071ccd5ec493bbce875bbd54 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 a2e296a3193c9b96aff2f506c6b2991fd0cad598 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 a6997599a86f11d0dc8dcef209e352b993180dd3 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 1165871a783..2c6b0721eea 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 033aa187301c82eb35b6f1a633ae72a972f26786 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 033171d28d96ad3b1a2c02f7c11761673a8dff52 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 f345574feabab003de6f7b448e46eccef7383899 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 59bece38ad27b213b5cfd41ba369b8124e719d00 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 0290c338ed6ecd58487fae747a65b1eda43f25f2 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 0ce89b187c319ab60b0452ac1324c58ab4ee1c14 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 9b9feb87a59c96bdae4cfaf5bfacb73f23825755 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 ce7ca239c6221b7bd5116ed3c6449c204c2658b3 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 dd92217bfc7..b7c2f4caef5 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -249,6 +249,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 b4e7dd16957e034f8d546c5dbf7bfbf040df913e 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 2c6b0721eea..413384990be 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -151,6 +151,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 b7c2f4caef5..97142009408 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -251,7 +251,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 aea1469530075d272e3f74b8c3dfb6824c8d4e17 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 b979ccfd54b76e57191476134ef40522510e744e 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 79fd606b7cd..2ce2db0447d 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -289,6 +289,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 fe893e91935..adcd340c88a 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_TS72XX_WATCHDOG) += ts72xx_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 bdda10b08d93ca156c7ca165dc43454724b8a24a 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 f5d8606a7b4e9d9de3bcfd5877d98f6090779a88 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 413384990be..a60a8831be2 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 @@ -103,9 +102,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 @@ -118,7 +116,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 @@ -147,6 +147,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 @@ -159,7 +160,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 @@ -225,6 +225,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_HSEM_U8500=y CONFIG_CG2900=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 1d23a64d88b5fd1f03e6577759d38b7cbc90b943 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 a60a8831be2..95e9f6bdd38 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -149,7 +149,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 be56b3d948a0b088fb1292fba7aba1d86ef9f2c1 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 637519d7ba4f2bb6f711729938abc88046e57270 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 5c183e9a477c3823a3020698ffd8683721fbb333 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 95e9f6bdd38..a8124c177be 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 @@ -147,7 +148,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 @@ -199,17 +199,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 @@ -218,6 +212,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 245ce15fe7a945b8237f1a335267b78d61940180 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 26964144baa..b1c5f6cdbfa 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -26,6 +26,10 @@ #include #include +#ifdef CONFIG_HAS_EARLYSUSPEND +#include +#endif + #define BH1780_REG_CONTROL 0x80 #define BH1780_REG_PARTID 0x8A #define BH1780_REG_MANFID 0x8B @@ -43,11 +47,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); @@ -192,6 +204,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; @@ -226,57 +244,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 }, @@ -289,7 +351,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 94f50f3b6d266489dd047e328b9fd55d01dbfe0a 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 b1c5f6cdbfa..ec303edd94a 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -244,6 +244,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; @@ -284,7 +285,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); @@ -313,6 +317,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) @@ -338,7 +343,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 619201d12bd9b9d6047126bb43e96456f17170e4 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 7f9d0dfcc55d9d28db254366a1e831426d240dcd 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 e4247024cb923a7c3e4d417e42f20c5f09e15ae5 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 671c906214ed059aeb416e2d2b2612f03f2b838c 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 f1561a7a1bbdf4022e06b56ac74f348e6fb5a162 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 8b0a3edcb04fdca8e475d5facf90eb260d826712 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 e2139245fa73da9f4b3588504c2dadb7d34594fa 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 68d11f4f0c6..ccfaf0cd8c4 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 2d1aa398617..055cde4d057 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 791a92fd25c..e2e633f0885 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -684,7 +684,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, @@ -778,7 +778,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 3ffc637bbc38ca85c56fa49c5c092f78b17e7ee0 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 | 3 + 7 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 drivers/hwmon/ab5500.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index ccfaf0cd8c4..44b08c044a9 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 055cde4d057..cdfa681c50a 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 ec10629a0b0..4eb01464058 100644 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -992,6 +992,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 6984b68439f..176d138f938 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -222,6 +222,9 @@ enum ab5500_devid { AB5500_DEVID_VIDEO, AB5500_DEVID_DBIECI, AB5500_DEVID_ONSWA, + AB5500_DEVID_CHARGALG, + AB5500_DEVID_BTEMP, + AB5500_DEVID_TEMPMON, AB5500_NUM_DEVICES, }; -- cgit v1.2.3 From 995ab80ff2f7fe579b738ab9ba64b53d44d581b6 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 cf8629283eacf752b5afeed1e6490868c72070c1 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 9ac0d1ea049828f8357ab35a7f47d3e779d4344b 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 2dcab0ee8e47424a61c9d822c27ac355a9f01667 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 94b7160ee5b38774ee70decf4fefaf55ac2e52b6 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 3d527edc85907d3f283ced11449a81f561f63ff7 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 62979776ff1aad32d4186a6a39b5c96d2187afc8 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 4eb01464058..8f08823f99b 100644 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -1315,6 +1315,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 7c32a6ac9737c06544091a302d973597e6bbce4e 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 aa9fe4ef801..3f5286b8e5e 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -8,7 +8,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -19,6 +21,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 15d6c55de94171f0cede94ab5de6725491b4b91d 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 3f5286b8e5e..e7847914e6f 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -23,6 +23,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, @@ -30,23 +34,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 74be100c47497cf03ce8cebe7f016ed5178da31e 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 e7847914e6f..2ae3bcce0ff 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -59,7 +59,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 af44dcf100eee3e516d86d4a0bd4feea2d5d441c 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 176d138f938..a586ce7b7b1 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -350,6 +350,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 97500bd5b094603a1208956eae00390e54647f88 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 | 127 +++++++++++++++++++++++++++++++++++++++++--- include/linux/mfd/tc3589x.h | 61 ++++++++++++--------- 2 files changed, 155 insertions(+), 33 deletions(-) diff --git a/drivers/mfd/tc3589x.c b/drivers/mfd/tc3589x.c index de979742c6f..6059be67bb2 100644 --- a/drivers/mfd/tc3589x.c +++ b/drivers/mfd/tc3589x.c @@ -358,16 +358,112 @@ static int __devexit tc3589x_remove(struct i2c_client *client) } #ifdef CONFIG_PM + +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; } @@ -377,12 +473,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 2dd987654eb2ff3ebe6b1160f2b44a7848ea79c8 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 6059be67bb2..8c58389f695 100644 --- a/drivers/mfd/tc3589x.c +++ b/drivers/mfd/tc3589x.c @@ -484,7 +484,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 c3a4c67067db69243beb5361b711f2259151bad7 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 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c index bdedf6c27d1..39b1e75b4ae 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -512,6 +512,11 @@ static void giveback(struct pl022 *pl022) msg->state = NULL; if (msg->complete) msg->complete(msg->context); + + /* 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 clocks & power */ pm_runtime_put(&pl022->adev->dev); } @@ -2315,11 +2320,6 @@ static int pl022_suspend(struct device *dev) return status; } - amba_vcore_enable(pl022->adev); - amba_pclk_enable(pl022->adev); - load_ssp_default_config(pl022); - amba_pclk_disable(pl022->adev); - amba_vcore_disable(pl022->adev); dev_dbg(dev, "suspended\n"); return 0; } -- cgit v1.2.3 From f68f4dfe6e97c47ada8404ebd302d58788fc7a8f 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 39b1e75b4ae..b2417604707 100644 --- a/drivers/spi/spi-pl022.c +++ b/drivers/spi/spi-pl022.c @@ -1892,7 +1892,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 d80855773c53a0de08e491e8cf73ea21030752f9 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 84c20ecd09f..6c62b868289 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -289,6 +289,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 b9b217b4c76..9bd04bf0ed7 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 433c561ad92e22ca89d2afb28eab63f0856bcb23 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 519c5dac6dc299b94b3ed09f4f1f7ded5dc5ef8e 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 b71bac6281f0a05913466fec98af36e1372355c3 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 37c0ff44572bab0bc2b82b413ec95176aa3cb32c 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 175da56dd4fce6ae05ac3d0f6ab26a6942dae529 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 7ccc3a1eba753e6d820b50b4e393db80a8d345e9 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 8a89dcab20b63c89f695b71cb8213a6e32772a16 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 18f2833a32ca47ec52d1a5307f0b8692f5278cd9 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 2eeae8b9be726a1dd8ccc8efeaf0c1b54af648a5 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 4c76b53d3bfbdff595f430411ef2e77aa4655d78 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 a8124c177be..de51185ec09 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 4768be6fcde06e06bc5f32c6e1bb0c2fd9bf4116 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 de51185ec09..0951a944d74 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -108,6 +108,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 04726665ee068ec46c61bef7a9554d0fa1804fef 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 0951a944d74..3a6d47b9abf 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -160,7 +160,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 44b08c044a9..dafc6227c0d 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 cdfa681c50a..d13b8e0324f 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_AD7314) += ad7314.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 3ba94ffd59ecdee530f65a43e508a761c8fece60 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 3a6d47b9abf..6baf7487e1f 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -179,6 +179,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 7a46cddd981fec903abdbd15d92dd71045f8ea08 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 6baf7487e1f..ae9e452031e 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 @@ -175,12 +179,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 334d7616ae68eccfe9d803f502c58e18959d1990 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 Use new include/linux/mfd/abx500/ab5500.h instead of upstream include/linux/mfd/ab5500/ab5500.h file Change-Id: I11dd2a23323229096b6e0ee2d4dc68cab8919bff --- drivers/leds/leds-ab5500.c | 1 + drivers/mfd/ab5500-core.c | 8 +- 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 | 202 ----------------------------------- include/linux/mfd/abx500/ab5500-bm.h | 4 +- include/linux/mfd/abx500/ab5500.h | 164 ++++++++++++++++++++++++++++ 11 files changed, 177 insertions(+), 208 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 8f08823f99b..d2c29191250 100644 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -22,8 +22,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -388,7 +388,7 @@ static struct ab5500_i2c_banks ab5500_bank_ranges[AB5500_NUM_DEVICES] = { }, }, }, - [AB5500_DEVID_FUELGAUGE] = { + [AB5500_DEVID_FG] = { .nbanks = 1, .bank = (struct ab5500_i2c_ranges []) { { @@ -594,9 +594,9 @@ static struct mfd_cell ab5500_devs[AB5500_NUM_DEVICES] = { }, }, }, - [AB5500_DEVID_FUELGAUGE] = { + [AB5500_DEVID_FG] = { .name = "ab5500-fuelgauge", - .id = AB5500_DEVID_FUELGAUGE, + .id = AB5500_DEVID_FG, .num_resources = 6, .resources = (struct resource[]) { { 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 a586ce7b7b1..66a82b2b148 100644 --- a/include/linux/mfd/abx500.h +++ b/include/linux/mfd/abx500.h @@ -152,208 +152,6 @@ struct abx500_init_settings { u8 setting; }; -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; -}; - -/** - * - * 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_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 - * @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 -}; - -#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; - 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 aea9e4ac992515b9ebccd671a64a1e52dca3197f 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 ae9e452031e..42847b2af27 100644 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -94,10 +94,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 16db42e33381c5ba7462fe66824a5a45d3b15e81 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 42847b2af27..9d13f5a24e4 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -95,6 +95,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 @@ -264,6 +266,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 8f2a33fe1fb490645caa218cc942e870e0167ce0 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 654000fd7b325a24b52e4889469179b0b0d57d35 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 5dfffa4ec81..37491ef2f1a 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -145,7 +145,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 @@ -317,7 +317,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, @@ -351,6 +351,10 @@ static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = { .first = 0xC0, .last = 0xC2, }, + { + .first = 0xf5, + .last = 0xf6, + }, }, }, [AB8500_GAS_GAUGE] = { @@ -370,6 +374,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 ec16864327e15416ecbfc7916471ae7c69f75fe1 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 41a10fd5b26..2120c7ca0ae 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 @@ -1460,7 +1460,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 2c9116f940058294129f7d41885528d8abdc4d77 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, 26 insertions(+) diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 6460487e41b..ac718a57b88 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,6 +30,8 @@ static struct class *hwmon_class; static DEFINE_IDA(hwmon_ida); +static BLOCKING_NOTIFIER_HEAD(hwmon_notifier_list); + /** * hwmon_device_register - register w/ hwmon * @dev: the device to register @@ -73,6 +76,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 c7856425be57661b55d855377240ab2acee1df1d 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 2ae3bcce0ff..d159dbf93eb 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -14,11 +14,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"}; @@ -66,14 +70,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) || @@ -118,6 +159,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 c8ef1305d58f080c5b57a39a97352a3f68c787dc 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 9d13f5a24e4..8da0f840039 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 a40064766a461d2c808c0aec1e43eef5d101a5e4 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 bd230e80113..b5b24989d28 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; @@ -402,20 +402,7 @@ static int amba_pm_runtime_resume(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( amba_pm_runtime_suspend, amba_pm_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 6e0ed74d206455286ad36d7bcd03d076c6ddf752 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 8da0f840039..7daafce77eb 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 d4781e7230f0408e0fde63392ae8eca8a34a7725 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 89c28702974639d940827d2f4c287cf5e26d719f 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 11208bda7bd..c44d757e804 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)) @@ -1730,8 +1730,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) @@ -1973,7 +1971,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); } @@ -1983,6 +1986,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 62220b3595a8fe45e2324210b92b64b441f45e1c 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 | 366 +++++++++++++++++++++++++++++++++++---- 3 files changed, 345 insertions(+), 30 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index 7daafce77eb..837ebccda98 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -179,6 +179,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 925a1e547a8..6fb92f30e33 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -330,6 +330,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 c44d757e804..d2d2c142f4d 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); @@ -1372,9 +1655,9 @@ static int pl011_startup(struct uart_port *port) goto out; /* - * 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 clk_unprep; @@ -1387,29 +1670,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); @@ -1449,7 +1710,7 @@ static int pl011_startup(struct uart_port *port) return 0; clk_dis: - clk_disable(uap->clk); + pl011_power_shutdown(uap); clk_unprep: clk_unprepare(uap->clk); out: @@ -1508,9 +1769,9 @@ 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); clk_unprepare(uap->clk); if (uap->port.dev->platform_data) { @@ -1523,6 +1784,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) @@ -1536,7 +1823,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. */ @@ -1724,6 +2016,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, @@ -1873,7 +2166,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; @@ -1902,6 +2194,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); @@ -1909,6 +2207,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; @@ -1929,6 +2228,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); @@ -1936,6 +2238,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); @@ -1959,6 +2263,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 a0c03256ec9064981056ea594dbc9198c9be3ccc 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 837ebccda98..9e8cd33dedc 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -210,8 +210,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 eacdf20dcab7927929c26c6d0dd28fd51b8c5d02 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 86f0a62ab48ff04db5bb7123a5e291998af3f7ad 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 3fee9f29da3147dd24de7adae68ea000dc90e7ca 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 f02ff6f384bdd69ecf8dd9f8ea0f3b8634b7cecd 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 d2d2c142f4d..88bd41ab6f5 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); @@ -1682,7 +1691,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); @@ -1720,11 +1730,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) @@ -1751,7 +1761,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 @@ -1791,22 +1802,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); } } @@ -1850,7 +1861,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; } @@ -2278,10 +2289,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); } @@ -2293,8 +2304,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 ed133fd8f75842b4a947a7869b81a03b861194f8 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 97142009408..2e32409a342 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -251,7 +251,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 bac9f836287796b0621f802489672efb98dc11bb 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 9e8cd33dedc..584ace2e2a7 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -193,7 +193,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 e661d83c12bb7e7bb22f156a0099c1c59944bc0d 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 9bdce07414e..2218c2a3d8f 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1705,7 +1705,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 d23bb66be54aeb48870d035fc68471f54f7c9d39 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 92ba796d2d814661bfcffdfc06dec1ed8f96a24c 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 584ace2e2a7..7ac7cf02512 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -143,7 +143,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 6a3617cebb52d55b6746f56011aa7ebce8bbbc6d 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 7ac7cf02512..c7e439a20d2 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -192,11 +192,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 517ad64686596a4f869a52d4a389cfdd7795d821 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 c7e439a20d2..cc69896a778 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -77,6 +77,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 @@ -193,6 +194,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 0f0fffcb8d148e11bdee373cb4d9c5ad94f2a3d9 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 2b023ae6cf35329cb552e270050161d7b902942e 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 fdb4b5cb665315aba9decc80de7eb13148ac1c2b 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 a9d6be63864..b6f1e80209a 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -340,7 +340,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 d401bd89a649a1ab3236e2ff0f1bc7bd349a83ce 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 d2c29191250..b2e8bcbdf80 100644 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -1005,6 +1005,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), + }, + }, + }, }; /* 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 30344868f1e1b2108b206d5200ad04b43cfb8a8f 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 cc69896a778..9453e7d34d6 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 9daff37e7d68a12228ac47dcfa66591f8216a2a0 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 9453e7d34d6..5781077ce16 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -77,7 +77,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 @@ -89,6 +88,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 @@ -97,7 +97,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 @@ -208,20 +207,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 @@ -259,13 +252,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 9aa9eed6c85f602f0e77b99ddb014e4e5944a46d 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 5781077ce16..941e8acb5e0 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -204,6 +204,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 0116a0734f0b6d27d2656cd73196a9357cb6af09 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 d33f8e4dff3363898f6119b3f1206ac1748da059 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 941e8acb5e0..b8e50d90894 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -227,7 +227,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 671d382edfbc9844d7dbc141797ce953a68a1f3f 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 b8e50d90894..bf093817b85 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 68027dbd7b6af7a407cf7509c18d389360b96628 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/ab8500-i2c.c | 3 +-- drivers/watchdog/ux500_wdt.c | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/arch/arm/configs/u8500_defconfig b/arch/arm/configs/u8500_defconfig index bf093817b85..25f87632a9e 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 @@ -187,6 +186,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/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 fb5cd266cb677b99340e7ab38fabc047e7acc3c3 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 25f87632a9e..388b4f7889c 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 c2ca343836eaf82c77bf6bfbeb22424e75b3c037 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 388b4f7889c..030a1834048 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 b9d2eb3a32ff756b24416ad0fb616ed6eba1da4b 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 030a1834048..1611b32f6c6 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 b97ba4c962d38c61139e7f6edd65dbd143472fef 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 1611b32f6c6..a6844de5c7b 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 19a287d18bdf50dd6d252b3b23cbf82fe6998425 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 a6844de5c7b..1353057312b 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 5fc7d64b3345eba423f461f7a8a582293155a039 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 1353057312b..017e01b2238 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 62712f6b924f57ee8316f2ce616a0786a90cce43 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 017e01b2238..490cf2c62d7 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 104c25b61b2f0f1835be191cd533ebd40f70f1e1 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 490cf2c62d7..552cf661dce 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 0b9cb998742e385fbe0bfabb7bf734f7103a7b74 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 552cf661dce..02bcba29884 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 d4fed5b3f66ab68cd9d5ec23dce67bb9fc832c8d 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 02bcba29884..52c86d57ccd 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -202,7 +202,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 2ce2db0447d..79fd606b7cd 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -289,22 +289,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 adcd340c88a..fe893e91935 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_TS72XX_WATCHDOG) += ts72xx_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 f601e8cd41c08389bc4167a75ca336252ec0e3ac 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 79fd606b7cd..38597a6747c 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -343,6 +343,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 fe893e91935..f2556f31511 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_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 6dff402956e8b8b965cfcc956ef01378e6919cd0 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 38597a6747c..01477f737ca 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -353,6 +353,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 24b0c2709291788afc54659171bac91c7145e5d0 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 52c86d57ccd..da3ed7f4bba 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -202,6 +202,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 847113f7b328604b55b1414691a3224d3d341845 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 da3ed7f4bba..9576cfbf7bf 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -216,6 +216,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 21fd4e68f7bbc5e2eed3a92fce1f3717b4d35cc6 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 88bd41ab6f5..23b1bbe581a 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -2363,7 +2363,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 e5e4a2d01ef273979a6e1a787869d1e74bc3a931 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 9576cfbf7bf..709e40be2d5 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 @@ -199,7 +205,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 @@ -211,12 +216,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 36a3adb459793797659a3d69b2296a27056fde0e 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 709e40be2d5..c92673ec05b 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -75,7 +75,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 @@ -85,12 +92,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 @@ -107,6 +119,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 @@ -167,6 +180,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 f0812fbb3304d5d93cdb4cec0f8e566f689f1d43 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 c92673ec05b..1ad15367dca 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 @@ -76,13 +61,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 @@ -92,7 +73,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 @@ -107,7 +87,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 @@ -116,40 +95,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 @@ -162,9 +112,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 @@ -181,17 +130,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 @@ -224,60 +164,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 @@ -292,27 +205,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 @@ -322,7 +227,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 71cd68d629ca5936c6ab5da9a6060a556aae56e3 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 f63db862827d2ec5707f0bb78c4ef9a69e1a9c8e 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/ethernet/smsc/smsc911x.c | 112 +++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/drivers/net/ethernet/smsc/smsc911x.c b/drivers/net/ethernet/smsc/smsc911x.c index 8843071fe98..15658d89441 100644 --- a/drivers/net/ethernet/smsc/smsc911x.c +++ b/drivers/net/ethernet/smsc/smsc911x.c @@ -58,6 +58,7 @@ #include #include #include +#include #include "smsc911x.h" #define SMSC_CHIPNAME "smsc911x" @@ -138,6 +139,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 */ @@ -362,6 +367,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) @@ -2065,6 +2145,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); @@ -2092,6 +2173,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; @@ -2174,6 +2261,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); @@ -2218,6 +2306,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; @@ -2242,6 +2341,18 @@ static int __devinit smsc911x_drv_probe(struct platform_device *pdev) if (pdata->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; @@ -2334,6 +2445,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 a2fbff675edf554ea332c0f799c5b62ea6243182 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 1ad15367dca..dc69b4eaf80 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -95,10 +95,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 d784e1f315289ce71915ee7d33a1efd40112b886 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 dc69b4eaf80..560ad8c7f9c 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -154,7 +154,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 @@ -185,9 +184,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 2296643960e88f42e11162af25316dcf910bc827 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 560ad8c7f9c..0eadd9445ec 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -95,10 +95,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 @@ -215,6 +213,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 3b835add9e0435a95a8074fa0cb4e92077a07ede 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 0eadd9445ec..dfc80ea1928 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 176d40b0b30df42202cd66a18331376e0e6b8583 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 dfc80ea1928..61234a8a864 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -215,11 +215,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 da915695caa7839eb17c497e88a1af82f067cab7 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 c928e44c79b23259e3f291ba1e82426f41402c4c 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 b521ba5dc0a1348d14a0692ac0f0bb7d3f851f38 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 750822a0588a8849ba381f2271ff6f89330ec41d 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 059919fb07fc63d4617193b3639eac383c054226 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 44b77e21350e50d4a3feadff5ea4e098e7660688 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 56e6fb8115e5b2ee09fdd24d6b871b7decced88f 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 78ebd0e6caffc301312e9e5acd86b3f17fca45dc 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 f45aa0101d3b7c0a893202b842ffc6688f002f9e 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 6c62b868289..42e3b122c6a 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -628,7 +628,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 86f3b0a33856347d41aa2a406b397260a5649fdb 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 bb526bafa3586dc96e751deb6a1ea41936162a90 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 40046116af520062faa0a75e0779f891173bcae8 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 61234a8a864..e1c34f89ff3 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -238,7 +238,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 e8ce813736bf004fafc3fc3ccb7543591278bfc3 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 b2e8bcbdf80..c0f36e2389c 100644 --- a/drivers/mfd/ab5500-core.c +++ b/drivers/mfd/ab5500-core.c @@ -1008,7 +1008,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", @@ -1058,18 +1058,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 31faf55cc6faafa4f15c6da28782d9db1f0fc6b6 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 e1c34f89ff3..48cea965fab 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -176,6 +176,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 9371fc2c2b6645c723a39916069966618fdc0471 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 48cea965fab..edbcb5417d4 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 348e543b28039f9f5e4396a2cd275666d4ab5a3f 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 edbcb5417d4..48cea965fab 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 903b2d2e3cd721f5599986c3fc49ce31f0f11248 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 2218c2a3d8f..557078c2ec4 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -2022,6 +2022,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 516666f7683..f499cdea584 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -118,6 +118,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 ec7f6d0e663..feec0e3689c 100644 --- a/kernel/kexec.c +++ b/kernel/kexec.c @@ -1084,6 +1084,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. @@ -1094,7 +1095,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); @@ -1103,6 +1103,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 b9ad5e1824ee426cf7b60c2bd8a6924b92b1764d 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 875e2d93f7c118271b2d3f6cbbea968b7c7130ee 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 48cea965fab..096773eb22b 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -110,6 +110,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 5021a2d6c7febe3775ecdd14ebcd308032c6e723 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 096773eb22b..bb303fc95b5 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 4ac6af5e8c1817a408871a851147a4f166ec137f 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 bb303fc95b5..e4ea7ac9048 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -187,6 +187,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 82e5ed65553ead726f089111d098bbfe8d6f65dd 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 efa1a56ff560ba26584cb663f51b6e5ed5dd0e6f 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 e4ea7ac9048..38dee6be5ef 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -63,6 +63,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 fac24828f04594ac22d4d9676ca4762b77074b6c 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 38dee6be5ef..bee8112a57b 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -116,8 +116,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 1649d53900b4dc41a2c8541ea155ffc31a0b8bc9 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 36e5f07adf8408d41ac16c6494f4531e109ddcf1 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 de19d1564fcea870ffa9104dcab5af63bf79378d 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 fff6e119ea8c1b7983141cdc9a549a289cf8b759 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 bee8112a57b..cf9cc24a501 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 @@ -62,11 +77,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 @@ -76,6 +96,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 @@ -98,6 +119,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 @@ -133,6 +166,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 @@ -171,8 +219,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 @@ -194,6 +246,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 9bf97eb24a95b1af69beb3143658b2d112684477 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 9b60a48f910cb6f7ec1a989dd6e57e6d10c7d36a 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 8c58389f695..0e79fe2d214 100644 --- a/drivers/mfd/tc3589x.c +++ b/drivers/mfd/tc3589x.c @@ -453,6 +453,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; @@ -492,6 +494,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 e1194c917b91a5488e7324321aca105d2d8adf94 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 cf9cc24a501..24b315e989d 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 @@ -245,6 +248,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 ef7be08058a326e5f2f9283bd10d379e844a703c 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 24b315e989d..732e0bdaee5 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -253,6 +253,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 92304e19965fb851d4f159695408fe983205661c 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 2dc19c2145899f5ca6cb52ec639cb2a34ac8d651 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 732e0bdaee5..9db55e55793 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -234,6 +234,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 97cb64afb8601ce43f0d78a1c084f5df57e75118 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 8efab39bf0efdff68ff280fbec8b00a2769d6ad5 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 dafc6227c0d..1367dbd0f82 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 2c018c5f08776f0a703e594c96581bc672ee2ac0 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 9db55e55793..3b649a1eecf 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 0276382ffc978d87f379349bed99d36678cb6291 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 3b649a1eecf..aa216b7016e 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 483976e3e1bf1e0de2047ac895fbc9997f5dd37e 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 4af85a1f28be07f7f8c770f4fac4904cc1cf1200 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 aa216b7016e..49d32f4d99f 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -197,6 +197,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 66eafd6ffd8607346d32c4afcb761c3c364142a0 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 49d32f4d99f..e98e2dc3fb3 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 8646e6417bc87ef9501e208e6ce1c05806d7d688 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 e98e2dc3fb3..681941d402d 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 @@ -84,16 +63,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 @@ -103,7 +76,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 @@ -126,18 +98,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 @@ -153,9 +114,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 @@ -170,24 +131,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 @@ -228,42 +176,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 1fec10e57385f28239fad26dd21c58228fa39d65 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 681941d402d..491b7b932cc 100755 --- a/arch/arm/configs/u8500_defconfig +++ b/arch/arm/configs/u8500_defconfig @@ -213,17 +213,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 e3a53460d2f04b930a5d7ed82115b37b0f0cf041 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 f7c543a801d..7b35e039a26 100644 --- a/kernel/irq/chip.c +++ b/kernel/irq/chip.c @@ -262,7 +262,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(); @@ -277,7 +277,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 From aadbc0e168063794209455b52d020d5c943c84f6 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 29 Nov 2011 11:23:53 +0100 Subject: mfd: ab550-power: Include module.h after 3.2 update Signed-off-by: Philippe Langlais --- drivers/mfd/ab5500-power.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/ab5500-power.c b/drivers/mfd/ab5500-power.c index a549b3e7538..9474c32809b 100644 --- a/drivers/mfd/ab5500-power.c +++ b/drivers/mfd/ab5500-power.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include -- cgit v1.2.3 From 664b261057869dedc9a9c83fc709936657501614 Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 29 Nov 2011 11:24:40 +0100 Subject: gpadc.h regression after 3.2 update to merge somewhere --- include/linux/mfd/ab8500/gpadc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/mfd/ab8500/gpadc.h b/include/linux/mfd/ab8500/gpadc.h index 252966769d9..fa706c5a04a 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 channel); int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel); int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, -- cgit v1.2.3