aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/char/mbcs.c5
-rw-r--r--drivers/cpufreq/cpufreq.c305
-rw-r--r--drivers/cpufreq/cpufreq_ondemand.c139
-rw-r--r--drivers/gpio/Kconfig7
-rw-r--r--drivers/gpio/Makefile1
-rw-r--r--drivers/gpio/wm831x-gpio.c252
-rw-r--r--drivers/hwmon/Kconfig21
-rw-r--r--drivers/hwmon/Makefile2
-rw-r--r--drivers/hwmon/wm831x-hwmon.c226
-rw-r--r--drivers/hwmon/wm8350-hwmon.c151
-rw-r--r--drivers/i2c/busses/i2c-sh_mobile.c39
-rw-r--r--drivers/input/keyboard/omap-keypad.c22
-rw-r--r--drivers/input/keyboard/sh_keysc.c3
-rw-r--r--drivers/input/misc/Kconfig20
-rw-r--r--drivers/input/misc/Makefile3
-rw-r--r--drivers/input/misc/pcap_keys.c144
-rw-r--r--drivers/input/misc/wm831x-on.c163
-rw-r--r--drivers/input/touchscreen/Kconfig9
-rw-r--r--drivers/input/touchscreen/Makefile1
-rw-r--r--drivers/input/touchscreen/pcap_ts.c271
-rw-r--r--drivers/media/video/sh_mobile_ceu_camera.c41
-rw-r--r--drivers/mfd/Kconfig42
-rw-r--r--drivers/mfd/Makefile6
-rw-r--r--drivers/mfd/ab3100-core.c62
-rw-r--r--drivers/mfd/ab3100-otp.c268
-rw-r--r--drivers/mfd/dm355evm_msp.c12
-rw-r--r--drivers/mfd/ezx-pcap.c105
-rw-r--r--drivers/mfd/mc13783-core.c427
-rw-r--r--drivers/mfd/mfd-core.c2
-rw-r--r--drivers/mfd/pcf50633-adc.c32
-rw-r--r--drivers/mfd/pcf50633-core.c5
-rw-r--r--drivers/mfd/twl4030-core.c23
-rw-r--r--drivers/mfd/twl4030-irq.c2
-rw-r--r--drivers/mfd/twl4030-power.c472
-rw-r--r--drivers/mfd/wm831x-core.c1549
-rw-r--r--drivers/mfd/wm831x-irq.c559
-rw-r--r--drivers/mfd/wm831x-otp.c83
-rw-r--r--drivers/mfd/wm8350-core.c27
-rw-r--r--drivers/misc/sgi-xp/xpc_sn2.c40
-rw-r--r--drivers/mtd/nand/ams-delta.c8
-rw-r--r--drivers/regulator/Kconfig31
-rw-r--r--drivers/regulator/Makefile6
-rw-r--r--drivers/regulator/ab3100.c700
-rw-r--r--drivers/regulator/core.c24
-rw-r--r--drivers/regulator/mc13783.c410
-rw-r--r--drivers/regulator/pcap-regulator.c318
-rw-r--r--drivers/regulator/wm831x-dcdc.c862
-rw-r--r--drivers/regulator/wm831x-isink.c260
-rw-r--r--drivers/regulator/wm831x-ldo.c852
-rw-r--r--drivers/rtc/Kconfig19
-rw-r--r--drivers/rtc/Makefile2
-rw-r--r--drivers/rtc/rtc-ab3100.c281
-rw-r--r--drivers/rtc/rtc-ds1302.c69
-rw-r--r--drivers/rtc/rtc-sh.c97
-rw-r--r--drivers/rtc/rtc-wm831x.c523
-rw-r--r--drivers/serial/ioc4_serial.c2
-rw-r--r--drivers/serial/sh-sci.c8
-rw-r--r--drivers/serial/sh-sci.h17
-rw-r--r--drivers/sh/intc.c71
-rw-r--r--drivers/uio/uio_pdrv_genirq.c54
-rw-r--r--drivers/usb/gadget/Kconfig28
-rw-r--r--drivers/usb/gadget/Makefile1
-rw-r--r--drivers/usb/gadget/gadget_chips.h8
-rw-r--r--drivers/usb/gadget/m66592-udc.c286
-rw-r--r--drivers/usb/gadget/m66592-udc.h90
-rw-r--r--drivers/usb/gadget/r8a66597-udc.c1689
-rw-r--r--drivers/usb/gadget/r8a66597-udc.h256
-rw-r--r--drivers/usb/host/Kconfig7
-rw-r--r--drivers/usb/host/r8a66597-hcd.c210
-rw-r--r--drivers/usb/host/r8a66597.h440
-rw-r--r--drivers/video/Kconfig2
-rw-r--r--drivers/video/omap/dispc.c6
-rw-r--r--drivers/video/sh_mobile_lcdcfb.c292
-rw-r--r--drivers/watchdog/Kconfig38
-rw-r--r--drivers/watchdog/Makefile3
-rw-r--r--drivers/watchdog/ar7_wdt.c107
-rw-r--r--drivers/watchdog/booke_wdt.c57
-rw-r--r--drivers/watchdog/coh901327_wdt.c2
-rw-r--r--drivers/watchdog/davinci_wdt.c19
-rw-r--r--drivers/watchdog/iop_wdt.c2
-rw-r--r--drivers/watchdog/nuc900_wdt.c353
-rw-r--r--drivers/watchdog/rm9k_wdt.c2
-rw-r--r--drivers/watchdog/sbc_fitpc2_wdt.c267
-rw-r--r--drivers/watchdog/sc1200wdt.c2
-rw-r--r--drivers/watchdog/wdt_pci.c21
-rw-r--r--drivers/watchdog/wm831x_wdt.c441
86 files changed, 13498 insertions, 1286 deletions
diff --git a/drivers/char/mbcs.c b/drivers/char/mbcs.c
index acd8e9ed474..87c67b42bc0 100644
--- a/drivers/char/mbcs.c
+++ b/drivers/char/mbcs.c
@@ -15,6 +15,7 @@
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/ioport.h>
+#include <linux/kernel.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
@@ -715,8 +716,8 @@ static ssize_t show_algo(struct device *dev, struct device_attribute *attr, char
*/
debug0 = *(uint64_t *) soft->debug_addr;
- return sprintf(buf, "0x%lx 0x%lx\n",
- (debug0 >> 32), (debug0 & 0xffffffff));
+ return sprintf(buf, "0x%x 0x%x\n",
+ upper_32_bits(debug0), lower_32_bits(debug0));
}
static ssize_t store_algo(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index 2968ed6a9c4..3938c781709 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -61,6 +61,8 @@ static DEFINE_SPINLOCK(cpufreq_driver_lock);
* are concerned with are online after they get the lock.
* - Governor routines that can be called in cpufreq hotplug path should not
* take this sem as top level hotplug notifier handler takes this.
+ * - Lock should not be held across
+ * __cpufreq_governor(data, CPUFREQ_GOV_STOP);
*/
static DEFINE_PER_CPU(int, policy_cpu);
static DEFINE_PER_CPU(struct rw_semaphore, cpu_policy_rwsem);
@@ -686,6 +688,9 @@ static struct attribute *default_attrs[] = {
NULL
};
+struct kobject *cpufreq_global_kobject;
+EXPORT_SYMBOL(cpufreq_global_kobject);
+
#define to_policy(k) container_of(k, struct cpufreq_policy, kobj)
#define to_attr(a) container_of(a, struct freq_attr, attr)
@@ -756,92 +761,20 @@ static struct kobj_type ktype_cpufreq = {
.release = cpufreq_sysfs_release,
};
-
-/**
- * cpufreq_add_dev - add a CPU device
- *
- * Adds the cpufreq interface for a CPU device.
- *
- * The Oracle says: try running cpufreq registration/unregistration concurrently
- * with with cpu hotplugging and all hell will break loose. Tried to clean this
- * mess up, but more thorough testing is needed. - Mathieu
+/*
+ * Returns:
+ * Negative: Failure
+ * 0: Success
+ * Positive: When we have a managed CPU and the sysfs got symlinked
*/
-static int cpufreq_add_dev(struct sys_device *sys_dev)
+int cpufreq_add_dev_policy(unsigned int cpu, struct cpufreq_policy *policy,
+ struct sys_device *sys_dev)
{
- unsigned int cpu = sys_dev->id;
int ret = 0;
- struct cpufreq_policy new_policy;
- struct cpufreq_policy *policy;
- struct freq_attr **drv_attr;
- struct sys_device *cpu_sys_dev;
+#ifdef CONFIG_SMP
unsigned long flags;
unsigned int j;
- if (cpu_is_offline(cpu))
- return 0;
-
- cpufreq_debug_disable_ratelimit();
- dprintk("adding CPU %u\n", cpu);
-
-#ifdef CONFIG_SMP
- /* check whether a different CPU already registered this
- * CPU because it is in the same boat. */
- policy = cpufreq_cpu_get(cpu);
- if (unlikely(policy)) {
- cpufreq_cpu_put(policy);
- cpufreq_debug_enable_ratelimit();
- return 0;
- }
-#endif
-
- if (!try_module_get(cpufreq_driver->owner)) {
- ret = -EINVAL;
- goto module_out;
- }
-
- policy = kzalloc(sizeof(struct cpufreq_policy), GFP_KERNEL);
- if (!policy) {
- ret = -ENOMEM;
- goto nomem_out;
- }
- if (!alloc_cpumask_var(&policy->cpus, GFP_KERNEL)) {
- ret = -ENOMEM;
- goto err_free_policy;
- }
- if (!zalloc_cpumask_var(&policy->related_cpus, GFP_KERNEL)) {
- ret = -ENOMEM;
- goto err_free_cpumask;
- }
-
- policy->cpu = cpu;
- cpumask_copy(policy->cpus, cpumask_of(cpu));
-
- /* Initially set CPU itself as the policy_cpu */
- per_cpu(policy_cpu, cpu) = cpu;
- ret = (lock_policy_rwsem_write(cpu) < 0);
- WARN_ON(ret);
-
- init_completion(&policy->kobj_unregister);
- INIT_WORK(&policy->update, handle_update);
-
- /* Set governor before ->init, so that driver could check it */
- policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
- /* call driver. From then on the cpufreq must be able
- * to accept all calls to ->verify and ->setpolicy for this CPU
- */
- ret = cpufreq_driver->init(policy);
- if (ret) {
- dprintk("initialization failed\n");
- goto err_unlock_policy;
- }
- policy->user_policy.min = policy->min;
- policy->user_policy.max = policy->max;
-
- blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
- CPUFREQ_START, policy);
-
-#ifdef CONFIG_SMP
-
#ifdef CONFIG_HOTPLUG_CPU
if (per_cpu(cpufreq_cpu_governor, cpu)) {
policy->governor = per_cpu(cpufreq_cpu_governor, cpu);
@@ -872,9 +805,8 @@ static int cpufreq_add_dev(struct sys_device *sys_dev)
/* Should not go through policy unlock path */
if (cpufreq_driver->exit)
cpufreq_driver->exit(policy);
- ret = -EBUSY;
cpufreq_cpu_put(managed_policy);
- goto err_free_cpumask;
+ return -EBUSY;
}
spin_lock_irqsave(&cpufreq_driver_lock, flags);
@@ -893,17 +825,62 @@ static int cpufreq_add_dev(struct sys_device *sys_dev)
* Call driver->exit() because only the cpu parent of
* the kobj needed to call init().
*/
- goto out_driver_exit; /* call driver->exit() */
+ if (cpufreq_driver->exit)
+ cpufreq_driver->exit(policy);
+
+ if (!ret)
+ return 1;
+ else
+ return ret;
}
}
#endif
- memcpy(&new_policy, policy, sizeof(struct cpufreq_policy));
+ return ret;
+}
+
+
+/* symlink affected CPUs */
+int cpufreq_add_dev_symlink(unsigned int cpu, struct cpufreq_policy *policy)
+{
+ unsigned int j;
+ int ret = 0;
+
+ for_each_cpu(j, policy->cpus) {
+ struct cpufreq_policy *managed_policy;
+ struct sys_device *cpu_sys_dev;
+
+ if (j == cpu)
+ continue;
+ if (!cpu_online(j))
+ continue;
+
+ dprintk("CPU %u already managed, adding link\n", j);
+ managed_policy = cpufreq_cpu_get(cpu);
+ cpu_sys_dev = get_cpu_sysdev(j);
+ ret = sysfs_create_link(&cpu_sys_dev->kobj, &policy->kobj,
+ "cpufreq");
+ if (ret) {
+ cpufreq_cpu_put(managed_policy);
+ return ret;
+ }
+ }
+ return ret;
+}
+
+int cpufreq_add_dev_interface(unsigned int cpu, struct cpufreq_policy *policy,
+ struct sys_device *sys_dev)
+{
+ struct cpufreq_policy new_policy;
+ struct freq_attr **drv_attr;
+ unsigned long flags;
+ int ret = 0;
+ unsigned int j;
/* prepare interface data */
- ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq, &sys_dev->kobj,
- "cpufreq");
+ ret = kobject_init_and_add(&policy->kobj, &ktype_cpufreq,
+ &sys_dev->kobj, "cpufreq");
if (ret)
- goto out_driver_exit;
+ return ret;
/* set up files for this cpu device */
drv_attr = cpufreq_driver->attr;
@@ -926,35 +903,20 @@ static int cpufreq_add_dev(struct sys_device *sys_dev)
spin_lock_irqsave(&cpufreq_driver_lock, flags);
for_each_cpu(j, policy->cpus) {
- if (!cpu_online(j))
- continue;
+ if (!cpu_online(j))
+ continue;
per_cpu(cpufreq_cpu_data, j) = policy;
per_cpu(policy_cpu, j) = policy->cpu;
}
spin_unlock_irqrestore(&cpufreq_driver_lock, flags);
- /* symlink affected CPUs */
- for_each_cpu(j, policy->cpus) {
- struct cpufreq_policy *managed_policy;
-
- if (j == cpu)
- continue;
- if (!cpu_online(j))
- continue;
-
- dprintk("CPU %u already managed, adding link\n", j);
- managed_policy = cpufreq_cpu_get(cpu);
- cpu_sys_dev = get_cpu_sysdev(j);
- ret = sysfs_create_link(&cpu_sys_dev->kobj, &policy->kobj,
- "cpufreq");
- if (ret) {
- cpufreq_cpu_put(managed_policy);
- goto err_out_unregister;
- }
- }
+ ret = cpufreq_add_dev_symlink(cpu, policy);
+ if (ret)
+ goto err_out_kobj_put;
- policy->governor = NULL; /* to assure that the starting sequence is
- * run in cpufreq_set_policy */
+ memcpy(&new_policy, policy, sizeof(struct cpufreq_policy));
+ /* assure that the starting sequence is run in __cpufreq_set_policy */
+ policy->governor = NULL;
/* set default policy */
ret = __cpufreq_set_policy(policy, &new_policy);
@@ -963,8 +925,107 @@ static int cpufreq_add_dev(struct sys_device *sys_dev)
if (ret) {
dprintk("setting policy failed\n");
- goto err_out_unregister;
+ if (cpufreq_driver->exit)
+ cpufreq_driver->exit(policy);
+ }
+ return ret;
+
+err_out_kobj_put:
+ kobject_put(&policy->kobj);
+ wait_for_completion(&policy->kobj_unregister);
+ return ret;
+}
+
+
+/**
+ * cpufreq_add_dev - add a CPU device
+ *
+ * Adds the cpufreq interface for a CPU device.
+ *
+ * The Oracle says: try running cpufreq registration/unregistration concurrently
+ * with with cpu hotplugging and all hell will break loose. Tried to clean this
+ * mess up, but more thorough testing is needed. - Mathieu
+ */
+static int cpufreq_add_dev(struct sys_device *sys_dev)
+{
+ unsigned int cpu = sys_dev->id;
+ int ret = 0;
+ struct cpufreq_policy *policy;
+ unsigned long flags;
+ unsigned int j;
+
+ if (cpu_is_offline(cpu))
+ return 0;
+
+ cpufreq_debug_disable_ratelimit();
+ dprintk("adding CPU %u\n", cpu);
+
+#ifdef CONFIG_SMP
+ /* check whether a different CPU already registered this
+ * CPU because it is in the same boat. */
+ policy = cpufreq_cpu_get(cpu);
+ if (unlikely(policy)) {
+ cpufreq_cpu_put(policy);
+ cpufreq_debug_enable_ratelimit();
+ return 0;
+ }
+#endif
+
+ if (!try_module_get(cpufreq_driver->owner)) {
+ ret = -EINVAL;
+ goto module_out;
+ }
+
+ ret = -ENOMEM;
+ policy = kzalloc(sizeof(struct cpufreq_policy), GFP_KERNEL);
+ if (!policy)
+ goto nomem_out;
+
+ if (!alloc_cpumask_var(&policy->cpus, GFP_KERNEL))
+ goto err_free_policy;
+
+ if (!zalloc_cpumask_var(&policy->related_cpus, GFP_KERNEL))
+ goto err_free_cpumask;
+
+ policy->cpu = cpu;
+ cpumask_copy(policy->cpus, cpumask_of(cpu));
+
+ /* Initially set CPU itself as the policy_cpu */
+ per_cpu(policy_cpu, cpu) = cpu;
+ ret = (lock_policy_rwsem_write(cpu) < 0);
+ WARN_ON(ret);
+
+ init_completion(&policy->kobj_unregister);
+ INIT_WORK(&policy->update, handle_update);
+
+ /* Set governor before ->init, so that driver could check it */
+ policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
+ /* call driver. From then on the cpufreq must be able
+ * to accept all calls to ->verify and ->setpolicy for this CPU
+ */
+ ret = cpufreq_driver->init(policy);
+ if (ret) {
+ dprintk("initialization failed\n");
+ goto err_unlock_policy;
}
+ policy->user_policy.min = policy->min;
+ policy->user_policy.max = policy->max;
+
+ blocking_notifier_call_chain(&cpufreq_policy_notifier_list,
+ CPUFREQ_START, policy);
+
+ ret = cpufreq_add_dev_policy(cpu, policy, sys_dev);
+ if (ret) {
+ if (ret > 0)
+ /* This is a managed cpu, symlink created,
+ exit with 0 */
+ ret = 0;
+ goto err_unlock_policy;
+ }
+
+ ret = cpufreq_add_dev_interface(cpu, policy, sys_dev);
+ if (ret)
+ goto err_out_unregister;
unlock_policy_rwsem_write(cpu);
@@ -982,14 +1043,9 @@ err_out_unregister:
per_cpu(cpufreq_cpu_data, j) = NULL;
spin_unlock_irqrestore(&cpufreq_driver_lock, flags);
-err_out_kobj_put:
kobject_put(&policy->kobj);
wait_for_completion(&policy->kobj_unregister);
-out_driver_exit:
- if (cpufreq_driver->exit)
- cpufreq_driver->exit(policy);
-
err_unlock_policy:
unlock_policy_rwsem_write(cpu);
err_free_cpumask:
@@ -1653,8 +1709,17 @@ static int __cpufreq_set_policy(struct cpufreq_policy *data,
dprintk("governor switch\n");
/* end old governor */
- if (data->governor)
+ if (data->governor) {
+ /*
+ * Need to release the rwsem around governor
+ * stop due to lock dependency between
+ * cancel_delayed_work_sync and the read lock
+ * taken in the delayed work handler.
+ */
+ unlock_policy_rwsem_write(data->cpu);
__cpufreq_governor(data, CPUFREQ_GOV_STOP);
+ lock_policy_rwsem_write(data->cpu);
+ }
/* start new governor */
data->governor = policy->governor;
@@ -1884,7 +1949,11 @@ static int __init cpufreq_core_init(void)
per_cpu(policy_cpu, cpu) = -1;
init_rwsem(&per_cpu(cpu_policy_rwsem, cpu));
}
+
+ cpufreq_global_kobject = kobject_create_and_add("cpufreq",
+ &cpu_sysdev_class.kset.kobj);
+ BUG_ON(!cpufreq_global_kobject);
+
return 0;
}
-
core_initcall(cpufreq_core_init);
diff --git a/drivers/cpufreq/cpufreq_ondemand.c b/drivers/cpufreq/cpufreq_ondemand.c
index d7a528c80de..071699de50e 100644
--- a/drivers/cpufreq/cpufreq_ondemand.c
+++ b/drivers/cpufreq/cpufreq_ondemand.c
@@ -55,6 +55,18 @@ static unsigned int min_sampling_rate;
#define TRANSITION_LATENCY_LIMIT (10 * 1000 * 1000)
static void do_dbs_timer(struct work_struct *work);
+static int cpufreq_governor_dbs(struct cpufreq_policy *policy,
+ unsigned int event);
+
+#ifndef CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND
+static
+#endif
+struct cpufreq_governor cpufreq_gov_ondemand = {
+ .name = "ondemand",
+ .governor = cpufreq_governor_dbs,
+ .max_transition_latency = TRANSITION_LATENCY_LIMIT,
+ .owner = THIS_MODULE,
+};
/* Sampling types */
enum {DBS_NORMAL_SAMPLE, DBS_SUB_SAMPLE};
@@ -207,20 +219,23 @@ static void ondemand_powersave_bias_init(void)
}
/************************** sysfs interface ************************/
-static ssize_t show_sampling_rate_max(struct cpufreq_policy *policy, char *buf)
+
+static ssize_t show_sampling_rate_max(struct kobject *kobj,
+ struct attribute *attr, char *buf)
{
printk_once(KERN_INFO "CPUFREQ: ondemand sampling_rate_max "
"sysfs file is deprecated - used by: %s\n", current->comm);
return sprintf(buf, "%u\n", -1U);
}
-static ssize_t show_sampling_rate_min(struct cpufreq_policy *policy, char *buf)
+static ssize_t show_sampling_rate_min(struct kobject *kobj,
+ struct attribute *attr, char *buf)
{
return sprintf(buf, "%u\n", min_sampling_rate);
}
#define define_one_ro(_name) \
-static struct freq_attr _name = \
+static struct global_attr _name = \
__ATTR(_name, 0444, show_##_name, NULL)
define_one_ro(sampling_rate_max);
@@ -229,7 +244,7 @@ define_one_ro(sampling_rate_min);
/* cpufreq_ondemand Governor Tunables */
#define show_one(file_name, object) \
static ssize_t show_##file_name \
-(struct cpufreq_policy *unused, char *buf) \
+(struct kobject *kobj, struct attribute *attr, char *buf) \
{ \
return sprintf(buf, "%u\n", dbs_tuners_ins.object); \
}
@@ -238,8 +253,38 @@ show_one(up_threshold, up_threshold);
show_one(ignore_nice_load, ignore_nice);
show_one(powersave_bias, powersave_bias);
-static ssize_t store_sampling_rate(struct cpufreq_policy *unused,
- const char *buf, size_t count)
+/*** delete after deprecation time ***/
+
+#define DEPRECATION_MSG(file_name) \
+ printk_once(KERN_INFO "CPUFREQ: Per core ondemand sysfs " \
+ "interface is deprecated - " #file_name "\n");
+
+#define show_one_old(file_name) \
+static ssize_t show_##file_name##_old \
+(struct cpufreq_policy *unused, char *buf) \
+{ \
+ printk_once(KERN_INFO "CPUFREQ: Per core ondemand sysfs " \
+ "interface is deprecated - " #file_name "\n"); \
+ return show_##file_name(NULL, NULL, buf); \
+}
+show_one_old(sampling_rate);
+show_one_old(up_threshold);
+show_one_old(ignore_nice_load);
+show_one_old(powersave_bias);
+show_one_old(sampling_rate_min);
+show_one_old(sampling_rate_max);
+
+#define define_one_ro_old(object, _name) \
+static struct freq_attr object = \
+__ATTR(_name, 0444, show_##_name##_old, NULL)
+
+define_one_ro_old(sampling_rate_min_old, sampling_rate_min);
+define_one_ro_old(sampling_rate_max_old, sampling_rate_max);
+
+/*** delete after deprecation time ***/
+
+static ssize_t store_sampling_rate(struct kobject *a, struct attribute *b,
+ const char *buf, size_t count)
{
unsigned int input;
int ret;
@@ -254,8 +299,8 @@ static ssize_t store_sampling_rate(struct cpufreq_policy *unused,
return count;
}
-static ssize_t store_up_threshold(struct cpufreq_policy *unused,
- const char *buf, size_t count)
+static ssize_t store_up_threshold(struct kobject *a, struct attribute *b,
+ const char *buf, size_t count)
{
unsigned int input;
int ret;
@@ -273,8 +318,8 @@ static ssize_t store_up_threshold(struct cpufreq_policy *unused,
return count;
}
-static ssize_t store_ignore_nice_load(struct cpufreq_policy *policy,
- const char *buf, size_t count)
+static ssize_t store_ignore_nice_load(struct kobject *a, struct attribute *b,
+ const char *buf, size_t count)
{
unsigned int input;
int ret;
@@ -310,8 +355,8 @@ static ssize_t store_ignore_nice_load(struct cpufreq_policy *policy,
return count;
}
-static ssize_t store_powersave_bias(struct cpufreq_policy *unused,
- const char *buf, size_t count)
+static ssize_t store_powersave_bias(struct kobject *a, struct attribute *b,
+ const char *buf, size_t count)
{
unsigned int input;
int ret;
@@ -332,7 +377,7 @@ static ssize_t store_powersave_bias(struct cpufreq_policy *unused,
}
#define define_one_rw(_name) \
-static struct freq_attr _name = \
+static struct global_attr _name = \
__ATTR(_name, 0644, show_##_name, store_##_name)
define_one_rw(sampling_rate);
@@ -355,6 +400,47 @@ static struct attribute_group dbs_attr_group = {
.name = "ondemand",
};
+/*** delete after deprecation time ***/
+
+#define write_one_old(file_name) \
+static ssize_t store_##file_name##_old \
+(struct cpufreq_policy *unused, const char *buf, size_t count) \
+{ \
+ printk_once(KERN_INFO "CPUFREQ: Per core ondemand sysfs " \
+ "interface is deprecated - " #file_name "\n"); \
+ return store_##file_name(NULL, NULL, buf, count); \
+}
+write_one_old(sampling_rate);
+write_one_old(up_threshold);
+write_one_old(ignore_nice_load);
+write_one_old(powersave_bias);
+
+#define define_one_rw_old(object, _name) \
+static struct freq_attr object = \
+__ATTR(_name, 0644, show_##_name##_old, store_##_name##_old)
+
+define_one_rw_old(sampling_rate_old, sampling_rate);
+define_one_rw_old(up_threshold_old, up_threshold);
+define_one_rw_old(ignore_nice_load_old, ignore_nice_load);
+define_one_rw_old(powersave_bias_old, powersave_bias);
+
+static struct attribute *dbs_attributes_old[] = {
+ &sampling_rate_max_old.attr,
+ &sampling_rate_min_old.attr,
+ &sampling_rate_old.attr,
+ &up_threshold_old.attr,
+ &ignore_nice_load_old.attr,
+ &powersave_bias_old.attr,
+ NULL
+};
+
+static struct attribute_group dbs_attr_group_old = {
+ .attrs = dbs_attributes_old,
+ .name = "ondemand",
+};
+
+/*** delete after deprecation time ***/
+
/************************** sysfs end ************************/
static void dbs_check_cpu(struct cpu_dbs_info_s *this_dbs_info)
@@ -545,7 +631,7 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy,
mutex_lock(&dbs_mutex);
- rc = sysfs_create_group(&policy->kobj, &dbs_attr_group);
+ rc = sysfs_create_group(&policy->kobj, &dbs_attr_group_old);
if (rc) {
mutex_unlock(&dbs_mutex);
return rc;
@@ -566,13 +652,20 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy,
}
this_dbs_info->cpu = cpu;
ondemand_powersave_bias_init_cpu(cpu);
- mutex_init(&this_dbs_info->timer_mutex);
/*
* Start the timerschedule work, when this governor
* is used for first time
*/
if (dbs_enable == 1) {
unsigned int latency;
+
+ rc = sysfs_create_group(cpufreq_global_kobject,
+ &dbs_attr_group);
+ if (rc) {
+ mutex_unlock(&dbs_mutex);
+ return rc;
+ }
+
/* policy latency is in nS. Convert it to uS first */
latency = policy->cpuinfo.transition_latency / 1000;
if (latency == 0)
@@ -586,6 +679,7 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy,
}
mutex_unlock(&dbs_mutex);
+ mutex_init(&this_dbs_info->timer_mutex);
dbs_timer_init(this_dbs_info);
break;
@@ -593,10 +687,13 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy,
dbs_timer_exit(this_dbs_info);
mutex_lock(&dbs_mutex);
- sysfs_remove_group(&policy->kobj, &dbs_attr_group);
+ sysfs_remove_group(&policy->kobj, &dbs_attr_group_old);
mutex_destroy(&this_dbs_info->timer_mutex);
dbs_enable--;
mutex_unlock(&dbs_mutex);
+ if (!dbs_enable)
+ sysfs_remove_group(cpufreq_global_kobject,
+ &dbs_attr_group);
break;
@@ -614,16 +711,6 @@ static int cpufreq_governor_dbs(struct cpufreq_policy *policy,
return 0;
}
-#ifndef CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND
-static
-#endif
-struct cpufreq_governor cpufreq_gov_ondemand = {
- .name = "ondemand",
- .governor = cpufreq_governor_dbs,
- .max_transition_latency = TRANSITION_LATENCY_LIMIT,
- .owner = THIS_MODULE,
-};
-
static int __init cpufreq_gov_dbs_init(void)
{
int err;
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 96dda81c922..6b4c484a699 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -155,6 +155,13 @@ config GPIO_TWL4030
Say yes here to access the GPIO signals of various multi-function
power management chips from Texas Instruments.
+config GPIO_WM831X
+ tristate "WM831x GPIOs"
+ depends on MFD_WM831X
+ help
+ Say yes here to access the GPIO signals of WM831x power management
+ chips from Wolfson Microelectronics.
+
comment "PCI GPIO expanders:"
config GPIO_BT8XX
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 9244c6fcd8b..ea7c745f26a 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_GPIO_TWL4030) += twl4030-gpio.o
obj-$(CONFIG_GPIO_XILINX) += xilinx_gpio.o
obj-$(CONFIG_GPIO_BT8XX) += bt8xxgpio.o
obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
+obj-$(CONFIG_GPIO_WM831X) += wm831x-gpio.o
diff --git a/drivers/gpio/wm831x-gpio.c b/drivers/gpio/wm831x-gpio.c
new file mode 100644
index 00000000000..f9c09a54ec7
--- /dev/null
+++ b/drivers/gpio/wm831x-gpio.c
@@ -0,0 +1,252 @@
+/*
+ * wm831x-gpio.c -- gpiolib support for Wolfson WM831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/gpio.h>
+#include <linux/mfd/core.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/mfd/wm831x/gpio.h>
+
+#define WM831X_GPIO_MAX 16
+
+struct wm831x_gpio {
+ struct wm831x *wm831x;
+ struct gpio_chip gpio_chip;
+};
+
+static inline struct wm831x_gpio *to_wm831x_gpio(struct gpio_chip *chip)
+{
+ return container_of(chip, struct wm831x_gpio, gpio_chip);
+}
+
+static int wm831x_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+
+ return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset,
+ WM831X_GPN_DIR | WM831X_GPN_TRI,
+ WM831X_GPN_DIR);
+}
+
+static int wm831x_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_GPIO_LEVEL);
+ if (ret < 0)
+ return ret;
+
+ if (ret & 1 << offset)
+ return 1;
+ else
+ return 0;
+}
+
+static int wm831x_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+
+ return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset,
+ WM831X_GPN_DIR | WM831X_GPN_TRI, 0);
+}
+
+static void wm831x_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+
+ wm831x_set_bits(wm831x, WM831X_GPIO_LEVEL, 1 << offset,
+ value << offset);
+}
+
+#ifdef CONFIG_DEBUG_FS
+static void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
+{
+ struct wm831x_gpio *wm831x_gpio = to_wm831x_gpio(chip);
+ struct wm831x *wm831x = wm831x_gpio->wm831x;
+ int i;
+
+ for (i = 0; i < chip->ngpio; i++) {
+ int gpio = i + chip->base;
+ int reg;
+ const char *label, *pull, *powerdomain;
+
+ /* We report the GPIO even if it's not requested since
+ * we're also reporting things like alternate
+ * functions which apply even when the GPIO is not in
+ * use as a GPIO.
+ */
+ label = gpiochip_is_requested(chip, i);
+ if (!label)
+ label = "Unrequested";
+
+ seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label);
+
+ reg = wm831x_reg_read(wm831x, WM831X_GPIO1_CONTROL + i);
+ if (reg < 0) {
+ dev_err(wm831x->dev,
+ "GPIO control %d read failed: %d\n",
+ gpio, reg);
+ seq_printf(s, "\n");
+ continue;
+ }
+
+ switch (reg & WM831X_GPN_PULL_MASK) {
+ case WM831X_GPIO_PULL_NONE:
+ pull = "nopull";
+ break;
+ case WM831X_GPIO_PULL_DOWN:
+ pull = "pulldown";
+ break;
+ case WM831X_GPIO_PULL_UP:
+ pull = "pullup";
+ default:
+ pull = "INVALID PULL";
+ break;
+ }
+
+ switch (i + 1) {
+ case 1 ... 3:
+ case 7 ... 9:
+ if (reg & WM831X_GPN_PWR_DOM)
+ powerdomain = "VPMIC";
+ else
+ powerdomain = "DBVDD";
+ break;
+
+ case 4 ... 6:
+ case 10 ... 12:
+ if (reg & WM831X_GPN_PWR_DOM)
+ powerdomain = "SYSVDD";
+ else
+ powerdomain = "DBVDD";
+ break;
+
+ case 13 ... 16:
+ powerdomain = "TPVDD";
+ break;
+
+ default:
+ BUG();
+ break;
+ }
+
+ seq_printf(s, " %s %s %s %s%s\n"
+ " %s%s (0x%4x)\n",
+ reg & WM831X_GPN_DIR ? "in" : "out",
+ wm831x_gpio_get(chip, i) ? "high" : "low",
+ pull,
+ powerdomain,
+ reg & WM831X_GPN_POL ? " inverted" : "",
+ reg & WM831X_GPN_OD ? "open-drain" : "CMOS",
+ reg & WM831X_GPN_TRI ? " tristated" : "",
+ reg);
+ }
+}
+#else
+#define wm831x_gpio_dbg_show NULL
+#endif
+
+static struct gpio_chip template_chip = {
+ .label = "wm831x",
+ .owner = THIS_MODULE,
+ .direction_input = wm831x_gpio_direction_in,
+ .get = wm831x_gpio_get,
+ .direction_output = wm831x_gpio_direction_out,
+ .set = wm831x_gpio_set,
+ .dbg_show = wm831x_gpio_dbg_show,
+ .can_sleep = 1,
+};
+
+static int __devinit wm831x_gpio_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *pdata = wm831x->dev->platform_data;
+ struct wm831x_gpio *wm831x_gpio;
+ int ret;
+
+ wm831x_gpio = kzalloc(sizeof(*wm831x_gpio), GFP_KERNEL);
+ if (wm831x_gpio == NULL)
+ return -ENOMEM;
+
+ wm831x_gpio->wm831x = wm831x;
+ wm831x_gpio->gpio_chip = template_chip;
+ wm831x_gpio->gpio_chip.ngpio = WM831X_GPIO_MAX;
+ wm831x_gpio->gpio_chip.dev = &pdev->dev;
+ if (pdata && pdata->gpio_base)
+ wm831x_gpio->gpio_chip.base = pdata->gpio_base;
+ else
+ wm831x_gpio->gpio_chip.base = -1;
+
+ ret = gpiochip_add(&wm831x_gpio->gpio_chip);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Could not register gpiochip, %d\n",
+ ret);
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, wm831x_gpio);
+
+ return ret;
+
+err:
+ kfree(wm831x_gpio);
+ return ret;
+}
+
+static int __devexit wm831x_gpio_remove(struct platform_device *pdev)
+{
+ struct wm831x_gpio *wm831x_gpio = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = gpiochip_remove(&wm831x_gpio->gpio_chip);
+ if (ret == 0)
+ kfree(wm831x_gpio);
+
+ return ret;
+}
+
+static struct platform_driver wm831x_gpio_driver = {
+ .driver.name = "wm831x-gpio",
+ .driver.owner = THIS_MODULE,
+ .probe = wm831x_gpio_probe,
+ .remove = __devexit_p(wm831x_gpio_remove),
+};
+
+static int __init wm831x_gpio_init(void)
+{
+ return platform_driver_register(&wm831x_gpio_driver);
+}
+subsys_initcall(wm831x_gpio_init);
+
+static void __exit wm831x_gpio_exit(void)
+{
+ platform_driver_unregister(&wm831x_gpio_driver);
+}
+module_exit(wm831x_gpio_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("GPIO interface for WM831x PMICs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-gpio");
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 461abb1e273..ed7711d11ae 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -946,6 +946,27 @@ config SENSORS_W83627EHF
This driver can also be built as a module. If so, the module
will be called w83627ehf.
+config SENSORS_WM831X
+ tristate "WM831x PMICs"
+ depends on MFD_WM831X
+ help
+ If you say yes here you get support for the hardware
+ monitoring functionality of the Wolfson Microelectronics
+ WM831x series of PMICs.
+
+ This driver can also be built as a module. If so, the module
+ will be called wm831x-hwmon.
+
+config SENSORS_WM8350
+ tristate "Wolfson Microelectronics WM835x"
+ depends on MFD_WM8350
+ help
+ If you say yes here you get support for the hardware
+ monitoring features of the WM835x series of PMICs.
+
+ This driver can also be built as a module. If so, the module
+ will be called wm8350-hwmon.
+
config SENSORS_ULTRA45
tristate "Sun Ultra45 PIC16F747"
depends on SPARC64
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 2e547881bc0..bcf73a9bb61 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -93,6 +93,8 @@ obj-$(CONFIG_SENSORS_VT8231) += vt8231.o
obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o
obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o
obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o
+obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o
+obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o
ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y)
EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/hwmon/wm831x-hwmon.c b/drivers/hwmon/wm831x-hwmon.c
new file mode 100644
index 00000000000..c16e9e74c35
--- /dev/null
+++ b/drivers/hwmon/wm831x-hwmon.c
@@ -0,0 +1,226 @@
+/*
+ * drivers/hwmon/wm831x-hwmon.c - Wolfson Microelectronics WM831x PMIC
+ * hardware monitoring features.
+ *
+ * Copyright (C) 2009 Wolfson Microelectronics plc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License v2 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/auxadc.h>
+
+struct wm831x_hwmon {
+ struct wm831x *wm831x;
+ struct device *classdev;
+};
+
+static ssize_t show_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "wm831x\n");
+}
+
+static const char *input_names[] = {
+ [WM831X_AUX_SYSVDD] = "SYSVDD",
+ [WM831X_AUX_USB] = "USB",
+ [WM831X_AUX_BKUP_BATT] = "Backup battery",
+ [WM831X_AUX_BATT] = "Battery",
+ [WM831X_AUX_WALL] = "WALL",
+ [WM831X_AUX_CHIP_TEMP] = "PMIC",
+ [WM831X_AUX_BATT_TEMP] = "Battery",
+};
+
+
+static ssize_t show_voltage(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wm831x_hwmon *hwmon = dev_get_drvdata(dev);
+ int channel = to_sensor_dev_attr(attr)->index;
+ int ret;
+
+ ret = wm831x_auxadc_read_uv(hwmon->wm831x, channel);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", DIV_ROUND_CLOSEST(ret, 1000));
+}
+
+static ssize_t show_chip_temp(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wm831x_hwmon *hwmon = dev_get_drvdata(dev);
+ int channel = to_sensor_dev_attr(attr)->index;
+ int ret;
+
+ ret = wm831x_auxadc_read(hwmon->wm831x, channel);
+ if (ret < 0)
+ return ret;
+
+ /* Degrees celsius = (512.18-ret) / 1.0983 */
+ ret = 512180 - (ret * 1000);
+ ret = DIV_ROUND_CLOSEST(ret * 10000, 10983);
+
+ return sprintf(buf, "%d\n", ret);
+}
+
+static ssize_t show_label(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int channel = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%s\n", input_names[channel]);
+}
+
+#define WM831X_VOLTAGE(id, name) \
+ static SENSOR_DEVICE_ATTR(in##id##_input, S_IRUGO, show_voltage, \
+ NULL, name)
+
+#define WM831X_NAMED_VOLTAGE(id, name) \
+ WM831X_VOLTAGE(id, name); \
+ static SENSOR_DEVICE_ATTR(in##id##_label, S_IRUGO, show_label, \
+ NULL, name)
+
+static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
+
+WM831X_VOLTAGE(0, WM831X_AUX_AUX1);
+WM831X_VOLTAGE(1, WM831X_AUX_AUX2);
+WM831X_VOLTAGE(2, WM831X_AUX_AUX3);
+WM831X_VOLTAGE(3, WM831X_AUX_AUX4);
+
+WM831X_NAMED_VOLTAGE(4, WM831X_AUX_SYSVDD);
+WM831X_NAMED_VOLTAGE(5, WM831X_AUX_USB);
+WM831X_NAMED_VOLTAGE(6, WM831X_AUX_BATT);
+WM831X_NAMED_VOLTAGE(7, WM831X_AUX_WALL);
+WM831X_NAMED_VOLTAGE(8, WM831X_AUX_BKUP_BATT);
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_chip_temp, NULL,
+ WM831X_AUX_CHIP_TEMP);
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL,
+ WM831X_AUX_CHIP_TEMP);
+/* Report as a voltage since conversion depends on external components
+ * and that's what the ABI wants. */
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_voltage, NULL,
+ WM831X_AUX_BATT_TEMP);
+static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL,
+ WM831X_AUX_BATT_TEMP);
+
+static struct attribute *wm831x_attributes[] = {
+ &dev_attr_name.attr,
+
+ &sensor_dev_attr_in0_input.dev_attr.attr,
+ &sensor_dev_attr_in1_input.dev_attr.attr,
+ &sensor_dev_attr_in2_input.dev_attr.attr,
+ &sensor_dev_attr_in3_input.dev_attr.attr,
+
+ &sensor_dev_attr_in4_input.dev_attr.attr,
+ &sensor_dev_attr_in4_label.dev_attr.attr,
+ &sensor_dev_attr_in5_input.dev_attr.attr,
+ &sensor_dev_attr_in5_label.dev_attr.attr,
+ &sensor_dev_attr_in6_input.dev_attr.attr,
+ &sensor_dev_attr_in6_label.dev_attr.attr,
+ &sensor_dev_attr_in7_input.dev_attr.attr,
+ &sensor_dev_attr_in7_label.dev_attr.attr,
+ &sensor_dev_attr_in8_input.dev_attr.attr,
+ &sensor_dev_attr_in8_label.dev_attr.attr,
+
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ &sensor_dev_attr_temp1_label.dev_attr.attr,
+ &sensor_dev_attr_temp2_input.dev_attr.attr,
+ &sensor_dev_attr_temp2_label.dev_attr.attr,
+
+ NULL
+};
+
+static const struct attribute_group wm831x_attr_group = {
+ .attrs = wm831x_attributes,
+};
+
+static int __devinit wm831x_hwmon_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_hwmon *hwmon;
+ int ret;
+
+ hwmon = kzalloc(sizeof(struct wm831x_hwmon), GFP_KERNEL);
+ if (!hwmon)
+ return -ENOMEM;
+
+ hwmon->wm831x = wm831x;
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &wm831x_attr_group);
+ if (ret)
+ goto err;
+
+ hwmon->classdev = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(hwmon->classdev)) {
+ ret = PTR_ERR(hwmon->classdev);
+ goto err_sysfs;
+ }
+
+ platform_set_drvdata(pdev, hwmon);
+
+ return 0;
+
+err_sysfs:
+ sysfs_remove_group(&pdev->dev.kobj, &wm831x_attr_group);
+err:
+ kfree(hwmon);
+ return ret;
+}
+
+static int __devexit wm831x_hwmon_remove(struct platform_device *pdev)
+{
+ struct wm831x_hwmon *hwmon = platform_get_drvdata(pdev);
+
+ hwmon_device_unregister(hwmon->classdev);
+ sysfs_remove_group(&pdev->dev.kobj, &wm831x_attr_group);
+ platform_set_drvdata(pdev, NULL);
+ kfree(hwmon);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_hwmon_driver = {
+ .probe = wm831x_hwmon_probe,
+ .remove = __devexit_p(wm831x_hwmon_remove),
+ .driver = {
+ .name = "wm831x-hwmon",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init wm831x_hwmon_init(void)
+{
+ return platform_driver_register(&wm831x_hwmon_driver);
+}
+module_init(wm831x_hwmon_init);
+
+static void __exit wm831x_hwmon_exit(void)
+{
+ platform_driver_unregister(&wm831x_hwmon_driver);
+}
+module_exit(wm831x_hwmon_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM831x Hardware Monitoring");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-hwmon");
diff --git a/drivers/hwmon/wm8350-hwmon.c b/drivers/hwmon/wm8350-hwmon.c
new file mode 100644
index 00000000000..13290595ca8
--- /dev/null
+++ b/drivers/hwmon/wm8350-hwmon.c
@@ -0,0 +1,151 @@
+/*
+ * drivers/hwmon/wm8350-hwmon.c - Wolfson Microelectronics WM8350 PMIC
+ * hardware monitoring features.
+ *
+ * Copyright (C) 2009 Wolfson Microelectronics plc
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License v2 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+
+#include <linux/mfd/wm8350/core.h>
+#include <linux/mfd/wm8350/comparator.h>
+
+static ssize_t show_name(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "wm8350\n");
+}
+
+static const char *input_names[] = {
+ [WM8350_AUXADC_USB] = "USB",
+ [WM8350_AUXADC_LINE] = "Line",
+ [WM8350_AUXADC_BATT] = "Battery",
+};
+
+
+static ssize_t show_voltage(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wm8350 *wm8350 = dev_get_drvdata(dev);
+ int channel = to_sensor_dev_attr(attr)->index;
+ int val;
+
+ val = wm8350_read_auxadc(wm8350, channel, 0, 0) * WM8350_AUX_COEFF;
+ val = DIV_ROUND_CLOSEST(val, 1000);
+
+ return sprintf(buf, "%d\n", val);
+}
+
+static ssize_t show_label(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int channel = to_sensor_dev_attr(attr)->index;
+
+ return sprintf(buf, "%s\n", input_names[channel]);
+}
+
+#define WM8350_NAMED_VOLTAGE(id, name) \
+ static SENSOR_DEVICE_ATTR(in##id##_input, S_IRUGO, show_voltage,\
+ NULL, name); \
+ static SENSOR_DEVICE_ATTR(in##id##_label, S_IRUGO, show_label, \
+ NULL, name)
+
+static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
+
+WM8350_NAMED_VOLTAGE(0, WM8350_AUXADC_USB);
+WM8350_NAMED_VOLTAGE(1, WM8350_AUXADC_BATT);
+WM8350_NAMED_VOLTAGE(2, WM8350_AUXADC_LINE);
+
+static struct attribute *wm8350_attributes[] = {
+ &dev_attr_name.attr,
+
+ &sensor_dev_attr_in0_input.dev_attr.attr,
+ &sensor_dev_attr_in0_label.dev_attr.attr,
+ &sensor_dev_attr_in1_input.dev_attr.attr,
+ &sensor_dev_attr_in1_label.dev_attr.attr,
+ &sensor_dev_attr_in2_input.dev_attr.attr,
+ &sensor_dev_attr_in2_label.dev_attr.attr,
+
+ NULL,
+};
+
+static const struct attribute_group wm8350_attr_group = {
+ .attrs = wm8350_attributes,
+};
+
+static int __devinit wm8350_hwmon_probe(struct platform_device *pdev)
+{
+ struct wm8350 *wm8350 = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &wm8350_attr_group);
+ if (ret)
+ goto err;
+
+ wm8350->hwmon.classdev = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(wm8350->hwmon.classdev)) {
+ ret = PTR_ERR(wm8350->hwmon.classdev);
+ goto err_group;
+ }
+
+ return 0;
+
+err_group:
+ sysfs_remove_group(&pdev->dev.kobj, &wm8350_attr_group);
+err:
+ return ret;
+}
+
+static int __devexit wm8350_hwmon_remove(struct platform_device *pdev)
+{
+ struct wm8350 *wm8350 = platform_get_drvdata(pdev);
+
+ hwmon_device_unregister(wm8350->hwmon.classdev);
+ sysfs_remove_group(&pdev->dev.kobj, &wm8350_attr_group);
+
+ return 0;
+}
+
+static struct platform_driver wm8350_hwmon_driver = {
+ .probe = wm8350_hwmon_probe,
+ .remove = __devexit_p(wm8350_hwmon_remove),
+ .driver = {
+ .name = "wm8350-hwmon",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init wm8350_hwmon_init(void)
+{
+ return platform_driver_register(&wm8350_hwmon_driver);
+}
+module_init(wm8350_hwmon_init);
+
+static void __exit wm8350_hwmon_exit(void)
+{
+ platform_driver_unregister(&wm8350_hwmon_driver);
+}
+module_exit(wm8350_hwmon_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM8350 Hardware Monitoring");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm8350-hwmon");
diff --git a/drivers/i2c/busses/i2c-sh_mobile.c b/drivers/i2c/busses/i2c-sh_mobile.c
index 820487d0d5c..86a9d4e8147 100644
--- a/drivers/i2c/busses/i2c-sh_mobile.c
+++ b/drivers/i2c/busses/i2c-sh_mobile.c
@@ -28,6 +28,7 @@
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/err.h>
+#include <linux/pm_runtime.h>
#include <linux/clk.h>
#include <linux/io.h>
@@ -165,7 +166,8 @@ static void activate_ch(struct sh_mobile_i2c_data *pd)
u_int32_t denom;
u_int32_t tmp;
- /* Make sure the clock is enabled */
+ /* Wake up device and enable clock */
+ pm_runtime_get_sync(pd->dev);
clk_enable(pd->clk);
/* Get clock rate after clock is enabled */
@@ -213,8 +215,9 @@ static void deactivate_ch(struct sh_mobile_i2c_data *pd)
/* Disable channel */
iowrite8(ioread8(ICCR(pd)) & ~ICCR_ICE, ICCR(pd));
- /* Disable clock */
+ /* Disable clock and mark device as idle */
clk_disable(pd->clk);
+ pm_runtime_put_sync(pd->dev);
}
static unsigned char i2c_op(struct sh_mobile_i2c_data *pd,
@@ -572,6 +575,19 @@ static int sh_mobile_i2c_probe(struct platform_device *dev)
goto err_irq;
}
+ /* Enable Runtime PM for this device.
+ *
+ * Also tell the Runtime PM core to ignore children
+ * for this device since it is valid for us to suspend
+ * this I2C master driver even though the slave devices
+ * on the I2C bus may not be suspended.
+ *
+ * The state of the I2C hardware bus is unaffected by
+ * the Runtime PM state.
+ */
+ pm_suspend_ignore_children(&dev->dev, true);
+ pm_runtime_enable(&dev->dev);
+
/* setup the private data */
adap = &pd->adap;
i2c_set_adapdata(adap, pd);
@@ -614,14 +630,33 @@ static int sh_mobile_i2c_remove(struct platform_device *dev)
iounmap(pd->reg);
sh_mobile_i2c_hook_irqs(dev, 0);
clk_put(pd->clk);
+ pm_runtime_disable(&dev->dev);
kfree(pd);
return 0;
}
+static int sh_mobile_i2c_runtime_nop(struct device *dev)
+{
+ /* Runtime PM callback shared between ->runtime_suspend()
+ * and ->runtime_resume(). Simply returns success.
+ *
+ * This driver re-initializes all registers after
+ * pm_runtime_get_sync() anyway so there is no need
+ * to save and restore registers here.
+ */
+ return 0;
+}
+
+static struct dev_pm_ops sh_mobile_i2c_dev_pm_ops = {
+ .runtime_suspend = sh_mobile_i2c_runtime_nop,
+ .runtime_resume = sh_mobile_i2c_runtime_nop,
+};
+
static struct platform_driver sh_mobile_i2c_driver = {
.driver = {
.name = "i2c-sh_mobile",
.owner = THIS_MODULE,
+ .pm = &sh_mobile_i2c_dev_pm_ops,
},
.probe = sh_mobile_i2c_probe,
.remove = sh_mobile_i2c_remove,
diff --git a/drivers/input/keyboard/omap-keypad.c b/drivers/input/keyboard/omap-keypad.c
index 87ec7b18ac6..bba85add35a 100644
--- a/drivers/input/keyboard/omap-keypad.c
+++ b/drivers/input/keyboard/omap-keypad.c
@@ -116,7 +116,7 @@ static irqreturn_t omap_kp_interrupt(int irq, void *dev_id)
}
} else
/* disable keyboard interrupt and schedule for handling */
- omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
+ omap_writew(1, OMAP1_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
tasklet_schedule(&kp_tasklet);
@@ -143,20 +143,20 @@ static void omap_kp_scan_keypad(struct omap_kp *omap_kp, unsigned char *state)
} else {
/* disable keyboard interrupt and schedule for handling */
- omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
+ omap_writew(1, OMAP1_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
/* read the keypad status */
- omap_writew(0xff, OMAP_MPUIO_BASE + OMAP_MPUIO_KBC);
+ omap_writew(0xff, OMAP1_MPUIO_BASE + OMAP_MPUIO_KBC);
for (col = 0; col < omap_kp->cols; col++) {
omap_writew(~(1 << col) & 0xff,
- OMAP_MPUIO_BASE + OMAP_MPUIO_KBC);
+ OMAP1_MPUIO_BASE + OMAP_MPUIO_KBC);
udelay(omap_kp->delay);
- state[col] = ~omap_readw(OMAP_MPUIO_BASE +
+ state[col] = ~omap_readw(OMAP1_MPUIO_BASE +
OMAP_MPUIO_KBR_LATCH) & 0xff;
}
- omap_writew(0x00, OMAP_MPUIO_BASE + OMAP_MPUIO_KBC);
+ omap_writew(0x00, OMAP1_MPUIO_BASE + OMAP_MPUIO_KBC);
udelay(2);
}
}
@@ -234,7 +234,7 @@ static void omap_kp_tasklet(unsigned long data)
for (i = 0; i < omap_kp_data->rows; i++)
enable_irq(gpio_to_irq(row_gpios[i]));
} else {
- omap_writew(0, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
+ omap_writew(0, OMAP1_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
kp_cur_group = -1;
}
}
@@ -317,7 +317,7 @@ static int __devinit omap_kp_probe(struct platform_device *pdev)
/* Disable the interrupt for the MPUIO keyboard */
if (!cpu_is_omap24xx())
- omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
+ omap_writew(1, OMAP1_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
keymap = pdata->keymap;
@@ -391,7 +391,7 @@ static int __devinit omap_kp_probe(struct platform_device *pdev)
}
if (pdata->dbounce)
- omap_writew(0xff, OMAP_MPUIO_BASE + OMAP_MPUIO_GPIO_DEBOUNCING);
+ omap_writew(0xff, OMAP1_MPUIO_BASE + OMAP_MPUIO_GPIO_DEBOUNCING);
/* scan current status and enable interrupt */
omap_kp_scan_keypad(omap_kp, keypad_state);
@@ -402,7 +402,7 @@ static int __devinit omap_kp_probe(struct platform_device *pdev)
"omap-keypad", omap_kp) < 0)
goto err4;
}
- omap_writew(0, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
+ omap_writew(0, OMAP1_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
} else {
for (irq_idx = 0; irq_idx < omap_kp->rows; irq_idx++) {
if (request_irq(gpio_to_irq(row_gpios[irq_idx]),
@@ -449,7 +449,7 @@ static int __devexit omap_kp_remove(struct platform_device *pdev)
free_irq(gpio_to_irq(row_gpios[i]), 0);
}
} else {
- omap_writew(1, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
+ omap_writew(1, OMAP1_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT);
free_irq(omap_kp->irq, 0);
}
diff --git a/drivers/input/keyboard/sh_keysc.c b/drivers/input/keyboard/sh_keysc.c
index 0714bf2c28f..887af79b7bf 100644
--- a/drivers/input/keyboard/sh_keysc.c
+++ b/drivers/input/keyboard/sh_keysc.c
@@ -80,6 +80,9 @@ static irqreturn_t sh_keysc_isr(int irq, void *dev_id)
iowrite16(KYCR2_IRQ_LEVEL | (keyin_set << 8),
priv->iomem_base + KYCR2_OFFS);
+ if (pdata->kycr2_delay)
+ udelay(pdata->kycr2_delay);
+
keys ^= ~0;
keys &= (1 << (sh_keysc_mode[pdata->mode].keyin *
sh_keysc_mode[pdata->mode].keyout)) - 1;
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index cbe21bc96b5..1a50be379cb 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -279,4 +279,24 @@ config INPUT_BFIN_ROTARY
To compile this driver as a module, choose M here: the
module will be called bfin-rotary.
+config INPUT_WM831X_ON
+ tristate "WM831X ON pin"
+ depends on MFD_WM831X
+ help
+ Support the ON pin of WM831X PMICs as an input device
+ reporting power button status.
+
+ To compile this driver as a module, choose M here: the module
+ will be called wm831x_on.
+
+config INPUT_PCAP
+ tristate "Motorola EZX PCAP misc input events"
+ depends on EZX_PCAP
+ help
+ Say Y here if you want to use Power key and Headphone button
+ on Motorola EZX phones.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcap_keys.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 79c1e9a5ea3..bf4db626c31 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o
obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
+obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o
obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o
obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o
obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
@@ -26,4 +27,6 @@ obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o
obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o
obj-$(CONFIG_INPUT_UINPUT) += uinput.o
obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
+obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
obj-$(CONFIG_INPUT_YEALINK) += yealink.o
+
diff --git a/drivers/input/misc/pcap_keys.c b/drivers/input/misc/pcap_keys.c
new file mode 100644
index 00000000000..7ea969347ca
--- /dev/null
+++ b/drivers/input/misc/pcap_keys.c
@@ -0,0 +1,144 @@
+/*
+ * Input driver for PCAP events:
+ * * Power key
+ * * Headphone button
+ *
+ * Copyright (c) 2008,2009 Ilya Petrov <ilya.muromec@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/mfd/ezx-pcap.h>
+
+struct pcap_keys {
+ struct pcap_chip *pcap;
+ struct input_dev *input;
+};
+
+/* PCAP2 interrupts us on keypress */
+static irqreturn_t pcap_keys_handler(int irq, void *_pcap_keys)
+{
+ struct pcap_keys *pcap_keys = _pcap_keys;
+ int pirq = irq_to_pcap(pcap_keys->pcap, irq);
+ u32 pstat;
+
+ ezx_pcap_read(pcap_keys->pcap, PCAP_REG_PSTAT, &pstat);
+ pstat &= 1 << pirq;
+
+ switch (pirq) {
+ case PCAP_IRQ_ONOFF:
+ input_report_key(pcap_keys->input, KEY_POWER, !pstat);
+ break;
+ case PCAP_IRQ_MIC:
+ input_report_key(pcap_keys->input, KEY_HP, !pstat);
+ break;
+ }
+
+ input_sync(pcap_keys->input);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit pcap_keys_probe(struct platform_device *pdev)
+{
+ int err = -ENOMEM;
+ struct pcap_keys *pcap_keys;
+ struct input_dev *input_dev;
+
+ pcap_keys = kmalloc(sizeof(struct pcap_keys), GFP_KERNEL);
+ if (!pcap_keys)
+ return err;
+
+ pcap_keys->pcap = dev_get_drvdata(pdev->dev.parent);
+
+ input_dev = input_allocate_device();
+ if (!input_dev)
+ goto fail;
+
+ pcap_keys->input = input_dev;
+
+ platform_set_drvdata(pdev, pcap_keys);
+ input_dev->name = pdev->name;
+ input_dev->phys = "pcap-keys/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_dev->dev.parent = &pdev->dev;
+
+ __set_bit(EV_KEY, input_dev->evbit);
+ __set_bit(KEY_POWER, input_dev->keybit);
+ __set_bit(KEY_HP, input_dev->keybit);
+
+ err = input_register_device(input_dev);
+ if (err)
+ goto fail_allocate;
+
+ err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF),
+ pcap_keys_handler, 0, "Power key", pcap_keys);
+ if (err)
+ goto fail_register;
+
+ err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC),
+ pcap_keys_handler, 0, "Headphone button", pcap_keys);
+ if (err)
+ goto fail_pwrkey;
+
+ return 0;
+
+fail_pwrkey:
+ free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF), pcap_keys);
+fail_register:
+ input_unregister_device(input_dev);
+ goto fail;
+fail_allocate:
+ input_free_device(input_dev);
+fail:
+ kfree(pcap_keys);
+ return err;
+}
+
+static int __devexit pcap_keys_remove(struct platform_device *pdev)
+{
+ struct pcap_keys *pcap_keys = platform_get_drvdata(pdev);
+
+ free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF), pcap_keys);
+ free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC), pcap_keys);
+
+ input_unregister_device(pcap_keys->input);
+ kfree(pcap_keys);
+
+ return 0;
+}
+
+static struct platform_driver pcap_keys_device_driver = {
+ .probe = pcap_keys_probe,
+ .remove = __devexit_p(pcap_keys_remove),
+ .driver = {
+ .name = "pcap-keys",
+ .owner = THIS_MODULE,
+ }
+};
+
+static int __init pcap_keys_init(void)
+{
+ return platform_driver_register(&pcap_keys_device_driver);
+};
+
+static void __exit pcap_keys_exit(void)
+{
+ platform_driver_unregister(&pcap_keys_device_driver);
+};
+
+module_init(pcap_keys_init);
+module_exit(pcap_keys_exit);
+
+MODULE_DESCRIPTION("Motorola PCAP2 input events driver");
+MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcap_keys");
diff --git a/drivers/input/misc/wm831x-on.c b/drivers/input/misc/wm831x-on.c
new file mode 100644
index 00000000000..ba4f5dd7c60
--- /dev/null
+++ b/drivers/input/misc/wm831x-on.c
@@ -0,0 +1,163 @@
+/**
+ * wm831x-on.c - WM831X ON pin driver
+ *
+ * Copyright (C) 2009 Wolfson Microelectronics plc
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * 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
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/wm831x/core.h>
+
+struct wm831x_on {
+ struct input_dev *dev;
+ struct delayed_work work;
+ struct wm831x *wm831x;
+};
+
+/*
+ * The chip gives us an interrupt when the ON pin is asserted but we
+ * then need to poll to see when the pin is deasserted.
+ */
+static void wm831x_poll_on(struct work_struct *work)
+{
+ struct wm831x_on *wm831x_on = container_of(work, struct wm831x_on,
+ work.work);
+ struct wm831x *wm831x = wm831x_on->wm831x;
+ int poll, ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_ON_PIN_CONTROL);
+ if (ret >= 0) {
+ poll = !(ret & WM831X_ON_PIN_STS);
+
+ input_report_key(wm831x_on->dev, KEY_POWER, poll);
+ input_sync(wm831x_on->dev);
+ } else {
+ dev_err(wm831x->dev, "Failed to read ON status: %d\n", ret);
+ poll = 1;
+ }
+
+ if (poll)
+ schedule_delayed_work(&wm831x_on->work, 100);
+}
+
+static irqreturn_t wm831x_on_irq(int irq, void *data)
+{
+ struct wm831x_on *wm831x_on = data;
+
+ schedule_delayed_work(&wm831x_on->work, 0);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit wm831x_on_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_on *wm831x_on;
+ int irq = platform_get_irq(pdev, 0);
+ int ret;
+
+ wm831x_on = kzalloc(sizeof(struct wm831x_on), GFP_KERNEL);
+ if (!wm831x_on) {
+ dev_err(&pdev->dev, "Can't allocate data\n");
+ return -ENOMEM;
+ }
+
+ wm831x_on->wm831x = wm831x;
+ INIT_DELAYED_WORK(&wm831x_on->work, wm831x_poll_on);
+
+ wm831x_on->dev = input_allocate_device();
+ if (!wm831x_on->dev) {
+ dev_err(&pdev->dev, "Can't allocate input dev\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ wm831x_on->dev->evbit[0] = BIT_MASK(EV_KEY);
+ wm831x_on->dev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER);
+ wm831x_on->dev->name = "wm831x_on";
+ wm831x_on->dev->phys = "wm831x_on/input0";
+ wm831x_on->dev->dev.parent = &pdev->dev;
+
+ ret = wm831x_request_irq(wm831x, irq, wm831x_on_irq,
+ IRQF_TRIGGER_RISING, "wm831x_on", wm831x_on);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Unable to request IRQ: %d\n", ret);
+ goto err_input_dev;
+ }
+ ret = input_register_device(wm831x_on->dev);
+ if (ret) {
+ dev_dbg(&pdev->dev, "Can't register input device: %d\n", ret);
+ goto err_irq;
+ }
+
+ platform_set_drvdata(pdev, wm831x_on);
+
+ return 0;
+
+err_irq:
+ wm831x_free_irq(wm831x, irq, NULL);
+err_input_dev:
+ input_free_device(wm831x_on->dev);
+err:
+ kfree(wm831x_on);
+ return ret;
+}
+
+static int __devexit wm831x_on_remove(struct platform_device *pdev)
+{
+ struct wm831x_on *wm831x_on = platform_get_drvdata(pdev);
+ int irq = platform_get_irq(pdev, 0);
+
+ wm831x_free_irq(wm831x_on->wm831x, irq, wm831x_on);
+ cancel_delayed_work_sync(&wm831x_on->work);
+ input_unregister_device(wm831x_on->dev);
+ kfree(wm831x_on);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_on_driver = {
+ .probe = wm831x_on_probe,
+ .remove = __devexit_p(wm831x_on_remove),
+ .driver = {
+ .name = "wm831x-on",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init wm831x_on_init(void)
+{
+ return platform_driver_register(&wm831x_on_driver);
+}
+module_init(wm831x_on_init);
+
+static void __exit wm831x_on_exit(void)
+{
+ platform_driver_unregister(&wm831x_on_driver);
+}
+module_exit(wm831x_on_exit);
+
+MODULE_ALIAS("platform:wm831x-on");
+MODULE_DESCRIPTION("WM831x ON pin");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 87a1ae63bcc..ab02d72afbf 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -510,4 +510,13 @@ config TOUCHSCREEN_W90X900
To compile this driver as a module, choose M here: the
module will be called w90p910_ts.
+config TOUCHSCREEN_PCAP
+ tristate "Motorola PCAP touchscreen"
+ depends on EZX_PCAP
+ help
+ Say Y here if you have a Motorola EZX telephone and
+ want to enable support for the built-in touchscreen.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pcap_ts.
endif
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 3e1c5e0b952..4599bf7ad81 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -40,3 +40,4 @@ obj-$(CONFIG_TOUCHSCREEN_WM97XX_ATMEL) += atmel-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX_MAINSTONE) += mainstone-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_WM97XX_ZYLONITE) += zylonite-wm97xx.o
obj-$(CONFIG_TOUCHSCREEN_W90X900) += w90p910_ts.o
+obj-$(CONFIG_TOUCHSCREEN_PCAP) += pcap_ts.o
diff --git a/drivers/input/touchscreen/pcap_ts.c b/drivers/input/touchscreen/pcap_ts.c
new file mode 100644
index 00000000000..67fcd33595d
--- /dev/null
+++ b/drivers/input/touchscreen/pcap_ts.c
@@ -0,0 +1,271 @@
+/*
+ * Driver for Motorola PCAP2 touchscreen as found in the EZX phone platform.
+ *
+ * Copyright (C) 2006 Harald Welte <laforge@openezx.org>
+ * Copyright (C) 2009 Daniel Ribeiro <drwyrm@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/pm.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/mfd/ezx-pcap.h>
+
+struct pcap_ts {
+ struct pcap_chip *pcap;
+ struct input_dev *input;
+ struct delayed_work work;
+ u16 x, y;
+ u16 pressure;
+ u8 read_state;
+};
+
+#define SAMPLE_DELAY 20 /* msecs */
+
+#define X_AXIS_MIN 0
+#define X_AXIS_MAX 1023
+#define Y_AXIS_MAX X_AXIS_MAX
+#define Y_AXIS_MIN X_AXIS_MIN
+#define PRESSURE_MAX X_AXIS_MAX
+#define PRESSURE_MIN X_AXIS_MIN
+
+static void pcap_ts_read_xy(void *data, u16 res[2])
+{
+ struct pcap_ts *pcap_ts = data;
+
+ switch (pcap_ts->read_state) {
+ case PCAP_ADC_TS_M_PRESSURE:
+ /* pressure reading is unreliable */
+ if (res[0] > PRESSURE_MIN && res[0] < PRESSURE_MAX)
+ pcap_ts->pressure = res[0];
+ pcap_ts->read_state = PCAP_ADC_TS_M_XY;
+ schedule_delayed_work(&pcap_ts->work, 0);
+ break;
+ case PCAP_ADC_TS_M_XY:
+ pcap_ts->y = res[0];
+ pcap_ts->x = res[1];
+ if (pcap_ts->x <= X_AXIS_MIN || pcap_ts->x >= X_AXIS_MAX ||
+ pcap_ts->y <= Y_AXIS_MIN || pcap_ts->y >= Y_AXIS_MAX) {
+ /* pen has been released */
+ input_report_abs(pcap_ts->input, ABS_PRESSURE, 0);
+ input_report_key(pcap_ts->input, BTN_TOUCH, 0);
+
+ pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY;
+ schedule_delayed_work(&pcap_ts->work, 0);
+ } else {
+ /* pen is touching the screen */
+ input_report_abs(pcap_ts->input, ABS_X, pcap_ts->x);
+ input_report_abs(pcap_ts->input, ABS_Y, pcap_ts->y);
+ input_report_key(pcap_ts->input, BTN_TOUCH, 1);
+ input_report_abs(pcap_ts->input, ABS_PRESSURE,
+ pcap_ts->pressure);
+
+ /* switch back to pressure read mode */
+ pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE;
+ schedule_delayed_work(&pcap_ts->work,
+ msecs_to_jiffies(SAMPLE_DELAY));
+ }
+ input_sync(pcap_ts->input);
+ break;
+ default:
+ dev_warn(&pcap_ts->input->dev,
+ "pcap_ts: Warning, unhandled read_state %d\n",
+ pcap_ts->read_state);
+ break;
+ }
+}
+
+static void pcap_ts_work(struct work_struct *work)
+{
+ struct delayed_work *dw = container_of(work, struct delayed_work, work);
+ struct pcap_ts *pcap_ts = container_of(dw, struct pcap_ts, work);
+ u8 ch[2];
+
+ pcap_set_ts_bits(pcap_ts->pcap,
+ pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
+
+ if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY)
+ return;
+
+ /* start adc conversion */
+ ch[0] = PCAP_ADC_CH_TS_X1;
+ ch[1] = PCAP_ADC_CH_TS_Y1;
+ pcap_adc_async(pcap_ts->pcap, PCAP_ADC_BANK_1, 0, ch,
+ pcap_ts_read_xy, pcap_ts);
+}
+
+static irqreturn_t pcap_ts_event_touch(int pirq, void *data)
+{
+ struct pcap_ts *pcap_ts = data;
+
+ if (pcap_ts->read_state == PCAP_ADC_TS_M_STANDBY) {
+ pcap_ts->read_state = PCAP_ADC_TS_M_PRESSURE;
+ schedule_delayed_work(&pcap_ts->work, 0);
+ }
+ return IRQ_HANDLED;
+}
+
+static int pcap_ts_open(struct input_dev *dev)
+{
+ struct pcap_ts *pcap_ts = input_get_drvdata(dev);
+
+ pcap_ts->read_state = PCAP_ADC_TS_M_STANDBY;
+ schedule_delayed_work(&pcap_ts->work, 0);
+
+ return 0;
+}
+
+static void pcap_ts_close(struct input_dev *dev)
+{
+ struct pcap_ts *pcap_ts = input_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&pcap_ts->work);
+
+ pcap_ts->read_state = PCAP_ADC_TS_M_NONTS;
+ pcap_set_ts_bits(pcap_ts->pcap,
+ pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
+}
+
+static int __devinit pcap_ts_probe(struct platform_device *pdev)
+{
+ struct input_dev *input_dev;
+ struct pcap_ts *pcap_ts;
+ int err = -ENOMEM;
+
+ pcap_ts = kzalloc(sizeof(*pcap_ts), GFP_KERNEL);
+ if (!pcap_ts)
+ return err;
+
+ pcap_ts->pcap = dev_get_drvdata(pdev->dev.parent);
+ platform_set_drvdata(pdev, pcap_ts);
+
+ input_dev = input_allocate_device();
+ if (!input_dev)
+ goto fail;
+
+ INIT_DELAYED_WORK(&pcap_ts->work, pcap_ts_work);
+
+ pcap_ts->read_state = PCAP_ADC_TS_M_NONTS;
+ pcap_set_ts_bits(pcap_ts->pcap,
+ pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
+
+ pcap_ts->input = input_dev;
+ input_set_drvdata(input_dev, pcap_ts);
+
+ input_dev->name = "pcap-touchscreen";
+ input_dev->phys = "pcap_ts/input0";
+ input_dev->id.bustype = BUS_HOST;
+ input_dev->id.vendor = 0x0001;
+ input_dev->id.product = 0x0002;
+ input_dev->id.version = 0x0100;
+ input_dev->dev.parent = &pdev->dev;
+ input_dev->open = pcap_ts_open;
+ input_dev->close = pcap_ts_close;
+
+ input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
+ input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
+ input_set_abs_params(input_dev, ABS_X, X_AXIS_MIN, X_AXIS_MAX, 0, 0);
+ input_set_abs_params(input_dev, ABS_Y, Y_AXIS_MIN, Y_AXIS_MAX, 0, 0);
+ input_set_abs_params(input_dev, ABS_PRESSURE, PRESSURE_MIN,
+ PRESSURE_MAX, 0, 0);
+
+ err = input_register_device(pcap_ts->input);
+ if (err)
+ goto fail_allocate;
+
+ err = request_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS),
+ pcap_ts_event_touch, 0, "Touch Screen", pcap_ts);
+ if (err)
+ goto fail_register;
+
+ return 0;
+
+fail_register:
+ input_unregister_device(input_dev);
+ goto fail;
+fail_allocate:
+ input_free_device(input_dev);
+fail:
+ kfree(pcap_ts);
+
+ return err;
+}
+
+static int __devexit pcap_ts_remove(struct platform_device *pdev)
+{
+ struct pcap_ts *pcap_ts = platform_get_drvdata(pdev);
+
+ free_irq(pcap_to_irq(pcap_ts->pcap, PCAP_IRQ_TS), pcap_ts);
+ cancel_delayed_work_sync(&pcap_ts->work);
+
+ input_unregister_device(pcap_ts->input);
+
+ kfree(pcap_ts);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pcap_ts_suspend(struct device *dev)
+{
+ struct pcap_ts *pcap_ts = dev_get_drvdata(dev);
+
+ pcap_set_ts_bits(pcap_ts->pcap, PCAP_ADC_TS_REF_LOWPWR);
+ return 0;
+}
+
+static int pcap_ts_resume(struct device *dev)
+{
+ struct pcap_ts *pcap_ts = dev_get_drvdata(dev);
+
+ pcap_set_ts_bits(pcap_ts->pcap,
+ pcap_ts->read_state << PCAP_ADC_TS_M_SHIFT);
+ return 0;
+}
+
+static struct dev_pm_ops pcap_ts_pm_ops = {
+ .suspend = pcap_ts_suspend,
+ .resume = pcap_ts_resume,
+};
+#define PCAP_TS_PM_OPS (&pcap_ts_pm_ops)
+#else
+#define PCAP_TS_PM_OPS NULL
+#endif
+
+static struct platform_driver pcap_ts_driver = {
+ .probe = pcap_ts_probe,
+ .remove = __devexit_p(pcap_ts_remove),
+ .driver = {
+ .name = "pcap-ts",
+ .owner = THIS_MODULE,
+ .pm = PCAP_TS_PM_OPS,
+ },
+};
+
+static int __init pcap_ts_init(void)
+{
+ return platform_driver_register(&pcap_ts_driver);
+}
+
+static void __exit pcap_ts_exit(void)
+{
+ platform_driver_unregister(&pcap_ts_driver);
+}
+
+module_init(pcap_ts_init);
+module_exit(pcap_ts_exit);
+
+MODULE_DESCRIPTION("Motorola PCAP2 touchscreen driver");
+MODULE_AUTHOR("Daniel Ribeiro / Harald Welte");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcap_ts");
diff --git a/drivers/media/video/sh_mobile_ceu_camera.c b/drivers/media/video/sh_mobile_ceu_camera.c
index e86878deea7..61c47b82408 100644
--- a/drivers/media/video/sh_mobile_ceu_camera.c
+++ b/drivers/media/video/sh_mobile_ceu_camera.c
@@ -30,7 +30,7 @@
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/videodev2.h>
-#include <linux/clk.h>
+#include <linux/pm_runtime.h>
#include <media/v4l2-common.h>
#include <media/v4l2-dev.h>
@@ -86,7 +86,6 @@ struct sh_mobile_ceu_dev {
unsigned int irq;
void __iomem *base;
- struct clk *clk;
unsigned long video_limit;
/* lock used to protect videobuf */
@@ -361,7 +360,7 @@ static int sh_mobile_ceu_add_device(struct soc_camera_device *icd)
if (ret)
goto err;
- clk_enable(pcdev->clk);
+ pm_runtime_get_sync(ici->dev);
ceu_write(pcdev, CAPSR, 1 << 16); /* reset */
while (ceu_read(pcdev, CSTSR) & 1)
@@ -395,7 +394,7 @@ static void sh_mobile_ceu_remove_device(struct soc_camera_device *icd)
}
spin_unlock_irqrestore(&pcdev->lock, flags);
- clk_disable(pcdev->clk);
+ pm_runtime_put_sync(ici->dev);
icd->ops->release(icd);
@@ -798,7 +797,6 @@ static int sh_mobile_ceu_probe(struct platform_device *pdev)
struct sh_mobile_ceu_dev *pcdev;
struct resource *res;
void __iomem *base;
- char clk_name[8];
unsigned int irq;
int err = 0;
@@ -862,13 +860,9 @@ static int sh_mobile_ceu_probe(struct platform_device *pdev)
goto exit_release_mem;
}
- snprintf(clk_name, sizeof(clk_name), "ceu%d", pdev->id);
- pcdev->clk = clk_get(&pdev->dev, clk_name);
- if (IS_ERR(pcdev->clk)) {
- dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name);
- err = PTR_ERR(pcdev->clk);
- goto exit_free_irq;
- }
+ pm_suspend_ignore_children(&pdev->dev, true);
+ pm_runtime_enable(&pdev->dev);
+ pm_runtime_resume(&pdev->dev);
pcdev->ici.priv = pcdev;
pcdev->ici.dev = &pdev->dev;
@@ -878,12 +872,10 @@ static int sh_mobile_ceu_probe(struct platform_device *pdev)
err = soc_camera_host_register(&pcdev->ici);
if (err)
- goto exit_free_clk;
+ goto exit_free_irq;
return 0;
-exit_free_clk:
- clk_put(pcdev->clk);
exit_free_irq:
free_irq(pcdev->irq, pcdev);
exit_release_mem:
@@ -904,7 +896,6 @@ static int sh_mobile_ceu_remove(struct platform_device *pdev)
struct sh_mobile_ceu_dev, ici);
soc_camera_host_unregister(soc_host);
- clk_put(pcdev->clk);
free_irq(pcdev->irq, pcdev);
if (platform_get_resource(pdev, IORESOURCE_MEM, 1))
dma_release_declared_memory(&pdev->dev);
@@ -913,9 +904,27 @@ static int sh_mobile_ceu_remove(struct platform_device *pdev)
return 0;
}
+static int sh_mobile_ceu_runtime_nop(struct device *dev)
+{
+ /* Runtime PM callback shared between ->runtime_suspend()
+ * and ->runtime_resume(). Simply returns success.
+ *
+ * This driver re-initializes all registers after
+ * pm_runtime_get_sync() anyway so there is no need
+ * to save and restore registers here.
+ */
+ return 0;
+}
+
+static struct dev_pm_ops sh_mobile_ceu_dev_pm_ops = {
+ .runtime_suspend = sh_mobile_ceu_runtime_nop,
+ .runtime_resume = sh_mobile_ceu_runtime_nop,
+};
+
static struct platform_driver sh_mobile_ceu_driver = {
.driver = {
.name = "sh_mobile_ceu",
+ .pm = &sh_mobile_ceu_dev_pm_ops,
},
.probe = sh_mobile_ceu_probe,
.remove = sh_mobile_ceu_remove,
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 491ac0f800d..570be139f9d 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -108,6 +108,19 @@ config TWL4030_CORE
high speed USB OTG transceiver, an audio codec (on most
versions) and many other features.
+config TWL4030_POWER
+ bool "Support power resources on TWL4030 family chips"
+ depends on TWL4030_CORE && ARM
+ help
+ Say yes here if you want to use the power resources on the
+ TWL4030 family chips. Most of these resources are regulators,
+ which have a separate driver; some are control signals, such
+ as clock request handshaking.
+
+ This driver uses board-specific data to initialize the resources
+ and load scripts controling which resources are switched off/on
+ or reset when a sleep, wakeup or warm reset event occurs.
+
config MFD_TMIO
bool
default n
@@ -157,6 +170,16 @@ config MFD_WM8400
the device, additional drivers must be enabled in order to use
the functionality of the device.
+config MFD_WM831X
+ tristate "Support Wolfson Microelectronics WM831x PMICs"
+ select MFD_CORE
+ depends on I2C
+ help
+ Support for the Wolfson Microelecronics WM831x PMICs. 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_WM8350
tristate
@@ -228,6 +251,16 @@ config MFD_PCF50633
facilities, and registers devices for the various functions
so that function-specific drivers can bind to them.
+config MFD_MC13783
+ tristate "Support Freescale MC13783"
+ depends on SPI_MASTER
+ select MFD_CORE
+ help
+ Support for the Freescale (Atlas) MC13783 PMIC and audio CODEC.
+ This driver provides common support for accessing the device,
+ additional drivers must be enabled in order to use the
+ functionality of the device.
+
config PCF50633_ADC
tristate "Support for NXP PCF50633 ADC"
depends on MFD_PCF50633
@@ -256,6 +289,15 @@ config AB3100_CORE
LEDs, vibrator, system power and temperature, power management
and ALSA sound.
+config AB3100_OTP
+ tristate "ST-Ericsson AB3100 OTP functions"
+ depends on AB3100_CORE
+ default y if AB3100_CORE
+ help
+ Select this to enable the AB3100 Mixed Signal IC OTP (one-time
+ programmable memory) support. This exposes a sysfs file to read
+ out OTP values.
+
config EZX_PCAP
bool "PCAP Support"
depends on GENERIC_HARDIRQS && SPI_MASTER
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 6f8a9a1af20..f3b277b90d4 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -15,6 +15,8 @@ obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o
obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o
obj-$(CONFIG_MFD_WM8400) += wm8400-core.o
+wm831x-objs := wm831x-core.o wm831x-irq.o wm831x-otp.o
+obj-$(CONFIG_MFD_WM831X) += wm831x.o
wm8350-objs := wm8350-core.o wm8350-regmap.o wm8350-gpio.o
obj-$(CONFIG_MFD_WM8350) += wm8350.o
obj-$(CONFIG_MFD_WM8350_I2C) += wm8350-i2c.o
@@ -23,6 +25,9 @@ obj-$(CONFIG_TPS65010) += tps65010.o
obj-$(CONFIG_MENELAUS) += menelaus.o
obj-$(CONFIG_TWL4030_CORE) += twl4030-core.o twl4030-irq.o
+obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o
+
+obj-$(CONFIG_MFD_MC13783) += mc13783-core.o
obj-$(CONFIG_MFD_CORE) += mfd-core.o
@@ -44,3 +49,4 @@ obj-$(CONFIG_MFD_PCF50633) += pcf50633-core.o
obj-$(CONFIG_PCF50633_ADC) += pcf50633-adc.o
obj-$(CONFIG_PCF50633_GPIO) += pcf50633-gpio.o
obj-$(CONFIG_AB3100_CORE) += ab3100-core.o
+obj-$(CONFIG_AB3100_OTP) += ab3100-otp.o
diff --git a/drivers/mfd/ab3100-core.c b/drivers/mfd/ab3100-core.c
index 13e7d7bfe85..c533f86ff5e 100644
--- a/drivers/mfd/ab3100-core.c
+++ b/drivers/mfd/ab3100-core.c
@@ -14,7 +14,6 @@
#include <linux/platform_device.h>
#include <linux/device.h>
#include <linux/interrupt.h>
-#include <linux/workqueue.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
@@ -77,7 +76,7 @@ u8 ab3100_get_chip_type(struct ab3100 *ab3100)
}
EXPORT_SYMBOL(ab3100_get_chip_type);
-int ab3100_set_register(struct ab3100 *ab3100, u8 reg, u8 regval)
+int ab3100_set_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 regval)
{
u8 regandval[2] = {reg, regval};
int err;
@@ -107,9 +106,10 @@ int ab3100_set_register(struct ab3100 *ab3100, u8 reg, u8 regval)
err = 0;
}
mutex_unlock(&ab3100->access_mutex);
- return 0;
+ return err;
}
-EXPORT_SYMBOL(ab3100_set_register);
+EXPORT_SYMBOL(ab3100_set_register_interruptible);
+
/*
* The test registers exist at an I2C bus address up one
@@ -118,7 +118,7 @@ EXPORT_SYMBOL(ab3100_set_register);
* anyway. It's currently only used from this file so declare
* it static and do not export.
*/
-static int ab3100_set_test_register(struct ab3100 *ab3100,
+static int ab3100_set_test_register_interruptible(struct ab3100 *ab3100,
u8 reg, u8 regval)
{
u8 regandval[2] = {reg, regval};
@@ -148,7 +148,8 @@ static int ab3100_set_test_register(struct ab3100 *ab3100,
return err;
}
-int ab3100_get_register(struct ab3100 *ab3100, u8 reg, u8 *regval)
+
+int ab3100_get_register_interruptible(struct ab3100 *ab3100, u8 reg, u8 *regval)
{
int err;
@@ -202,9 +203,10 @@ int ab3100_get_register(struct ab3100 *ab3100, u8 reg, u8 *regval)
mutex_unlock(&ab3100->access_mutex);
return err;
}
-EXPORT_SYMBOL(ab3100_get_register);
+EXPORT_SYMBOL(ab3100_get_register_interruptible);
-int ab3100_get_register_page(struct ab3100 *ab3100,
+
+int ab3100_get_register_page_interruptible(struct ab3100 *ab3100,
u8 first_reg, u8 *regvals, u8 numregs)
{
int err;
@@ -258,9 +260,10 @@ int ab3100_get_register_page(struct ab3100 *ab3100,
mutex_unlock(&ab3100->access_mutex);
return err;
}
-EXPORT_SYMBOL(ab3100_get_register_page);
+EXPORT_SYMBOL(ab3100_get_register_page_interruptible);
+
-int ab3100_mask_and_set_register(struct ab3100 *ab3100,
+int ab3100_mask_and_set_register_interruptible(struct ab3100 *ab3100,
u8 reg, u8 andmask, u8 ormask)
{
u8 regandval[2] = {reg, 0};
@@ -328,7 +331,8 @@ int ab3100_mask_and_set_register(struct ab3100 *ab3100,
mutex_unlock(&ab3100->access_mutex);
return err;
}
-EXPORT_SYMBOL(ab3100_mask_and_set_register);
+EXPORT_SYMBOL(ab3100_mask_and_set_register_interruptible);
+
/*
* Register a simple callback for handling any AB3100 events.
@@ -371,7 +375,7 @@ static void ab3100_work(struct work_struct *work)
u32 fatevent;
int err;
- err = ab3100_get_register_page(ab3100, AB3100_EVENTA1,
+ err = ab3100_get_register_page_interruptible(ab3100, AB3100_EVENTA1,
event_regs, 3);
if (err)
goto err_event_wq;
@@ -417,7 +421,7 @@ static irqreturn_t ab3100_irq_handler(int irq, void *data)
* stuff and we will re-enable the interrupts once th
* worker has finished.
*/
- disable_irq(ab3100->i2c_client->irq);
+ disable_irq_nosync(irq);
schedule_work(&ab3100->work);
return IRQ_HANDLED;
}
@@ -435,7 +439,7 @@ static int ab3100_registers_print(struct seq_file *s, void *p)
seq_printf(s, "AB3100 registers:\n");
for (reg = 0; reg < 0xff; reg++) {
- ab3100_get_register(ab3100, reg, &value);
+ ab3100_get_register_interruptible(ab3100, reg, &value);
seq_printf(s, "[0x%x]: 0x%x\n", reg, value);
}
return 0;
@@ -465,14 +469,14 @@ static int ab3100_get_set_reg_open_file(struct inode *inode, struct file *file)
return 0;
}
-static int ab3100_get_set_reg(struct file *file,
- const char __user *user_buf,
- size_t count, loff_t *ppos)
+static ssize_t ab3100_get_set_reg(struct file *file,
+ const char __user *user_buf,
+ size_t count, loff_t *ppos)
{
struct ab3100_get_set_reg_priv *priv = file->private_data;
struct ab3100 *ab3100 = priv->ab3100;
char buf[32];
- int buf_size;
+ ssize_t buf_size;
int regp;
unsigned long user_reg;
int err;
@@ -515,7 +519,7 @@ static int ab3100_get_set_reg(struct file *file,
u8 reg = (u8) user_reg;
u8 regvalue;
- ab3100_get_register(ab3100, reg, &regvalue);
+ ab3100_get_register_interruptible(ab3100, reg, &regvalue);
dev_info(ab3100->dev,
"debug read AB3100 reg[0x%02x]: 0x%02x\n",
@@ -547,8 +551,8 @@ static int ab3100_get_set_reg(struct file *file,
return -EINVAL;
value = (u8) user_value;
- ab3100_set_register(ab3100, reg, value);
- ab3100_get_register(ab3100, reg, &regvalue);
+ ab3100_set_register_interruptible(ab3100, reg, value);
+ ab3100_get_register_interruptible(ab3100, reg, &regvalue);
dev_info(ab3100->dev,
"debug write reg[0x%02x] with 0x%02x, "
@@ -662,7 +666,7 @@ ab3100_init_settings[] = {
.setting = 0x01
}, {
.abreg = AB3100_IMRB1,
- .setting = 0xFF
+ .setting = 0xBF
}, {
.abreg = AB3100_IMRB2,
.setting = 0xFF
@@ -696,7 +700,7 @@ static int __init ab3100_setup(struct ab3100 *ab3100)
int i;
for (i = 0; i < ARRAY_SIZE(ab3100_init_settings); i++) {
- err = ab3100_set_register(ab3100,
+ err = ab3100_set_register_interruptible(ab3100,
ab3100_init_settings[i].abreg,
ab3100_init_settings[i].setting);
if (err)
@@ -705,14 +709,14 @@ static int __init ab3100_setup(struct ab3100 *ab3100)
/*
* Special trick to make the AB3100 use the 32kHz clock (RTC)
- * bit 3 in test registe 0x02 is a special, undocumented test
+ * bit 3 in test register 0x02 is a special, undocumented test
* register bit that only exist in AB3100 P1E
*/
if (ab3100->chip_id == 0xc4) {
dev_warn(ab3100->dev,
"AB3100 P1E variant detected, "
"forcing chip to 32KHz\n");
- err = ab3100_set_test_register(ab3100, 0x02, 0x08);
+ err = ab3100_set_test_register_interruptible(ab3100, 0x02, 0x08);
}
exit_no_setup:
@@ -833,6 +837,8 @@ static int __init ab3100_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct ab3100 *ab3100;
+ struct ab3100_platform_data *ab3100_plf_data =
+ client->dev.platform_data;
int err;
int i;
@@ -852,8 +858,8 @@ static int __init ab3100_probe(struct i2c_client *client,
i2c_set_clientdata(client, ab3100);
/* Read chip ID register */
- err = ab3100_get_register(ab3100, AB3100_CID,
- &ab3100->chip_id);
+ err = ab3100_get_register_interruptible(ab3100, AB3100_CID,
+ &ab3100->chip_id);
if (err) {
dev_err(&client->dev,
"could not communicate with the AB3100 analog "
@@ -916,6 +922,8 @@ static int __init ab3100_probe(struct i2c_client *client,
for (i = 0; i < ARRAY_SIZE(ab3100_platform_devs); i++) {
ab3100_platform_devs[i]->dev.parent =
&client->dev;
+ ab3100_platform_devs[i]->dev.platform_data =
+ ab3100_plf_data;
platform_set_drvdata(ab3100_platform_devs[i], ab3100);
}
diff --git a/drivers/mfd/ab3100-otp.c b/drivers/mfd/ab3100-otp.c
new file mode 100644
index 00000000000..0499b2031a2
--- /dev/null
+++ b/drivers/mfd/ab3100-otp.c
@@ -0,0 +1,268 @@
+/*
+ * drivers/mfd/ab3100_otp.c
+ *
+ * Copyright (C) 2007-2009 ST-Ericsson AB
+ * License terms: GNU General Public License (GPL) version 2
+ * Driver to read out OTP from the AB3100 Mixed-signal circuit
+ * Author: Linus Walleij <linus.walleij@stericsson.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/ab3100.h>
+#include <linux/debugfs.h>
+
+/* The OTP registers */
+#define AB3100_OTP0 0xb0
+#define AB3100_OTP1 0xb1
+#define AB3100_OTP2 0xb2
+#define AB3100_OTP3 0xb3
+#define AB3100_OTP4 0xb4
+#define AB3100_OTP5 0xb5
+#define AB3100_OTP6 0xb6
+#define AB3100_OTP7 0xb7
+#define AB3100_OTPP 0xbf
+
+/**
+ * struct ab3100_otp
+ * @dev containing device
+ * @ab3100 a pointer to the parent ab3100 device struct
+ * @locked whether the OTP is locked, after locking, no more bits
+ * can be changed but before locking it is still possible
+ * to change bits from 1->0.
+ * @freq clocking frequency for the OTP, this frequency is either
+ * 32768Hz or 1MHz/30
+ * @paf product activation flag, indicates whether this is a real
+ * product (paf true) or a lab board etc (paf false)
+ * @imeich if this is set it is possible to override the
+ * IMEI number found in the tac, fac and svn fields with
+ * (secured) software
+ * @cid customer ID
+ * @tac type allocation code of the IMEI
+ * @fac final assembly code of the IMEI
+ * @svn software version number of the IMEI
+ * @debugfs a debugfs file used when dumping to file
+ */
+struct ab3100_otp {
+ struct device *dev;
+ struct ab3100 *ab3100;
+ bool locked;
+ u32 freq;
+ bool paf;
+ bool imeich;
+ u16 cid:14;
+ u32 tac:20;
+ u8 fac;
+ u32 svn:20;
+ struct dentry *debugfs;
+};
+
+static int __init ab3100_otp_read(struct ab3100_otp *otp)
+{
+ struct ab3100 *ab = otp->ab3100;
+ u8 otpval[8];
+ u8 otpp;
+ int err;
+
+ err = ab3100_get_register_interruptible(ab, AB3100_OTPP, &otpp);
+ if (err) {
+ dev_err(otp->dev, "unable to read OTPP register\n");
+ return err;
+ }
+
+ err = ab3100_get_register_page_interruptible(ab, AB3100_OTP0,
+ otpval, 8);
+ if (err) {
+ dev_err(otp->dev, "unable to read OTP register page\n");
+ return err;
+ }
+
+ /* Cache OTP properties, they never change by nature */
+ otp->locked = (otpp & 0x80);
+ otp->freq = (otpp & 0x40) ? 32768 : 34100;
+ otp->paf = (otpval[1] & 0x80);
+ otp->imeich = (otpval[1] & 0x40);
+ otp->cid = ((otpval[1] << 8) | otpval[0]) & 0x3fff;
+ otp->tac = ((otpval[4] & 0x0f) << 16) | (otpval[3] << 8) | otpval[2];
+ otp->fac = ((otpval[5] & 0x0f) << 4) | (otpval[4] >> 4);
+ otp->svn = (otpval[7] << 12) | (otpval[6] << 4) | (otpval[5] >> 4);
+ return 0;
+}
+
+/*
+ * This is a simple debugfs human-readable file that dumps out
+ * the contents of the OTP.
+ */
+#ifdef CONFIG_DEBUGFS
+static int show_otp(struct seq_file *s, void *v)
+{
+ struct ab3100_otp *otp = s->private;
+ int err;
+
+ seq_printf(s, "OTP is %s\n", otp->locked ? "LOCKED" : "UNLOCKED");
+ seq_printf(s, "OTP clock switch startup is %uHz\n", otp->freq);
+ seq_printf(s, "PAF is %s\n", otp->paf ? "SET" : "NOT SET");
+ seq_printf(s, "IMEI is %s\n", otp->imeich ?
+ "CHANGEABLE" : "NOT CHANGEABLE");
+ seq_printf(s, "CID: 0x%04x (decimal: %d)\n", otp->cid, otp->cid);
+ seq_printf(s, "IMEI: %u-%u-%u\n", otp->tac, otp->fac, otp->svn);
+ return 0;
+}
+
+static int ab3100_otp_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ab3100_otp_show, inode->i_private);
+}
+
+static const struct file_operations ab3100_otp_operations = {
+ .open = ab3100_otp_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int __init ab3100_otp_init_debugfs(struct device *dev,
+ struct ab3100_otp *otp)
+{
+ otp->debugfs = debugfs_create_file("ab3100_otp", S_IFREG | S_IRUGO,
+ NULL, otp,
+ &ab3100_otp_operations);
+ if (!otp->debugfs) {
+ dev_err(dev, "AB3100 debugfs OTP file registration failed!\n");
+ return err;
+ }
+}
+
+static void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp)
+{
+ debugfs_remove_file(otp->debugfs);
+}
+#else
+/* Compile this out if debugfs not selected */
+static inline int __init ab3100_otp_init_debugfs(struct device *dev,
+ struct ab3100_otp *otp)
+{
+ return 0;
+}
+
+static inline void __exit ab3100_otp_exit_debugfs(struct ab3100_otp *otp)
+{
+}
+#endif
+
+#define SHOW_AB3100_ATTR(name) \
+static ssize_t ab3100_otp_##name##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{\
+ struct ab3100_otp *otp = dev_get_drvdata(dev); \
+ return sprintf(buf, "%u\n", otp->name); \
+}
+
+SHOW_AB3100_ATTR(locked)
+SHOW_AB3100_ATTR(freq)
+SHOW_AB3100_ATTR(paf)
+SHOW_AB3100_ATTR(imeich)
+SHOW_AB3100_ATTR(cid)
+SHOW_AB3100_ATTR(fac)
+SHOW_AB3100_ATTR(tac)
+SHOW_AB3100_ATTR(svn)
+
+static struct device_attribute ab3100_otp_attrs[] = {
+ __ATTR(locked, S_IRUGO, ab3100_otp_locked_show, NULL),
+ __ATTR(freq, S_IRUGO, ab3100_otp_freq_show, NULL),
+ __ATTR(paf, S_IRUGO, ab3100_otp_paf_show, NULL),
+ __ATTR(imeich, S_IRUGO, ab3100_otp_imeich_show, NULL),
+ __ATTR(cid, S_IRUGO, ab3100_otp_cid_show, NULL),
+ __ATTR(fac, S_IRUGO, ab3100_otp_fac_show, NULL),
+ __ATTR(tac, S_IRUGO, ab3100_otp_tac_show, NULL),
+ __ATTR(svn, S_IRUGO, ab3100_otp_svn_show, NULL),
+};
+
+static int __init ab3100_otp_probe(struct platform_device *pdev)
+{
+ struct ab3100_otp *otp;
+ int err = 0;
+ int i;
+
+ otp = kzalloc(sizeof(struct ab3100_otp), GFP_KERNEL);
+ if (!otp) {
+ dev_err(&pdev->dev, "could not allocate AB3100 OTP device\n");
+ return -ENOMEM;
+ }
+ otp->dev = &pdev->dev;
+
+ /* Replace platform data coming in with a local struct */
+ otp->ab3100 = platform_get_drvdata(pdev);
+ platform_set_drvdata(pdev, otp);
+
+ err = ab3100_otp_read(otp);
+ if (err)
+ return err;
+
+ dev_info(&pdev->dev, "AB3100 OTP readout registered\n");
+
+ /* sysfs entries */
+ for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++) {
+ err = device_create_file(&pdev->dev,
+ &ab3100_otp_attrs[i]);
+ if (err)
+ goto out_no_sysfs;
+ }
+
+ /* debugfs entries */
+ err = ab3100_otp_init_debugfs(&pdev->dev, otp);
+ if (err)
+ goto out_no_debugfs;
+
+ return 0;
+
+out_no_sysfs:
+ for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++)
+ device_remove_file(&pdev->dev,
+ &ab3100_otp_attrs[i]);
+out_no_debugfs:
+ kfree(otp);
+ return err;
+}
+
+static int __exit ab3100_otp_remove(struct platform_device *pdev)
+{
+ struct ab3100_otp *otp = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ab3100_otp_attrs); i++)
+ device_remove_file(&pdev->dev,
+ &ab3100_otp_attrs[i]);
+ ab3100_otp_exit_debugfs(otp);
+ kfree(otp);
+ return 0;
+}
+
+static struct platform_driver ab3100_otp_driver = {
+ .driver = {
+ .name = "ab3100-otp",
+ .owner = THIS_MODULE,
+ },
+ .remove = __exit_p(ab3100_otp_remove),
+};
+
+static int __init ab3100_otp_init(void)
+{
+ return platform_driver_probe(&ab3100_otp_driver,
+ ab3100_otp_probe);
+}
+
+static void __exit ab3100_otp_exit(void)
+{
+ platform_driver_unregister(&ab3100_otp_driver);
+}
+
+module_init(ab3100_otp_init);
+module_exit(ab3100_otp_exit);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>");
+MODULE_DESCRIPTION("AB3100 OTP Readout Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/dm355evm_msp.c b/drivers/mfd/dm355evm_msp.c
index 5b6e58a3ba4..3d4a861976c 100644
--- a/drivers/mfd/dm355evm_msp.c
+++ b/drivers/mfd/dm355evm_msp.c
@@ -107,8 +107,16 @@ static const u8 msp_gpios[] = {
MSP_GPIO(2, SWITCH1), MSP_GPIO(3, SWITCH1),
MSP_GPIO(4, SWITCH1),
/* switches on MMC/SD sockets */
- MSP_GPIO(1, SDMMC), MSP_GPIO(2, SDMMC), /* mmc0 WP, nCD */
- MSP_GPIO(3, SDMMC), MSP_GPIO(4, SDMMC), /* mmc1 WP, nCD */
+ /*
+ * Note: EVMDM355_ECP_VA4.pdf suggests that Bit 2 and 4 should be
+ * checked for card detection. However on the EVM bit 1 and 3 gives
+ * this status, for 0 and 1 instance respectively. The pdf also
+ * suggests that Bit 1 and 3 should be checked for write protection.
+ * However on the EVM bit 2 and 4 gives this status,for 0 and 1
+ * instance respectively.
+ */
+ MSP_GPIO(2, SDMMC), MSP_GPIO(1, SDMMC), /* mmc0 WP, nCD */
+ MSP_GPIO(4, SDMMC), MSP_GPIO(3, SDMMC), /* mmc1 WP, nCD */
};
#define MSP_GPIO_REG(offset) (msp_gpios[(offset)] >> 3)
diff --git a/drivers/mfd/ezx-pcap.c b/drivers/mfd/ezx-pcap.c
index c1de4afa89a..016be4938e4 100644
--- a/drivers/mfd/ezx-pcap.c
+++ b/drivers/mfd/ezx-pcap.c
@@ -17,6 +17,7 @@
#include <linux/irq.h>
#include <linux/mfd/ezx-pcap.h>
#include <linux/spi/spi.h>
+#include <linux/gpio.h>
#define PCAP_ADC_MAXQ 8
struct pcap_adc_request {
@@ -106,11 +107,35 @@ int ezx_pcap_read(struct pcap_chip *pcap, u8 reg_num, u32 *value)
}
EXPORT_SYMBOL_GPL(ezx_pcap_read);
+int ezx_pcap_set_bits(struct pcap_chip *pcap, u8 reg_num, u32 mask, u32 val)
+{
+ int ret;
+ u32 tmp = PCAP_REGISTER_READ_OP_BIT |
+ (reg_num << PCAP_REGISTER_ADDRESS_SHIFT);
+
+ mutex_lock(&pcap->io_mutex);
+ ret = ezx_pcap_putget(pcap, &tmp);
+ if (ret)
+ goto out_unlock;
+
+ tmp &= (PCAP_REGISTER_VALUE_MASK & ~mask);
+ tmp |= (val & mask) | PCAP_REGISTER_WRITE_OP_BIT |
+ (reg_num << PCAP_REGISTER_ADDRESS_SHIFT);
+
+ ret = ezx_pcap_putget(pcap, &tmp);
+out_unlock:
+ mutex_unlock(&pcap->io_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(ezx_pcap_set_bits);
+
/* IRQ */
-static inline unsigned int irq2pcap(struct pcap_chip *pcap, int irq)
+int irq_to_pcap(struct pcap_chip *pcap, int irq)
{
- return 1 << (irq - pcap->irq_base);
+ return irq - pcap->irq_base;
}
+EXPORT_SYMBOL_GPL(irq_to_pcap);
int pcap_to_irq(struct pcap_chip *pcap, int irq)
{
@@ -122,7 +147,7 @@ static void pcap_mask_irq(unsigned int irq)
{
struct pcap_chip *pcap = get_irq_chip_data(irq);
- pcap->msr |= irq2pcap(pcap, irq);
+ pcap->msr |= 1 << irq_to_pcap(pcap, irq);
queue_work(pcap->workqueue, &pcap->msr_work);
}
@@ -130,7 +155,7 @@ static void pcap_unmask_irq(unsigned int irq)
{
struct pcap_chip *pcap = get_irq_chip_data(irq);
- pcap->msr &= ~irq2pcap(pcap, irq);
+ pcap->msr &= ~(1 << irq_to_pcap(pcap, irq));
queue_work(pcap->workqueue, &pcap->msr_work);
}
@@ -154,34 +179,38 @@ static void pcap_isr_work(struct work_struct *work)
u32 msr, isr, int_sel, service;
int irq;
- ezx_pcap_read(pcap, PCAP_REG_MSR, &msr);
- ezx_pcap_read(pcap, PCAP_REG_ISR, &isr);
+ do {
+ ezx_pcap_read(pcap, PCAP_REG_MSR, &msr);
+ ezx_pcap_read(pcap, PCAP_REG_ISR, &isr);
- /* We cant service/ack irqs that are assigned to port 2 */
- if (!(pdata->config & PCAP_SECOND_PORT)) {
- ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel);
- isr &= ~int_sel;
- }
- ezx_pcap_write(pcap, PCAP_REG_ISR, isr);
+ /* We cant service/ack irqs that are assigned to port 2 */
+ if (!(pdata->config & PCAP_SECOND_PORT)) {
+ ezx_pcap_read(pcap, PCAP_REG_INT_SEL, &int_sel);
+ isr &= ~int_sel;
+ }
- local_irq_disable();
- service = isr & ~msr;
+ ezx_pcap_write(pcap, PCAP_REG_MSR, isr | msr);
+ ezx_pcap_write(pcap, PCAP_REG_ISR, isr);
- for (irq = pcap->irq_base; service; service >>= 1, irq++) {
- if (service & 1) {
- struct irq_desc *desc = irq_to_desc(irq);
+ local_irq_disable();
+ service = isr & ~msr;
+ for (irq = pcap->irq_base; service; service >>= 1, irq++) {
+ if (service & 1) {
+ struct irq_desc *desc = irq_to_desc(irq);
- if (WARN(!desc, KERN_WARNING
- "Invalid PCAP IRQ %d\n", irq))
- break;
+ if (WARN(!desc, KERN_WARNING
+ "Invalid PCAP IRQ %d\n", irq))
+ break;
- if (desc->status & IRQ_DISABLED)
- note_interrupt(irq, desc, IRQ_NONE);
- else
- desc->handle_irq(irq, desc);
+ if (desc->status & IRQ_DISABLED)
+ note_interrupt(irq, desc, IRQ_NONE);
+ else
+ desc->handle_irq(irq, desc);
+ }
}
- }
- local_irq_enable();
+ local_irq_enable();
+ ezx_pcap_write(pcap, PCAP_REG_MSR, pcap->msr);
+ } while (gpio_get_value(irq_to_gpio(pcap->spi->irq)));
}
static void pcap_irq_handler(unsigned int irq, struct irq_desc *desc)
@@ -194,6 +223,19 @@ static void pcap_irq_handler(unsigned int irq, struct irq_desc *desc)
}
/* ADC */
+void pcap_set_ts_bits(struct pcap_chip *pcap, u32 bits)
+{
+ u32 tmp;
+
+ mutex_lock(&pcap->adc_mutex);
+ ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp);
+ tmp &= ~(PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR);
+ tmp |= bits & (PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR);
+ ezx_pcap_write(pcap, PCAP_REG_ADC, tmp);
+ mutex_unlock(&pcap->adc_mutex);
+}
+EXPORT_SYMBOL_GPL(pcap_set_ts_bits);
+
static void pcap_disable_adc(struct pcap_chip *pcap)
{
u32 tmp;
@@ -216,15 +258,16 @@ static void pcap_adc_trigger(struct pcap_chip *pcap)
mutex_unlock(&pcap->adc_mutex);
return;
}
- mutex_unlock(&pcap->adc_mutex);
-
- /* start conversion on requested bank */
- tmp = pcap->adc_queue[head]->flags | PCAP_ADC_ADEN;
+ /* start conversion on requested bank, save TS_M bits */
+ ezx_pcap_read(pcap, PCAP_REG_ADC, &tmp);
+ tmp &= (PCAP_ADC_TS_M_MASK | PCAP_ADC_TS_REF_LOWPWR);
+ tmp |= pcap->adc_queue[head]->flags | PCAP_ADC_ADEN;
if (pcap->adc_queue[head]->bank == PCAP_ADC_BANK_1)
tmp |= PCAP_ADC_AD_SEL1;
ezx_pcap_write(pcap, PCAP_REG_ADC, tmp);
+ mutex_unlock(&pcap->adc_mutex);
ezx_pcap_write(pcap, PCAP_REG_ADR, PCAP_ADR_ASC);
}
@@ -499,7 +542,7 @@ static void __exit ezx_pcap_exit(void)
spi_unregister_driver(&ezxpcap_driver);
}
-module_init(ezx_pcap_init);
+subsys_initcall(ezx_pcap_init);
module_exit(ezx_pcap_exit);
MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/mc13783-core.c b/drivers/mfd/mc13783-core.c
new file mode 100644
index 00000000000..e354d2912ef
--- /dev/null
+++ b/drivers/mfd/mc13783-core.c
@@ -0,0 +1,427 @@
+/*
+ * Copyright 2009 Pengutronix, Sascha Hauer <s.hauer@pengutronix.de>
+ *
+ * This code is in parts based on wm8350-core.c and pcf50633-core.c
+ *
+ * Initial development of this code was funded by
+ * Phytec Messtechnik GmbH, http://www.phytec.de
+ *
+ * This program 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/mfd/mc13783-private.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/mc13783.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/spi/spi.h>
+#include <linux/uaccess.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+
+#define MC13783_MAX_REG_NUM 0x3f
+#define MC13783_FRAME_MASK 0x00ffffff
+#define MC13783_MAX_REG_NUM 0x3f
+#define MC13783_REG_NUM_SHIFT 0x19
+#define MC13783_WRITE_BIT_SHIFT 31
+
+static inline int spi_rw(struct spi_device *spi, u8 * buf, size_t len)
+{
+ struct spi_transfer t = {
+ .tx_buf = (const void *)buf,
+ .rx_buf = buf,
+ .len = len,
+ .cs_change = 0,
+ .delay_usecs = 0,
+ };
+ struct spi_message m;
+
+ spi_message_init(&m);
+ spi_message_add_tail(&t, &m);
+ if (spi_sync(spi, &m) != 0 || m.status != 0)
+ return -EINVAL;
+ return len - m.actual_length;
+}
+
+static int mc13783_read(struct mc13783 *mc13783, int reg_num, u32 *reg_val)
+{
+ unsigned int frame = 0;
+ int ret = 0;
+
+ if (reg_num > MC13783_MAX_REG_NUM)
+ return -EINVAL;
+
+ frame |= reg_num << MC13783_REG_NUM_SHIFT;
+
+ ret = spi_rw(mc13783->spi_device, (u8 *)&frame, 4);
+
+ *reg_val = frame & MC13783_FRAME_MASK;
+
+ return ret;
+}
+
+static int mc13783_write(struct mc13783 *mc13783, int reg_num, u32 reg_val)
+{
+ unsigned int frame = 0;
+
+ if (reg_num > MC13783_MAX_REG_NUM)
+ return -EINVAL;
+
+ frame |= (1 << MC13783_WRITE_BIT_SHIFT);
+ frame |= reg_num << MC13783_REG_NUM_SHIFT;
+ frame |= reg_val & MC13783_FRAME_MASK;
+
+ return spi_rw(mc13783->spi_device, (u8 *)&frame, 4);
+}
+
+int mc13783_reg_read(struct mc13783 *mc13783, int reg_num, u32 *reg_val)
+{
+ int ret;
+
+ mutex_lock(&mc13783->io_lock);
+ ret = mc13783_read(mc13783, reg_num, reg_val);
+ mutex_unlock(&mc13783->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mc13783_reg_read);
+
+int mc13783_reg_write(struct mc13783 *mc13783, int reg_num, u32 reg_val)
+{
+ int ret;
+
+ mutex_lock(&mc13783->io_lock);
+ ret = mc13783_write(mc13783, reg_num, reg_val);
+ mutex_unlock(&mc13783->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mc13783_reg_write);
+
+/**
+ * mc13783_set_bits - Bitmask write
+ *
+ * @mc13783: Pointer to mc13783 control structure
+ * @reg: Register to access
+ * @mask: Mask of bits to change
+ * @val: Value to set for masked bits
+ */
+int mc13783_set_bits(struct mc13783 *mc13783, int reg, u32 mask, u32 val)
+{
+ u32 tmp;
+ int ret;
+
+ mutex_lock(&mc13783->io_lock);
+
+ ret = mc13783_read(mc13783, reg, &tmp);
+ tmp = (tmp & ~mask) | val;
+ if (ret == 0)
+ ret = mc13783_write(mc13783, reg, tmp);
+
+ mutex_unlock(&mc13783->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mc13783_set_bits);
+
+int mc13783_register_irq(struct mc13783 *mc13783, int irq,
+ void (*handler) (int, void *), void *data)
+{
+ if (irq < 0 || irq > MC13783_NUM_IRQ || !handler)
+ return -EINVAL;
+
+ if (WARN_ON(mc13783->irq_handler[irq].handler))
+ return -EBUSY;
+
+ mutex_lock(&mc13783->io_lock);
+ mc13783->irq_handler[irq].handler = handler;
+ mc13783->irq_handler[irq].data = data;
+ mutex_unlock(&mc13783->io_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mc13783_register_irq);
+
+int mc13783_free_irq(struct mc13783 *mc13783, int irq)
+{
+ if (irq < 0 || irq > MC13783_NUM_IRQ)
+ return -EINVAL;
+
+ mutex_lock(&mc13783->io_lock);
+ mc13783->irq_handler[irq].handler = NULL;
+ mutex_unlock(&mc13783->io_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mc13783_free_irq);
+
+static void mc13783_irq_work(struct work_struct *work)
+{
+ struct mc13783 *mc13783 = container_of(work, struct mc13783, work);
+ int i;
+ unsigned int adc_sts;
+
+ /* check if the adc has finished any completion */
+ mc13783_reg_read(mc13783, MC13783_REG_INTERRUPT_STATUS_0, &adc_sts);
+ mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_STATUS_0,
+ adc_sts & MC13783_INT_STAT_ADCDONEI);
+
+ if (adc_sts & MC13783_INT_STAT_ADCDONEI)
+ complete_all(&mc13783->adc_done);
+
+ for (i = 0; i < MC13783_NUM_IRQ; i++)
+ if (mc13783->irq_handler[i].handler)
+ mc13783->irq_handler[i].handler(i,
+ mc13783->irq_handler[i].data);
+ enable_irq(mc13783->irq);
+}
+
+static irqreturn_t mc13783_interrupt(int irq, void *dev_id)
+{
+ struct mc13783 *mc13783 = dev_id;
+
+ disable_irq_nosync(irq);
+
+ schedule_work(&mc13783->work);
+ return IRQ_HANDLED;
+}
+
+/* set adc to ts interrupt mode, which generates touchscreen wakeup interrupt */
+static inline void mc13783_adc_set_ts_irq_mode(struct mc13783 *mc13783)
+{
+ unsigned int reg_adc0, reg_adc1;
+
+ reg_adc0 = MC13783_ADC0_ADREFEN | MC13783_ADC0_ADREFMODE
+ | MC13783_ADC0_TSMOD0;
+ reg_adc1 = MC13783_ADC1_ADEN | MC13783_ADC1_ADTRIGIGN;
+
+ mc13783_reg_write(mc13783, MC13783_REG_ADC_0, reg_adc0);
+ mc13783_reg_write(mc13783, MC13783_REG_ADC_1, reg_adc1);
+}
+
+int mc13783_adc_do_conversion(struct mc13783 *mc13783, unsigned int mode,
+ unsigned int channel, unsigned int *sample)
+{
+ unsigned int reg_adc0, reg_adc1;
+ int i;
+
+ mutex_lock(&mc13783->adc_conv_lock);
+
+ /* set up auto incrementing anyway to make quick read */
+ reg_adc0 = MC13783_ADC0_ADINC1 | MC13783_ADC0_ADINC2;
+ /* enable the adc, ignore external triggering and set ASC to trigger
+ * conversion */
+ reg_adc1 = MC13783_ADC1_ADEN | MC13783_ADC1_ADTRIGIGN
+ | MC13783_ADC1_ASC;
+
+ /* setup channel number */
+ if (channel > 7)
+ reg_adc1 |= MC13783_ADC1_ADSEL;
+
+ switch (mode) {
+ case MC13783_ADC_MODE_TS:
+ /* enables touch screen reference mode and set touchscreen mode
+ * to position mode */
+ reg_adc0 |= MC13783_ADC0_ADREFEN | MC13783_ADC0_ADREFMODE
+ | MC13783_ADC0_TSMOD0 | MC13783_ADC0_TSMOD1;
+ reg_adc1 |= 4 << MC13783_ADC1_CHAN1_SHIFT;
+ break;
+ case MC13783_ADC_MODE_SINGLE_CHAN:
+ reg_adc1 |= (channel & 0x7) << MC13783_ADC1_CHAN0_SHIFT;
+ reg_adc1 |= MC13783_ADC1_RAND;
+ break;
+ case MC13783_ADC_MODE_MULT_CHAN:
+ reg_adc1 |= 4 << MC13783_ADC1_CHAN1_SHIFT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mc13783_reg_write(mc13783, MC13783_REG_ADC_0, reg_adc0);
+ mc13783_reg_write(mc13783, MC13783_REG_ADC_1, reg_adc1);
+
+ wait_for_completion_interruptible(&mc13783->adc_done);
+
+ for (i = 0; i < 4; i++)
+ mc13783_reg_read(mc13783, MC13783_REG_ADC_2, &sample[i]);
+
+ if (mc13783->ts_active)
+ mc13783_adc_set_ts_irq_mode(mc13783);
+
+ mutex_unlock(&mc13783->adc_conv_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mc13783_adc_do_conversion);
+
+void mc13783_adc_set_ts_status(struct mc13783 *mc13783, unsigned int status)
+{
+ mc13783->ts_active = status;
+}
+EXPORT_SYMBOL_GPL(mc13783_adc_set_ts_status);
+
+static int mc13783_check_revision(struct mc13783 *mc13783)
+{
+ u32 rev_id, rev1, rev2, finid, icid;
+
+ mc13783_read(mc13783, MC13783_REG_REVISION, &rev_id);
+
+ rev1 = (rev_id & 0x018) >> 3;
+ rev2 = (rev_id & 0x007);
+ icid = (rev_id & 0x01C0) >> 6;
+ finid = (rev_id & 0x01E00) >> 9;
+
+ /* Ver 0.2 is actually 3.2a. Report as 3.2 */
+ if ((rev1 == 0) && (rev2 == 2))
+ rev1 = 3;
+
+ if (rev1 == 0 || icid != 2) {
+ dev_err(mc13783->dev, "No MC13783 detected.\n");
+ return -ENODEV;
+ }
+
+ mc13783->revision = ((rev1 * 10) + rev2);
+ dev_info(mc13783->dev, "MC13783 Rev %d.%d FinVer %x detected\n", rev1,
+ rev2, finid);
+
+ return 0;
+}
+
+/*
+ * Register a client device. This is non-fatal since there is no need to
+ * fail the entire device init due to a single platform device failing.
+ */
+static void mc13783_client_dev_register(struct mc13783 *mc13783,
+ const char *name)
+{
+ struct mfd_cell cell = {};
+
+ cell.name = name;
+
+ mfd_add_devices(mc13783->dev, -1, &cell, 1, NULL, 0);
+}
+
+static int __devinit mc13783_probe(struct spi_device *spi)
+{
+ struct mc13783 *mc13783;
+ struct mc13783_platform_data *pdata = spi->dev.platform_data;
+ int ret;
+
+ mc13783 = kzalloc(sizeof(struct mc13783), GFP_KERNEL);
+ if (!mc13783)
+ return -ENOMEM;
+
+ dev_set_drvdata(&spi->dev, mc13783);
+ spi->mode = SPI_MODE_0 | SPI_CS_HIGH;
+ spi->bits_per_word = 32;
+ spi_setup(spi);
+
+ mc13783->spi_device = spi;
+ mc13783->dev = &spi->dev;
+ mc13783->irq = spi->irq;
+
+ INIT_WORK(&mc13783->work, mc13783_irq_work);
+ mutex_init(&mc13783->io_lock);
+ mutex_init(&mc13783->adc_conv_lock);
+ init_completion(&mc13783->adc_done);
+
+ if (pdata) {
+ mc13783->flags = pdata->flags;
+ mc13783->regulators = pdata->regulators;
+ mc13783->num_regulators = pdata->num_regulators;
+ }
+
+ if (mc13783_check_revision(mc13783)) {
+ ret = -ENODEV;
+ goto err_out;
+ }
+
+ /* clear and mask all interrupts */
+ mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_STATUS_0, 0x00ffffff);
+ mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_MASK_0, 0x00ffffff);
+ mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_STATUS_1, 0x00ffffff);
+ mc13783_reg_write(mc13783, MC13783_REG_INTERRUPT_MASK_1, 0x00ffffff);
+
+ /* unmask adcdone interrupts */
+ mc13783_set_bits(mc13783, MC13783_REG_INTERRUPT_MASK_0,
+ MC13783_INT_MASK_ADCDONEM, 0);
+
+ ret = request_irq(mc13783->irq, mc13783_interrupt,
+ IRQF_DISABLED | IRQF_TRIGGER_HIGH, "mc13783",
+ mc13783);
+ if (ret)
+ goto err_out;
+
+ if (mc13783->flags & MC13783_USE_CODEC)
+ mc13783_client_dev_register(mc13783, "mc13783-codec");
+ if (mc13783->flags & MC13783_USE_ADC)
+ mc13783_client_dev_register(mc13783, "mc13783-adc");
+ if (mc13783->flags & MC13783_USE_RTC)
+ mc13783_client_dev_register(mc13783, "mc13783-rtc");
+ if (mc13783->flags & MC13783_USE_REGULATOR)
+ mc13783_client_dev_register(mc13783, "mc13783-regulator");
+ if (mc13783->flags & MC13783_USE_TOUCHSCREEN)
+ mc13783_client_dev_register(mc13783, "mc13783-ts");
+
+ return 0;
+
+err_out:
+ kfree(mc13783);
+ return ret;
+}
+
+static int __devexit mc13783_remove(struct spi_device *spi)
+{
+ struct mc13783 *mc13783;
+
+ mc13783 = dev_get_drvdata(&spi->dev);
+
+ free_irq(mc13783->irq, mc13783);
+
+ mfd_remove_devices(&spi->dev);
+
+ return 0;
+}
+
+static struct spi_driver pmic_driver = {
+ .driver = {
+ .name = "mc13783",
+ .bus = &spi_bus_type,
+ .owner = THIS_MODULE,
+ },
+ .probe = mc13783_probe,
+ .remove = __devexit_p(mc13783_remove),
+};
+
+static int __init pmic_init(void)
+{
+ return spi_register_driver(&pmic_driver);
+}
+subsys_initcall(pmic_init);
+
+static void __exit pmic_exit(void)
+{
+ spi_unregister_driver(&pmic_driver);
+}
+module_exit(pmic_exit);
+
+MODULE_DESCRIPTION("Core/Protocol driver for Freescale MC13783 PMIC");
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c
index 54ddf3772e0..ae15e495e20 100644
--- a/drivers/mfd/mfd-core.c
+++ b/drivers/mfd/mfd-core.c
@@ -25,7 +25,7 @@ static int mfd_add_device(struct device *parent, int id,
int ret = -ENOMEM;
int r;
- pdev = platform_device_alloc(cell->name, id);
+ pdev = platform_device_alloc(cell->name, id + cell->id);
if (!pdev)
goto fail_alloc;
diff --git a/drivers/mfd/pcf50633-adc.c b/drivers/mfd/pcf50633-adc.c
index c2d05becfa9..3d31e97d6a4 100644
--- a/drivers/mfd/pcf50633-adc.c
+++ b/drivers/mfd/pcf50633-adc.c
@@ -73,15 +73,10 @@ static void trigger_next_adc_job_if_any(struct pcf50633 *pcf)
struct pcf50633_adc *adc = __to_adc(pcf);
int head;
- mutex_lock(&adc->queue_mutex);
-
head = adc->queue_head;
- if (!adc->queue[head]) {
- mutex_unlock(&adc->queue_mutex);
+ if (!adc->queue[head])
return;
- }
- mutex_unlock(&adc->queue_mutex);
adc_setup(pcf, adc->queue[head]->mux, adc->queue[head]->avg);
}
@@ -99,16 +94,17 @@ adc_enqueue_request(struct pcf50633 *pcf, struct pcf50633_adc_request *req)
if (adc->queue[tail]) {
mutex_unlock(&adc->queue_mutex);
+ dev_err(pcf->dev, "ADC queue is full, dropping request\n");
return -EBUSY;
}
adc->queue[tail] = req;
+ if (head == tail)
+ trigger_next_adc_job_if_any(pcf);
adc->queue_tail = (tail + 1) & (PCF50633_MAX_ADC_FIFO_DEPTH - 1);
mutex_unlock(&adc->queue_mutex);
- trigger_next_adc_job_if_any(pcf);
-
return 0;
}
@@ -124,6 +120,7 @@ pcf50633_adc_sync_read_callback(struct pcf50633 *pcf, void *param, int result)
int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg)
{
struct pcf50633_adc_request *req;
+ int err;
/* req is freed when the result is ready, in interrupt handler */
req = kzalloc(sizeof(*req), GFP_KERNEL);
@@ -136,9 +133,13 @@ int pcf50633_adc_sync_read(struct pcf50633 *pcf, int mux, int avg)
req->callback_param = req;
init_completion(&req->completion);
- adc_enqueue_request(pcf, req);
+ err = adc_enqueue_request(pcf, req);
+ if (err)
+ return err;
+
wait_for_completion(&req->completion);
+ /* FIXME by this time req might be already freed */
return req->result;
}
EXPORT_SYMBOL_GPL(pcf50633_adc_sync_read);
@@ -159,9 +160,7 @@ int pcf50633_adc_async_read(struct pcf50633 *pcf, int mux, int avg,
req->callback = callback;
req->callback_param = callback_param;
- adc_enqueue_request(pcf, req);
-
- return 0;
+ return adc_enqueue_request(pcf, req);
}
EXPORT_SYMBOL_GPL(pcf50633_adc_async_read);
@@ -184,7 +183,7 @@ static void pcf50633_adc_irq(int irq, void *data)
struct pcf50633_adc *adc = data;
struct pcf50633 *pcf = adc->pcf;
struct pcf50633_adc_request *req;
- int head;
+ int head, res;
mutex_lock(&adc->queue_mutex);
head = adc->queue_head;
@@ -199,12 +198,13 @@ static void pcf50633_adc_irq(int irq, void *data)
adc->queue_head = (head + 1) &
(PCF50633_MAX_ADC_FIFO_DEPTH - 1);
+ res = adc_result(pcf);
+ trigger_next_adc_job_if_any(pcf);
+
mutex_unlock(&adc->queue_mutex);
- req->callback(pcf, req->callback_param, adc_result(pcf));
+ req->callback(pcf, req->callback_param, res);
kfree(req);
-
- trigger_next_adc_job_if_any(pcf);
}
static int __devinit pcf50633_adc_probe(struct platform_device *pdev)
diff --git a/drivers/mfd/pcf50633-core.c b/drivers/mfd/pcf50633-core.c
index 8d3c38bf971..d26d7747175 100644
--- a/drivers/mfd/pcf50633-core.c
+++ b/drivers/mfd/pcf50633-core.c
@@ -444,7 +444,7 @@ static irqreturn_t pcf50633_irq(int irq, void *data)
get_device(pcf->dev);
disable_irq_nosync(pcf->irq);
- schedule_work(&pcf->irq_work);
+ queue_work(pcf->work_queue, &pcf->irq_work);
return IRQ_HANDLED;
}
@@ -575,6 +575,7 @@ static int __devinit pcf50633_probe(struct i2c_client *client,
pcf->dev = &client->dev;
pcf->i2c_client = client;
pcf->irq = client->irq;
+ pcf->work_queue = create_singlethread_workqueue("pcf50633");
INIT_WORK(&pcf->irq_work, pcf50633_irq_worker);
@@ -651,6 +652,7 @@ static int __devinit pcf50633_probe(struct i2c_client *client,
return 0;
err:
+ destroy_workqueue(pcf->work_queue);
kfree(pcf);
return ret;
}
@@ -661,6 +663,7 @@ static int __devexit pcf50633_remove(struct i2c_client *client)
int i;
free_irq(pcf->irq, pcf);
+ destroy_workqueue(pcf->work_queue);
platform_device_unregister(pcf->input_pdev);
platform_device_unregister(pcf->rtc_pdev);
diff --git a/drivers/mfd/twl4030-core.c b/drivers/mfd/twl4030-core.c
index ca54996ffd0..e424cf6d8e9 100644
--- a/drivers/mfd/twl4030-core.c
+++ b/drivers/mfd/twl4030-core.c
@@ -89,6 +89,12 @@
#define twl_has_madc() false
#endif
+#ifdef CONFIG_TWL4030_POWER
+#define twl_has_power() true
+#else
+#define twl_has_power() false
+#endif
+
#if defined(CONFIG_RTC_DRV_TWL4030) || defined(CONFIG_RTC_DRV_TWL4030_MODULE)
#define twl_has_rtc() true
#else
@@ -115,6 +121,12 @@
#define TWL4030_NUM_SLAVES 4
+#if defined(CONFIG_INPUT_TWL4030_PWRBUTTON) \
+ || defined(CONFIG_INPUT_TWL4030_PWBUTTON_MODULE)
+#define twl_has_pwrbutton() true
+#else
+#define twl_has_pwrbutton() false
+#endif
/* Base Address defns for twl4030_map[] */
@@ -538,6 +550,13 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features)
return PTR_ERR(child);
}
+ if (twl_has_pwrbutton()) {
+ child = add_child(1, "twl4030_pwrbutton",
+ NULL, 0, true, pdata->irq_base + 8 + 0, 0);
+ if (IS_ERR(child))
+ return PTR_ERR(child);
+ }
+
if (twl_has_regulator()) {
/*
child = add_regulator(TWL4030_REG_VPLL1, pdata->vpll1);
@@ -788,6 +807,10 @@ twl4030_probe(struct i2c_client *client, const struct i2c_device_id *id)
/* setup clock framework */
clocks_init(&client->dev);
+ /* load power event scripts */
+ if (twl_has_power() && pdata->power)
+ twl4030_power_init(pdata->power);
+
/* Maybe init the T2 Interrupt subsystem */
if (client->irq
&& pdata->irq_base
diff --git a/drivers/mfd/twl4030-irq.c b/drivers/mfd/twl4030-irq.c
index 7d430835655..fb194fe244c 100644
--- a/drivers/mfd/twl4030-irq.c
+++ b/drivers/mfd/twl4030-irq.c
@@ -424,7 +424,7 @@ static void twl4030_sih_do_edge(struct work_struct *work)
/* see what work we have */
spin_lock_irq(&sih_agent_lock);
edge_change = agent->edge_change;
- agent->edge_change = 0;;
+ agent->edge_change = 0;
sih = edge_change ? agent->sih : NULL;
spin_unlock_irq(&sih_agent_lock);
if (!sih)
diff --git a/drivers/mfd/twl4030-power.c b/drivers/mfd/twl4030-power.c
new file mode 100644
index 00000000000..d423e0c4176
--- /dev/null
+++ b/drivers/mfd/twl4030-power.c
@@ -0,0 +1,472 @@
+/*
+ * linux/drivers/i2c/chips/twl4030-power.c
+ *
+ * Handle TWL4030 Power initialization
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Copyright (C) 2006 Texas Instruments, Inc
+ *
+ * Written by Kalle Jokiniemi
+ * Peter De Schrijver <peter.de-schrijver@nokia.com>
+ * Several fixes by Amit Kucheria <amit.kucheria@verdurent.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General
+ * Public License. See the file "COPYING" in the main directory of this
+ * archive for more details.
+ *
+ * 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
+ */
+
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/i2c/twl4030.h>
+#include <linux/platform_device.h>
+
+#include <asm/mach-types.h>
+
+static u8 twl4030_start_script_address = 0x2b;
+
+#define PWR_P1_SW_EVENTS 0x10
+#define PWR_DEVOFF (1<<0)
+
+#define PHY_TO_OFF_PM_MASTER(p) (p - 0x36)
+#define PHY_TO_OFF_PM_RECEIVER(p) (p - 0x5b)
+
+/* resource - hfclk */
+#define R_HFCLKOUT_DEV_GRP PHY_TO_OFF_PM_RECEIVER(0xe6)
+
+/* PM events */
+#define R_P1_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x46)
+#define R_P2_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x47)
+#define R_P3_SW_EVENTS PHY_TO_OFF_PM_MASTER(0x48)
+#define R_CFG_P1_TRANSITION PHY_TO_OFF_PM_MASTER(0x36)
+#define R_CFG_P2_TRANSITION PHY_TO_OFF_PM_MASTER(0x37)
+#define R_CFG_P3_TRANSITION PHY_TO_OFF_PM_MASTER(0x38)
+
+#define LVL_WAKEUP 0x08
+
+#define ENABLE_WARMRESET (1<<4)
+
+#define END_OF_SCRIPT 0x3f
+
+#define R_SEQ_ADD_A2S PHY_TO_OFF_PM_MASTER(0x55)
+#define R_SEQ_ADD_S2A12 PHY_TO_OFF_PM_MASTER(0x56)
+#define R_SEQ_ADD_S2A3 PHY_TO_OFF_PM_MASTER(0x57)
+#define R_SEQ_ADD_WARM PHY_TO_OFF_PM_MASTER(0x58)
+#define R_MEMORY_ADDRESS PHY_TO_OFF_PM_MASTER(0x59)
+#define R_MEMORY_DATA PHY_TO_OFF_PM_MASTER(0x5a)
+
+#define R_PROTECT_KEY 0x0E
+#define R_KEY_1 0xC0
+#define R_KEY_2 0x0C
+
+/* resource configuration registers */
+
+#define DEVGROUP_OFFSET 0
+#define TYPE_OFFSET 1
+
+/* Bit positions */
+#define DEVGROUP_SHIFT 5
+#define DEVGROUP_MASK (7 << DEVGROUP_SHIFT)
+#define TYPE_SHIFT 0
+#define TYPE_MASK (7 << TYPE_SHIFT)
+#define TYPE2_SHIFT 3
+#define TYPE2_MASK (3 << TYPE2_SHIFT)
+
+static u8 res_config_addrs[] = {
+ [RES_VAUX1] = 0x17,
+ [RES_VAUX2] = 0x1b,
+ [RES_VAUX3] = 0x1f,
+ [RES_VAUX4] = 0x23,
+ [RES_VMMC1] = 0x27,
+ [RES_VMMC2] = 0x2b,
+ [RES_VPLL1] = 0x2f,
+ [RES_VPLL2] = 0x33,
+ [RES_VSIM] = 0x37,
+ [RES_VDAC] = 0x3b,
+ [RES_VINTANA1] = 0x3f,
+ [RES_VINTANA2] = 0x43,
+ [RES_VINTDIG] = 0x47,
+ [RES_VIO] = 0x4b,
+ [RES_VDD1] = 0x55,
+ [RES_VDD2] = 0x63,
+ [RES_VUSB_1V5] = 0x71,
+ [RES_VUSB_1V8] = 0x74,
+ [RES_VUSB_3V1] = 0x77,
+ [RES_VUSBCP] = 0x7a,
+ [RES_REGEN] = 0x7f,
+ [RES_NRES_PWRON] = 0x82,
+ [RES_CLKEN] = 0x85,
+ [RES_SYSEN] = 0x88,
+ [RES_HFCLKOUT] = 0x8b,
+ [RES_32KCLKOUT] = 0x8e,
+ [RES_RESET] = 0x91,
+ [RES_Main_Ref] = 0x94,
+};
+
+static int __init twl4030_write_script_byte(u8 address, u8 byte)
+{
+ int err;
+
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address,
+ R_MEMORY_ADDRESS);
+ if (err)
+ goto out;
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, byte,
+ R_MEMORY_DATA);
+out:
+ return err;
+}
+
+static int __init twl4030_write_script_ins(u8 address, u16 pmb_message,
+ u8 delay, u8 next)
+{
+ int err;
+
+ address *= 4;
+ err = twl4030_write_script_byte(address++, pmb_message >> 8);
+ if (err)
+ goto out;
+ err = twl4030_write_script_byte(address++, pmb_message & 0xff);
+ if (err)
+ goto out;
+ err = twl4030_write_script_byte(address++, delay);
+ if (err)
+ goto out;
+ err = twl4030_write_script_byte(address++, next);
+out:
+ return err;
+}
+
+static int __init twl4030_write_script(u8 address, struct twl4030_ins *script,
+ int len)
+{
+ int err;
+
+ for (; len; len--, address++, script++) {
+ if (len == 1) {
+ err = twl4030_write_script_ins(address,
+ script->pmb_message,
+ script->delay,
+ END_OF_SCRIPT);
+ if (err)
+ break;
+ } else {
+ err = twl4030_write_script_ins(address,
+ script->pmb_message,
+ script->delay,
+ address + 1);
+ if (err)
+ break;
+ }
+ }
+ return err;
+}
+
+static int __init twl4030_config_wakeup3_sequence(u8 address)
+{
+ int err;
+ u8 data;
+
+ /* Set SLEEP to ACTIVE SEQ address for P3 */
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address,
+ R_SEQ_ADD_S2A3);
+ if (err)
+ goto out;
+
+ /* P3 LVL_WAKEUP should be on LEVEL */
+ err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data,
+ R_P3_SW_EVENTS);
+ if (err)
+ goto out;
+ data |= LVL_WAKEUP;
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data,
+ R_P3_SW_EVENTS);
+out:
+ if (err)
+ pr_err("TWL4030 wakeup sequence for P3 config error\n");
+ return err;
+}
+
+static int __init twl4030_config_wakeup12_sequence(u8 address)
+{
+ int err = 0;
+ u8 data;
+
+ /* Set SLEEP to ACTIVE SEQ address for P1 and P2 */
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address,
+ R_SEQ_ADD_S2A12);
+ if (err)
+ goto out;
+
+ /* P1/P2 LVL_WAKEUP should be on LEVEL */
+ err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data,
+ R_P1_SW_EVENTS);
+ if (err)
+ goto out;
+
+ data |= LVL_WAKEUP;
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data,
+ R_P1_SW_EVENTS);
+ if (err)
+ goto out;
+
+ err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data,
+ R_P2_SW_EVENTS);
+ if (err)
+ goto out;
+
+ data |= LVL_WAKEUP;
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data,
+ R_P2_SW_EVENTS);
+ if (err)
+ goto out;
+
+ if (machine_is_omap_3430sdp() || machine_is_omap_ldp()) {
+ /* Disabling AC charger effect on sleep-active transitions */
+ err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &data,
+ R_CFG_P1_TRANSITION);
+ if (err)
+ goto out;
+ data &= ~(1<<1);
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, data ,
+ R_CFG_P1_TRANSITION);
+ if (err)
+ goto out;
+ }
+
+out:
+ if (err)
+ pr_err("TWL4030 wakeup sequence for P1 and P2" \
+ "config error\n");
+ return err;
+}
+
+static int __init twl4030_config_sleep_sequence(u8 address)
+{
+ int err;
+
+ /* Set ACTIVE to SLEEP SEQ address in T2 memory*/
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address,
+ R_SEQ_ADD_A2S);
+
+ if (err)
+ pr_err("TWL4030 sleep sequence config error\n");
+
+ return err;
+}
+
+static int __init twl4030_config_warmreset_sequence(u8 address)
+{
+ int err;
+ u8 rd_data;
+
+ /* Set WARM RESET SEQ address for P1 */
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, address,
+ R_SEQ_ADD_WARM);
+ if (err)
+ goto out;
+
+ /* P1/P2/P3 enable WARMRESET */
+ err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data,
+ R_P1_SW_EVENTS);
+ if (err)
+ goto out;
+
+ rd_data |= ENABLE_WARMRESET;
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data,
+ R_P1_SW_EVENTS);
+ if (err)
+ goto out;
+
+ err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data,
+ R_P2_SW_EVENTS);
+ if (err)
+ goto out;
+
+ rd_data |= ENABLE_WARMRESET;
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data,
+ R_P2_SW_EVENTS);
+ if (err)
+ goto out;
+
+ err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &rd_data,
+ R_P3_SW_EVENTS);
+ if (err)
+ goto out;
+
+ rd_data |= ENABLE_WARMRESET;
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, rd_data,
+ R_P3_SW_EVENTS);
+out:
+ if (err)
+ pr_err("TWL4030 warmreset seq config error\n");
+ return err;
+}
+
+static int __init twl4030_configure_resource(struct twl4030_resconfig *rconfig)
+{
+ int rconfig_addr;
+ int err;
+ u8 type;
+ u8 grp;
+
+ if (rconfig->resource > TOTAL_RESOURCES) {
+ pr_err("TWL4030 Resource %d does not exist\n",
+ rconfig->resource);
+ return -EINVAL;
+ }
+
+ rconfig_addr = res_config_addrs[rconfig->resource];
+
+ /* Set resource group */
+ err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, &grp,
+ rconfig_addr + DEVGROUP_OFFSET);
+ if (err) {
+ pr_err("TWL4030 Resource %d group could not be read\n",
+ rconfig->resource);
+ return err;
+ }
+
+ if (rconfig->devgroup >= 0) {
+ grp &= ~DEVGROUP_MASK;
+ grp |= rconfig->devgroup << DEVGROUP_SHIFT;
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER,
+ grp, rconfig_addr + DEVGROUP_OFFSET);
+ if (err < 0) {
+ pr_err("TWL4030 failed to program devgroup\n");
+ return err;
+ }
+ }
+
+ /* Set resource types */
+ err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, &type,
+ rconfig_addr + TYPE_OFFSET);
+ if (err < 0) {
+ pr_err("TWL4030 Resource %d type could not be read\n",
+ rconfig->resource);
+ return err;
+ }
+
+ if (rconfig->type >= 0) {
+ type &= ~TYPE_MASK;
+ type |= rconfig->type << TYPE_SHIFT;
+ }
+
+ if (rconfig->type2 >= 0) {
+ type &= ~TYPE2_MASK;
+ type |= rconfig->type2 << TYPE2_SHIFT;
+ }
+
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER,
+ type, rconfig_addr + TYPE_OFFSET);
+ if (err < 0) {
+ pr_err("TWL4030 failed to program resource type\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int __init load_twl4030_script(struct twl4030_script *tscript,
+ u8 address)
+{
+ int err;
+ static int order;
+
+ /* Make sure the script isn't going beyond last valid address (0x3f) */
+ if ((address + tscript->size) > END_OF_SCRIPT) {
+ pr_err("TWL4030 scripts too big error\n");
+ return -EINVAL;
+ }
+
+ err = twl4030_write_script(address, tscript->script, tscript->size);
+ if (err)
+ goto out;
+
+ if (tscript->flags & TWL4030_WRST_SCRIPT) {
+ err = twl4030_config_warmreset_sequence(address);
+ if (err)
+ goto out;
+ }
+ if (tscript->flags & TWL4030_WAKEUP12_SCRIPT) {
+ err = twl4030_config_wakeup12_sequence(address);
+ if (err)
+ goto out;
+ order = 1;
+ }
+ if (tscript->flags & TWL4030_WAKEUP3_SCRIPT) {
+ err = twl4030_config_wakeup3_sequence(address);
+ if (err)
+ goto out;
+ }
+ if (tscript->flags & TWL4030_SLEEP_SCRIPT)
+ if (order)
+ pr_warning("TWL4030: Bad order of scripts (sleep "\
+ "script before wakeup) Leads to boot"\
+ "failure on some boards\n");
+ err = twl4030_config_sleep_sequence(address);
+out:
+ return err;
+}
+
+void __init twl4030_power_init(struct twl4030_power_data *twl4030_scripts)
+{
+ int err = 0;
+ int i;
+ struct twl4030_resconfig *resconfig;
+ u8 address = twl4030_start_script_address;
+
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_1,
+ R_PROTECT_KEY);
+ if (err)
+ goto unlock;
+
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, R_KEY_2,
+ R_PROTECT_KEY);
+ if (err)
+ goto unlock;
+
+ for (i = 0; i < twl4030_scripts->num; i++) {
+ err = load_twl4030_script(twl4030_scripts->scripts[i], address);
+ if (err)
+ goto load;
+ address += twl4030_scripts->scripts[i]->size;
+ }
+
+ resconfig = twl4030_scripts->resource_config;
+ if (resconfig) {
+ while (resconfig->resource) {
+ err = twl4030_configure_resource(resconfig);
+ if (err)
+ goto resource;
+ resconfig++;
+
+ }
+ }
+
+ err = twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, R_PROTECT_KEY);
+ if (err)
+ pr_err("TWL4030 Unable to relock registers\n");
+ return;
+
+unlock:
+ if (err)
+ pr_err("TWL4030 Unable to unlock registers\n");
+ return;
+load:
+ if (err)
+ pr_err("TWL4030 failed to load scripts\n");
+ return;
+resource:
+ if (err)
+ pr_err("TWL4030 failed to configure resource\n");
+ return;
+}
diff --git a/drivers/mfd/wm831x-core.c b/drivers/mfd/wm831x-core.c
new file mode 100644
index 00000000000..49b7885c270
--- /dev/null
+++ b/drivers/mfd/wm831x-core.c
@@ -0,0 +1,1549 @@
+/*
+ * wm831x-core.c -- Device access for Wolfson WM831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/bcd.h>
+#include <linux/delay.h>
+#include <linux/mfd/core.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/mfd/wm831x/irq.h>
+#include <linux/mfd/wm831x/auxadc.h>
+#include <linux/mfd/wm831x/otp.h>
+#include <linux/mfd/wm831x/regulator.h>
+
+/* Current settings - values are 2*2^(reg_val/4) microamps. These are
+ * exported since they are used by multiple drivers.
+ */
+int wm831x_isinkv_values[WM831X_ISINK_MAX_ISEL] = {
+ 2,
+ 2,
+ 3,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 10,
+ 11,
+ 13,
+ 16,
+ 19,
+ 23,
+ 27,
+ 32,
+ 38,
+ 45,
+ 54,
+ 64,
+ 76,
+ 91,
+ 108,
+ 128,
+ 152,
+ 181,
+ 215,
+ 256,
+ 304,
+ 362,
+ 431,
+ 512,
+ 609,
+ 724,
+ 861,
+ 1024,
+ 1218,
+ 1448,
+ 1722,
+ 2048,
+ 2435,
+ 2896,
+ 3444,
+ 4096,
+ 4871,
+ 5793,
+ 6889,
+ 8192,
+ 9742,
+ 11585,
+ 13777,
+ 16384,
+ 19484,
+ 23170,
+ 27554,
+};
+EXPORT_SYMBOL_GPL(wm831x_isinkv_values);
+
+enum wm831x_parent {
+ WM8310 = 0,
+ WM8311 = 1,
+ WM8312 = 2,
+};
+
+static int wm831x_reg_locked(struct wm831x *wm831x, unsigned short reg)
+{
+ if (!wm831x->locked)
+ return 0;
+
+ switch (reg) {
+ case WM831X_WATCHDOG:
+ case WM831X_DC4_CONTROL:
+ case WM831X_ON_PIN_CONTROL:
+ case WM831X_BACKUP_CHARGER_CONTROL:
+ case WM831X_CHARGER_CONTROL_1:
+ case WM831X_CHARGER_CONTROL_2:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+/**
+ * wm831x_reg_unlock: Unlock user keyed registers
+ *
+ * The WM831x has a user key preventing writes to particularly
+ * critical registers. This function locks those registers,
+ * allowing writes to them.
+ */
+void wm831x_reg_lock(struct wm831x *wm831x)
+{
+ int ret;
+
+ ret = wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0);
+ if (ret == 0) {
+ dev_vdbg(wm831x->dev, "Registers locked\n");
+
+ mutex_lock(&wm831x->io_lock);
+ WARN_ON(wm831x->locked);
+ wm831x->locked = 1;
+ mutex_unlock(&wm831x->io_lock);
+ } else {
+ dev_err(wm831x->dev, "Failed to lock registers: %d\n", ret);
+ }
+
+}
+EXPORT_SYMBOL_GPL(wm831x_reg_lock);
+
+/**
+ * wm831x_reg_unlock: Unlock user keyed registers
+ *
+ * The WM831x has a user key preventing writes to particularly
+ * critical registers. This function locks those registers,
+ * preventing spurious writes.
+ */
+int wm831x_reg_unlock(struct wm831x *wm831x)
+{
+ int ret;
+
+ /* 0x9716 is the value required to unlock the registers */
+ ret = wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0x9716);
+ if (ret == 0) {
+ dev_vdbg(wm831x->dev, "Registers unlocked\n");
+
+ mutex_lock(&wm831x->io_lock);
+ WARN_ON(!wm831x->locked);
+ wm831x->locked = 0;
+ mutex_unlock(&wm831x->io_lock);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm831x_reg_unlock);
+
+static int wm831x_read(struct wm831x *wm831x, unsigned short reg,
+ int bytes, void *dest)
+{
+ int ret, i;
+ u16 *buf = dest;
+
+ BUG_ON(bytes % 2);
+ BUG_ON(bytes <= 0);
+
+ ret = wm831x->read_dev(wm831x, reg, bytes, dest);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < bytes / 2; i++) {
+ buf[i] = be16_to_cpu(buf[i]);
+
+ dev_vdbg(wm831x->dev, "Read %04x from R%d(0x%x)\n",
+ buf[i], reg + i, reg + i);
+ }
+
+ return 0;
+}
+
+/**
+ * wm831x_reg_read: Read a single WM831x register.
+ *
+ * @wm831x: Device to read from.
+ * @reg: Register to read.
+ */
+int wm831x_reg_read(struct wm831x *wm831x, unsigned short reg)
+{
+ unsigned short val;
+ int ret;
+
+ mutex_lock(&wm831x->io_lock);
+
+ ret = wm831x_read(wm831x, reg, 2, &val);
+
+ mutex_unlock(&wm831x->io_lock);
+
+ if (ret < 0)
+ return ret;
+ else
+ return val;
+}
+EXPORT_SYMBOL_GPL(wm831x_reg_read);
+
+/**
+ * wm831x_bulk_read: Read multiple WM831x registers
+ *
+ * @wm831x: Device to read from
+ * @reg: First register
+ * @count: Number of registers
+ * @buf: Buffer to fill.
+ */
+int wm831x_bulk_read(struct wm831x *wm831x, unsigned short reg,
+ int count, u16 *buf)
+{
+ int ret;
+
+ mutex_lock(&wm831x->io_lock);
+
+ ret = wm831x_read(wm831x, reg, count * 2, buf);
+
+ mutex_unlock(&wm831x->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm831x_bulk_read);
+
+static int wm831x_write(struct wm831x *wm831x, unsigned short reg,
+ int bytes, void *src)
+{
+ u16 *buf = src;
+ int i;
+
+ BUG_ON(bytes % 2);
+ BUG_ON(bytes <= 0);
+
+ for (i = 0; i < bytes / 2; i++) {
+ if (wm831x_reg_locked(wm831x, reg))
+ return -EPERM;
+
+ dev_vdbg(wm831x->dev, "Write %04x to R%d(0x%x)\n",
+ buf[i], reg + i, reg + i);
+
+ buf[i] = cpu_to_be16(buf[i]);
+ }
+
+ return wm831x->write_dev(wm831x, reg, bytes, src);
+}
+
+/**
+ * wm831x_reg_write: Write a single WM831x register.
+ *
+ * @wm831x: Device to write to.
+ * @reg: Register to write to.
+ * @val: Value to write.
+ */
+int wm831x_reg_write(struct wm831x *wm831x, unsigned short reg,
+ unsigned short val)
+{
+ int ret;
+
+ mutex_lock(&wm831x->io_lock);
+
+ ret = wm831x_write(wm831x, reg, 2, &val);
+
+ mutex_unlock(&wm831x->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm831x_reg_write);
+
+/**
+ * wm831x_set_bits: Set the value of a bitfield in a WM831x register
+ *
+ * @wm831x: Device to write to.
+ * @reg: Register to write to.
+ * @mask: Mask of bits to set.
+ * @val: Value to set (unshifted)
+ */
+int wm831x_set_bits(struct wm831x *wm831x, unsigned short reg,
+ unsigned short mask, unsigned short val)
+{
+ int ret;
+ u16 r;
+
+ mutex_lock(&wm831x->io_lock);
+
+ ret = wm831x_read(wm831x, reg, 2, &r);
+ if (ret < 0)
+ goto out;
+
+ r &= ~mask;
+ r |= val;
+
+ ret = wm831x_write(wm831x, reg, 2, &r);
+
+out:
+ mutex_unlock(&wm831x->io_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm831x_set_bits);
+
+/**
+ * wm831x_auxadc_read: Read a value from the WM831x AUXADC
+ *
+ * @wm831x: Device to read from.
+ * @input: AUXADC input to read.
+ */
+int wm831x_auxadc_read(struct wm831x *wm831x, enum wm831x_auxadc input)
+{
+ int tries = 10;
+ int ret, src;
+
+ mutex_lock(&wm831x->auxadc_lock);
+
+ ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL,
+ WM831X_AUX_ENA, WM831X_AUX_ENA);
+ if (ret < 0) {
+ dev_err(wm831x->dev, "Failed to enable AUXADC: %d\n", ret);
+ goto out;
+ }
+
+ /* We force a single source at present */
+ src = input;
+ ret = wm831x_reg_write(wm831x, WM831X_AUXADC_SOURCE,
+ 1 << src);
+ if (ret < 0) {
+ dev_err(wm831x->dev, "Failed to set AUXADC source: %d\n", ret);
+ goto out;
+ }
+
+ ret = wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL,
+ WM831X_AUX_CVT_ENA, WM831X_AUX_CVT_ENA);
+ if (ret < 0) {
+ dev_err(wm831x->dev, "Failed to start AUXADC: %d\n", ret);
+ goto disable;
+ }
+
+ do {
+ msleep(1);
+
+ ret = wm831x_reg_read(wm831x, WM831X_AUXADC_CONTROL);
+ if (ret < 0)
+ ret = WM831X_AUX_CVT_ENA;
+ } while ((ret & WM831X_AUX_CVT_ENA) && --tries);
+
+ if (ret & WM831X_AUX_CVT_ENA) {
+ dev_err(wm831x->dev, "Timed out reading AUXADC\n");
+ ret = -EBUSY;
+ goto disable;
+ }
+
+ ret = wm831x_reg_read(wm831x, WM831X_AUXADC_DATA);
+ if (ret < 0) {
+ dev_err(wm831x->dev, "Failed to read AUXADC data: %d\n", ret);
+ } else {
+ src = ((ret & WM831X_AUX_DATA_SRC_MASK)
+ >> WM831X_AUX_DATA_SRC_SHIFT) - 1;
+
+ if (src == 14)
+ src = WM831X_AUX_CAL;
+
+ if (src != input) {
+ dev_err(wm831x->dev, "Data from source %d not %d\n",
+ src, input);
+ ret = -EINVAL;
+ } else {
+ ret &= WM831X_AUX_DATA_MASK;
+ }
+ }
+
+disable:
+ wm831x_set_bits(wm831x, WM831X_AUXADC_CONTROL, WM831X_AUX_ENA, 0);
+out:
+ mutex_unlock(&wm831x->auxadc_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm831x_auxadc_read);
+
+/**
+ * wm831x_auxadc_read_uv: Read a voltage from the WM831x AUXADC
+ *
+ * @wm831x: Device to read from.
+ * @input: AUXADC input to read.
+ */
+int wm831x_auxadc_read_uv(struct wm831x *wm831x, enum wm831x_auxadc input)
+{
+ int ret;
+
+ ret = wm831x_auxadc_read(wm831x, input);
+ if (ret < 0)
+ return ret;
+
+ ret *= 1465;
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm831x_auxadc_read_uv);
+
+static struct resource wm831x_dcdc1_resources[] = {
+ {
+ .start = WM831X_DC1_CONTROL_1,
+ .end = WM831X_DC1_DVS_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_DC1,
+ .end = WM831X_IRQ_UV_DC1,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "HC",
+ .start = WM831X_IRQ_HC_DC1,
+ .end = WM831X_IRQ_HC_DC1,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+
+static struct resource wm831x_dcdc2_resources[] = {
+ {
+ .start = WM831X_DC2_CONTROL_1,
+ .end = WM831X_DC2_DVS_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_DC2,
+ .end = WM831X_IRQ_UV_DC2,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "HC",
+ .start = WM831X_IRQ_HC_DC2,
+ .end = WM831X_IRQ_HC_DC2,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_dcdc3_resources[] = {
+ {
+ .start = WM831X_DC3_CONTROL_1,
+ .end = WM831X_DC3_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_DC3,
+ .end = WM831X_IRQ_UV_DC3,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_dcdc4_resources[] = {
+ {
+ .start = WM831X_DC4_CONTROL,
+ .end = WM831X_DC4_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_DC4,
+ .end = WM831X_IRQ_UV_DC4,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_gpio_resources[] = {
+ {
+ .start = WM831X_IRQ_GPIO_1,
+ .end = WM831X_IRQ_GPIO_16,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_isink1_resources[] = {
+ {
+ .start = WM831X_CURRENT_SINK_1,
+ .end = WM831X_CURRENT_SINK_1,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .start = WM831X_IRQ_CS1,
+ .end = WM831X_IRQ_CS1,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_isink2_resources[] = {
+ {
+ .start = WM831X_CURRENT_SINK_2,
+ .end = WM831X_CURRENT_SINK_2,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .start = WM831X_IRQ_CS2,
+ .end = WM831X_IRQ_CS2,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_ldo1_resources[] = {
+ {
+ .start = WM831X_LDO1_CONTROL,
+ .end = WM831X_LDO1_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_LDO1,
+ .end = WM831X_IRQ_UV_LDO1,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_ldo2_resources[] = {
+ {
+ .start = WM831X_LDO2_CONTROL,
+ .end = WM831X_LDO2_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_LDO2,
+ .end = WM831X_IRQ_UV_LDO2,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_ldo3_resources[] = {
+ {
+ .start = WM831X_LDO3_CONTROL,
+ .end = WM831X_LDO3_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_LDO3,
+ .end = WM831X_IRQ_UV_LDO3,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_ldo4_resources[] = {
+ {
+ .start = WM831X_LDO4_CONTROL,
+ .end = WM831X_LDO4_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_LDO4,
+ .end = WM831X_IRQ_UV_LDO4,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_ldo5_resources[] = {
+ {
+ .start = WM831X_LDO5_CONTROL,
+ .end = WM831X_LDO5_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_LDO5,
+ .end = WM831X_IRQ_UV_LDO5,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_ldo6_resources[] = {
+ {
+ .start = WM831X_LDO6_CONTROL,
+ .end = WM831X_LDO6_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_LDO6,
+ .end = WM831X_IRQ_UV_LDO6,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_ldo7_resources[] = {
+ {
+ .start = WM831X_LDO7_CONTROL,
+ .end = WM831X_LDO7_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_LDO7,
+ .end = WM831X_IRQ_UV_LDO7,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_ldo8_resources[] = {
+ {
+ .start = WM831X_LDO8_CONTROL,
+ .end = WM831X_LDO8_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_LDO8,
+ .end = WM831X_IRQ_UV_LDO8,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_ldo9_resources[] = {
+ {
+ .start = WM831X_LDO9_CONTROL,
+ .end = WM831X_LDO9_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_LDO9,
+ .end = WM831X_IRQ_UV_LDO9,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_ldo10_resources[] = {
+ {
+ .start = WM831X_LDO10_CONTROL,
+ .end = WM831X_LDO10_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+ {
+ .name = "UV",
+ .start = WM831X_IRQ_UV_LDO10,
+ .end = WM831X_IRQ_UV_LDO10,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_ldo11_resources[] = {
+ {
+ .start = WM831X_LDO11_ON_CONTROL,
+ .end = WM831X_LDO11_SLEEP_CONTROL,
+ .flags = IORESOURCE_IO,
+ },
+};
+
+static struct resource wm831x_on_resources[] = {
+ {
+ .start = WM831X_IRQ_ON,
+ .end = WM831X_IRQ_ON,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+
+static struct resource wm831x_power_resources[] = {
+ {
+ .name = "SYSLO",
+ .start = WM831X_IRQ_PPM_SYSLO,
+ .end = WM831X_IRQ_PPM_SYSLO,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "PWR SRC",
+ .start = WM831X_IRQ_PPM_PWR_SRC,
+ .end = WM831X_IRQ_PPM_PWR_SRC,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "USB CURR",
+ .start = WM831X_IRQ_PPM_USB_CURR,
+ .end = WM831X_IRQ_PPM_USB_CURR,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "BATT HOT",
+ .start = WM831X_IRQ_CHG_BATT_HOT,
+ .end = WM831X_IRQ_CHG_BATT_HOT,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "BATT COLD",
+ .start = WM831X_IRQ_CHG_BATT_COLD,
+ .end = WM831X_IRQ_CHG_BATT_COLD,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "BATT FAIL",
+ .start = WM831X_IRQ_CHG_BATT_FAIL,
+ .end = WM831X_IRQ_CHG_BATT_FAIL,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "OV",
+ .start = WM831X_IRQ_CHG_OV,
+ .end = WM831X_IRQ_CHG_OV,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "END",
+ .start = WM831X_IRQ_CHG_END,
+ .end = WM831X_IRQ_CHG_END,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "TO",
+ .start = WM831X_IRQ_CHG_TO,
+ .end = WM831X_IRQ_CHG_TO,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "MODE",
+ .start = WM831X_IRQ_CHG_MODE,
+ .end = WM831X_IRQ_CHG_MODE,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "START",
+ .start = WM831X_IRQ_CHG_START,
+ .end = WM831X_IRQ_CHG_START,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_rtc_resources[] = {
+ {
+ .name = "PER",
+ .start = WM831X_IRQ_RTC_PER,
+ .end = WM831X_IRQ_RTC_PER,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "ALM",
+ .start = WM831X_IRQ_RTC_ALM,
+ .end = WM831X_IRQ_RTC_ALM,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_status1_resources[] = {
+ {
+ .start = WM831X_STATUS_LED_1,
+ .end = WM831X_STATUS_LED_1,
+ .flags = IORESOURCE_IO,
+ },
+};
+
+static struct resource wm831x_status2_resources[] = {
+ {
+ .start = WM831X_STATUS_LED_2,
+ .end = WM831X_STATUS_LED_2,
+ .flags = IORESOURCE_IO,
+ },
+};
+
+static struct resource wm831x_touch_resources[] = {
+ {
+ .name = "TCHPD",
+ .start = WM831X_IRQ_TCHPD,
+ .end = WM831X_IRQ_TCHPD,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .name = "TCHDATA",
+ .start = WM831X_IRQ_TCHDATA,
+ .end = WM831X_IRQ_TCHDATA,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct resource wm831x_wdt_resources[] = {
+ {
+ .start = WM831X_IRQ_WDOG_TO,
+ .end = WM831X_IRQ_WDOG_TO,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct mfd_cell wm8310_devs[] = {
+ {
+ .name = "wm831x-buckv",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources),
+ .resources = wm831x_dcdc1_resources,
+ },
+ {
+ .name = "wm831x-buckv",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources),
+ .resources = wm831x_dcdc2_resources,
+ },
+ {
+ .name = "wm831x-buckp",
+ .id = 3,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources),
+ .resources = wm831x_dcdc3_resources,
+ },
+ {
+ .name = "wm831x-boostp",
+ .id = 4,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources),
+ .resources = wm831x_dcdc4_resources,
+ },
+ {
+ .name = "wm831x-epe",
+ .id = 1,
+ },
+ {
+ .name = "wm831x-epe",
+ .id = 2,
+ },
+ {
+ .name = "wm831x-gpio",
+ .num_resources = ARRAY_SIZE(wm831x_gpio_resources),
+ .resources = wm831x_gpio_resources,
+ },
+ {
+ .name = "wm831x-hwmon",
+ },
+ {
+ .name = "wm831x-isink",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_isink1_resources),
+ .resources = wm831x_isink1_resources,
+ },
+ {
+ .name = "wm831x-isink",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_isink2_resources),
+ .resources = wm831x_isink2_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_ldo1_resources),
+ .resources = wm831x_ldo1_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_ldo2_resources),
+ .resources = wm831x_ldo2_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 3,
+ .num_resources = ARRAY_SIZE(wm831x_ldo3_resources),
+ .resources = wm831x_ldo3_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 4,
+ .num_resources = ARRAY_SIZE(wm831x_ldo4_resources),
+ .resources = wm831x_ldo4_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 5,
+ .num_resources = ARRAY_SIZE(wm831x_ldo5_resources),
+ .resources = wm831x_ldo5_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 6,
+ .num_resources = ARRAY_SIZE(wm831x_ldo6_resources),
+ .resources = wm831x_ldo6_resources,
+ },
+ {
+ .name = "wm831x-aldo",
+ .id = 7,
+ .num_resources = ARRAY_SIZE(wm831x_ldo7_resources),
+ .resources = wm831x_ldo7_resources,
+ },
+ {
+ .name = "wm831x-aldo",
+ .id = 8,
+ .num_resources = ARRAY_SIZE(wm831x_ldo8_resources),
+ .resources = wm831x_ldo8_resources,
+ },
+ {
+ .name = "wm831x-aldo",
+ .id = 9,
+ .num_resources = ARRAY_SIZE(wm831x_ldo9_resources),
+ .resources = wm831x_ldo9_resources,
+ },
+ {
+ .name = "wm831x-aldo",
+ .id = 10,
+ .num_resources = ARRAY_SIZE(wm831x_ldo10_resources),
+ .resources = wm831x_ldo10_resources,
+ },
+ {
+ .name = "wm831x-alive-ldo",
+ .id = 11,
+ .num_resources = ARRAY_SIZE(wm831x_ldo11_resources),
+ .resources = wm831x_ldo11_resources,
+ },
+ {
+ .name = "wm831x-on",
+ .num_resources = ARRAY_SIZE(wm831x_on_resources),
+ .resources = wm831x_on_resources,
+ },
+ {
+ .name = "wm831x-power",
+ .num_resources = ARRAY_SIZE(wm831x_power_resources),
+ .resources = wm831x_power_resources,
+ },
+ {
+ .name = "wm831x-rtc",
+ .num_resources = ARRAY_SIZE(wm831x_rtc_resources),
+ .resources = wm831x_rtc_resources,
+ },
+ {
+ .name = "wm831x-status",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_status1_resources),
+ .resources = wm831x_status1_resources,
+ },
+ {
+ .name = "wm831x-status",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_status2_resources),
+ .resources = wm831x_status2_resources,
+ },
+ {
+ .name = "wm831x-watchdog",
+ .num_resources = ARRAY_SIZE(wm831x_wdt_resources),
+ .resources = wm831x_wdt_resources,
+ },
+};
+
+static struct mfd_cell wm8311_devs[] = {
+ {
+ .name = "wm831x-buckv",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources),
+ .resources = wm831x_dcdc1_resources,
+ },
+ {
+ .name = "wm831x-buckv",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources),
+ .resources = wm831x_dcdc2_resources,
+ },
+ {
+ .name = "wm831x-buckp",
+ .id = 3,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources),
+ .resources = wm831x_dcdc3_resources,
+ },
+ {
+ .name = "wm831x-boostp",
+ .id = 4,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources),
+ .resources = wm831x_dcdc4_resources,
+ },
+ {
+ .name = "wm831x-epe",
+ .id = 1,
+ },
+ {
+ .name = "wm831x-epe",
+ .id = 2,
+ },
+ {
+ .name = "wm831x-gpio",
+ .num_resources = ARRAY_SIZE(wm831x_gpio_resources),
+ .resources = wm831x_gpio_resources,
+ },
+ {
+ .name = "wm831x-hwmon",
+ },
+ {
+ .name = "wm831x-isink",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_isink1_resources),
+ .resources = wm831x_isink1_resources,
+ },
+ {
+ .name = "wm831x-isink",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_isink2_resources),
+ .resources = wm831x_isink2_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_ldo1_resources),
+ .resources = wm831x_ldo1_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_ldo2_resources),
+ .resources = wm831x_ldo2_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 3,
+ .num_resources = ARRAY_SIZE(wm831x_ldo3_resources),
+ .resources = wm831x_ldo3_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 4,
+ .num_resources = ARRAY_SIZE(wm831x_ldo4_resources),
+ .resources = wm831x_ldo4_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 5,
+ .num_resources = ARRAY_SIZE(wm831x_ldo5_resources),
+ .resources = wm831x_ldo5_resources,
+ },
+ {
+ .name = "wm831x-aldo",
+ .id = 7,
+ .num_resources = ARRAY_SIZE(wm831x_ldo7_resources),
+ .resources = wm831x_ldo7_resources,
+ },
+ {
+ .name = "wm831x-alive-ldo",
+ .id = 11,
+ .num_resources = ARRAY_SIZE(wm831x_ldo11_resources),
+ .resources = wm831x_ldo11_resources,
+ },
+ {
+ .name = "wm831x-on",
+ .num_resources = ARRAY_SIZE(wm831x_on_resources),
+ .resources = wm831x_on_resources,
+ },
+ {
+ .name = "wm831x-power",
+ .num_resources = ARRAY_SIZE(wm831x_power_resources),
+ .resources = wm831x_power_resources,
+ },
+ {
+ .name = "wm831x-rtc",
+ .num_resources = ARRAY_SIZE(wm831x_rtc_resources),
+ .resources = wm831x_rtc_resources,
+ },
+ {
+ .name = "wm831x-status",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_status1_resources),
+ .resources = wm831x_status1_resources,
+ },
+ {
+ .name = "wm831x-status",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_status2_resources),
+ .resources = wm831x_status2_resources,
+ },
+ {
+ .name = "wm831x-touch",
+ .num_resources = ARRAY_SIZE(wm831x_touch_resources),
+ .resources = wm831x_touch_resources,
+ },
+ {
+ .name = "wm831x-watchdog",
+ .num_resources = ARRAY_SIZE(wm831x_wdt_resources),
+ .resources = wm831x_wdt_resources,
+ },
+};
+
+static struct mfd_cell wm8312_devs[] = {
+ {
+ .name = "wm831x-buckv",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc1_resources),
+ .resources = wm831x_dcdc1_resources,
+ },
+ {
+ .name = "wm831x-buckv",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc2_resources),
+ .resources = wm831x_dcdc2_resources,
+ },
+ {
+ .name = "wm831x-buckp",
+ .id = 3,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc3_resources),
+ .resources = wm831x_dcdc3_resources,
+ },
+ {
+ .name = "wm831x-boostp",
+ .id = 4,
+ .num_resources = ARRAY_SIZE(wm831x_dcdc4_resources),
+ .resources = wm831x_dcdc4_resources,
+ },
+ {
+ .name = "wm831x-epe",
+ .id = 1,
+ },
+ {
+ .name = "wm831x-epe",
+ .id = 2,
+ },
+ {
+ .name = "wm831x-gpio",
+ .num_resources = ARRAY_SIZE(wm831x_gpio_resources),
+ .resources = wm831x_gpio_resources,
+ },
+ {
+ .name = "wm831x-hwmon",
+ },
+ {
+ .name = "wm831x-isink",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_isink1_resources),
+ .resources = wm831x_isink1_resources,
+ },
+ {
+ .name = "wm831x-isink",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_isink2_resources),
+ .resources = wm831x_isink2_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_ldo1_resources),
+ .resources = wm831x_ldo1_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_ldo2_resources),
+ .resources = wm831x_ldo2_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 3,
+ .num_resources = ARRAY_SIZE(wm831x_ldo3_resources),
+ .resources = wm831x_ldo3_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 4,
+ .num_resources = ARRAY_SIZE(wm831x_ldo4_resources),
+ .resources = wm831x_ldo4_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 5,
+ .num_resources = ARRAY_SIZE(wm831x_ldo5_resources),
+ .resources = wm831x_ldo5_resources,
+ },
+ {
+ .name = "wm831x-ldo",
+ .id = 6,
+ .num_resources = ARRAY_SIZE(wm831x_ldo6_resources),
+ .resources = wm831x_ldo6_resources,
+ },
+ {
+ .name = "wm831x-aldo",
+ .id = 7,
+ .num_resources = ARRAY_SIZE(wm831x_ldo7_resources),
+ .resources = wm831x_ldo7_resources,
+ },
+ {
+ .name = "wm831x-aldo",
+ .id = 8,
+ .num_resources = ARRAY_SIZE(wm831x_ldo8_resources),
+ .resources = wm831x_ldo8_resources,
+ },
+ {
+ .name = "wm831x-aldo",
+ .id = 9,
+ .num_resources = ARRAY_SIZE(wm831x_ldo9_resources),
+ .resources = wm831x_ldo9_resources,
+ },
+ {
+ .name = "wm831x-aldo",
+ .id = 10,
+ .num_resources = ARRAY_SIZE(wm831x_ldo10_resources),
+ .resources = wm831x_ldo10_resources,
+ },
+ {
+ .name = "wm831x-alive-ldo",
+ .id = 11,
+ .num_resources = ARRAY_SIZE(wm831x_ldo11_resources),
+ .resources = wm831x_ldo11_resources,
+ },
+ {
+ .name = "wm831x-on",
+ .num_resources = ARRAY_SIZE(wm831x_on_resources),
+ .resources = wm831x_on_resources,
+ },
+ {
+ .name = "wm831x-power",
+ .num_resources = ARRAY_SIZE(wm831x_power_resources),
+ .resources = wm831x_power_resources,
+ },
+ {
+ .name = "wm831x-rtc",
+ .num_resources = ARRAY_SIZE(wm831x_rtc_resources),
+ .resources = wm831x_rtc_resources,
+ },
+ {
+ .name = "wm831x-status",
+ .id = 1,
+ .num_resources = ARRAY_SIZE(wm831x_status1_resources),
+ .resources = wm831x_status1_resources,
+ },
+ {
+ .name = "wm831x-status",
+ .id = 2,
+ .num_resources = ARRAY_SIZE(wm831x_status2_resources),
+ .resources = wm831x_status2_resources,
+ },
+ {
+ .name = "wm831x-touch",
+ .num_resources = ARRAY_SIZE(wm831x_touch_resources),
+ .resources = wm831x_touch_resources,
+ },
+ {
+ .name = "wm831x-watchdog",
+ .num_resources = ARRAY_SIZE(wm831x_wdt_resources),
+ .resources = wm831x_wdt_resources,
+ },
+};
+
+static struct mfd_cell backlight_devs[] = {
+ {
+ .name = "wm831x-backlight",
+ },
+};
+
+/*
+ * Instantiate the generic non-control parts of the device.
+ */
+static int wm831x_device_init(struct wm831x *wm831x, unsigned long id, int irq)
+{
+ struct wm831x_pdata *pdata = wm831x->dev->platform_data;
+ int rev;
+ enum wm831x_parent parent;
+ int ret;
+
+ mutex_init(&wm831x->io_lock);
+ mutex_init(&wm831x->key_lock);
+ mutex_init(&wm831x->auxadc_lock);
+ dev_set_drvdata(wm831x->dev, wm831x);
+
+ ret = wm831x_reg_read(wm831x, WM831X_PARENT_ID);
+ if (ret < 0) {
+ dev_err(wm831x->dev, "Failed to read parent ID: %d\n", ret);
+ goto err;
+ }
+ if (ret != 0x6204) {
+ dev_err(wm831x->dev, "Device is not a WM831x: ID %x\n", ret);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = wm831x_reg_read(wm831x, WM831X_REVISION);
+ if (ret < 0) {
+ dev_err(wm831x->dev, "Failed to read revision: %d\n", ret);
+ goto err;
+ }
+ rev = (ret & WM831X_PARENT_REV_MASK) >> WM831X_PARENT_REV_SHIFT;
+
+ ret = wm831x_reg_read(wm831x, WM831X_RESET_ID);
+ if (ret < 0) {
+ dev_err(wm831x->dev, "Failed to read device ID: %d\n", ret);
+ goto err;
+ }
+
+ switch (ret) {
+ case 0x8310:
+ parent = WM8310;
+ switch (rev) {
+ case 0:
+ dev_info(wm831x->dev, "WM8310 revision %c\n",
+ 'A' + rev);
+ break;
+ }
+ break;
+
+ case 0x8311:
+ parent = WM8311;
+ switch (rev) {
+ case 0:
+ dev_info(wm831x->dev, "WM8311 revision %c\n",
+ 'A' + rev);
+ break;
+ }
+ break;
+
+ case 0x8312:
+ parent = WM8312;
+ switch (rev) {
+ case 0:
+ dev_info(wm831x->dev, "WM8312 revision %c\n",
+ 'A' + rev);
+ break;
+ }
+ break;
+
+ case 0:
+ /* Some engineering samples do not have the ID set,
+ * rely on the device being registered correctly.
+ * This will need revisiting for future devices with
+ * multiple dies.
+ */
+ parent = id;
+ switch (rev) {
+ case 0:
+ dev_info(wm831x->dev, "WM831%d ES revision %c\n",
+ parent, 'A' + rev);
+ break;
+ }
+ break;
+
+ default:
+ dev_err(wm831x->dev, "Unknown WM831x device %04x\n", ret);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* This will need revisiting in future but is OK for all
+ * current parts.
+ */
+ if (parent != id)
+ dev_warn(wm831x->dev, "Device was registered as a WM831%lu\n",
+ id);
+
+ /* Bootstrap the user key */
+ ret = wm831x_reg_read(wm831x, WM831X_SECURITY_KEY);
+ if (ret < 0) {
+ dev_err(wm831x->dev, "Failed to read security key: %d\n", ret);
+ goto err;
+ }
+ if (ret != 0) {
+ dev_warn(wm831x->dev, "Security key had non-zero value %x\n",
+ ret);
+ wm831x_reg_write(wm831x, WM831X_SECURITY_KEY, 0);
+ }
+ wm831x->locked = 1;
+
+ if (pdata && pdata->pre_init) {
+ ret = pdata->pre_init(wm831x);
+ if (ret != 0) {
+ dev_err(wm831x->dev, "pre_init() failed: %d\n", ret);
+ goto err;
+ }
+ }
+
+ ret = wm831x_irq_init(wm831x, irq);
+ if (ret != 0)
+ goto err;
+
+ /* The core device is up, instantiate the subdevices. */
+ switch (parent) {
+ case WM8310:
+ ret = mfd_add_devices(wm831x->dev, -1,
+ wm8310_devs, ARRAY_SIZE(wm8310_devs),
+ NULL, 0);
+ break;
+
+ case WM8311:
+ ret = mfd_add_devices(wm831x->dev, -1,
+ wm8311_devs, ARRAY_SIZE(wm8311_devs),
+ NULL, 0);
+ break;
+
+ case WM8312:
+ ret = mfd_add_devices(wm831x->dev, -1,
+ wm8312_devs, ARRAY_SIZE(wm8312_devs),
+ NULL, 0);
+ break;
+
+ default:
+ /* If this happens the bus probe function is buggy */
+ BUG();
+ }
+
+ if (ret != 0) {
+ dev_err(wm831x->dev, "Failed to add children\n");
+ goto err_irq;
+ }
+
+ if (pdata && pdata->backlight) {
+ /* Treat errors as non-critical */
+ ret = mfd_add_devices(wm831x->dev, -1, backlight_devs,
+ ARRAY_SIZE(backlight_devs), NULL, 0);
+ if (ret < 0)
+ dev_err(wm831x->dev, "Failed to add backlight: %d\n",
+ ret);
+ }
+
+ wm831x_otp_init(wm831x);
+
+ if (pdata && pdata->post_init) {
+ ret = pdata->post_init(wm831x);
+ if (ret != 0) {
+ dev_err(wm831x->dev, "post_init() failed: %d\n", ret);
+ goto err_irq;
+ }
+ }
+
+ return 0;
+
+err_irq:
+ wm831x_irq_exit(wm831x);
+err:
+ mfd_remove_devices(wm831x->dev);
+ kfree(wm831x);
+ return ret;
+}
+
+static void wm831x_device_exit(struct wm831x *wm831x)
+{
+ wm831x_otp_exit(wm831x);
+ mfd_remove_devices(wm831x->dev);
+ wm831x_irq_exit(wm831x);
+ kfree(wm831x);
+}
+
+static int wm831x_i2c_read_device(struct wm831x *wm831x, unsigned short reg,
+ int bytes, void *dest)
+{
+ struct i2c_client *i2c = wm831x->control_data;
+ int ret;
+ u16 r = cpu_to_be16(reg);
+
+ ret = i2c_master_send(i2c, (unsigned char *)&r, 2);
+ if (ret < 0)
+ return ret;
+ if (ret != 2)
+ return -EIO;
+
+ ret = i2c_master_recv(i2c, dest, bytes);
+ if (ret < 0)
+ return ret;
+ if (ret != bytes)
+ return -EIO;
+ return 0;
+}
+
+/* Currently we allocate the write buffer on the stack; this is OK for
+ * small writes - if we need to do large writes this will need to be
+ * revised.
+ */
+static int wm831x_i2c_write_device(struct wm831x *wm831x, unsigned short reg,
+ int bytes, void *src)
+{
+ struct i2c_client *i2c = wm831x->control_data;
+ unsigned char msg[bytes + 2];
+ int ret;
+
+ reg = cpu_to_be16(reg);
+ memcpy(&msg[0], &reg, 2);
+ memcpy(&msg[2], src, bytes);
+
+ ret = i2c_master_send(i2c, msg, bytes + 2);
+ if (ret < 0)
+ return ret;
+ if (ret < bytes + 2)
+ return -EIO;
+
+ return 0;
+}
+
+static int wm831x_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct wm831x *wm831x;
+
+ wm831x = kzalloc(sizeof(struct wm831x), GFP_KERNEL);
+ if (wm831x == NULL) {
+ kfree(i2c);
+ return -ENOMEM;
+ }
+
+ i2c_set_clientdata(i2c, wm831x);
+ wm831x->dev = &i2c->dev;
+ wm831x->control_data = i2c;
+ wm831x->read_dev = wm831x_i2c_read_device;
+ wm831x->write_dev = wm831x_i2c_write_device;
+
+ return wm831x_device_init(wm831x, id->driver_data, i2c->irq);
+}
+
+static int wm831x_i2c_remove(struct i2c_client *i2c)
+{
+ struct wm831x *wm831x = i2c_get_clientdata(i2c);
+
+ wm831x_device_exit(wm831x);
+
+ return 0;
+}
+
+static const struct i2c_device_id wm831x_i2c_id[] = {
+ { "wm8310", WM8310 },
+ { "wm8311", WM8311 },
+ { "wm8312", WM8312 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, wm831x_i2c_id);
+
+
+static struct i2c_driver wm831x_i2c_driver = {
+ .driver = {
+ .name = "wm831x",
+ .owner = THIS_MODULE,
+ },
+ .probe = wm831x_i2c_probe,
+ .remove = wm831x_i2c_remove,
+ .id_table = wm831x_i2c_id,
+};
+
+static int __init wm831x_i2c_init(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&wm831x_i2c_driver);
+ if (ret != 0)
+ pr_err("Failed to register wm831x I2C driver: %d\n", ret);
+
+ return ret;
+}
+subsys_initcall(wm831x_i2c_init);
+
+static void __exit wm831x_i2c_exit(void)
+{
+ i2c_del_driver(&wm831x_i2c_driver);
+}
+module_exit(wm831x_i2c_exit);
+
+MODULE_DESCRIPTION("I2C support for the WM831X AudioPlus PMIC");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mark Brown");
diff --git a/drivers/mfd/wm831x-irq.c b/drivers/mfd/wm831x-irq.c
new file mode 100644
index 00000000000..d3015dfb913
--- /dev/null
+++ b/drivers/mfd/wm831x-irq.c
@@ -0,0 +1,559 @@
+/*
+ * wm831x-irq.c -- Interrupt controller support for Wolfson WM831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/interrupt.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/mfd/wm831x/irq.h>
+
+#include <linux/delay.h>
+
+/*
+ * Since generic IRQs don't currently support interrupt controllers on
+ * interrupt driven buses we don't use genirq but instead provide an
+ * interface that looks very much like the standard ones. This leads
+ * to some bodges, including storing interrupt handler information in
+ * the static irq_data table we use to look up the data for individual
+ * interrupts, but hopefully won't last too long.
+ */
+
+struct wm831x_irq_data {
+ int primary;
+ int reg;
+ int mask;
+ irq_handler_t handler;
+ void *handler_data;
+};
+
+static struct wm831x_irq_data wm831x_irqs[] = {
+ [WM831X_IRQ_TEMP_THW] = {
+ .primary = WM831X_TEMP_INT,
+ .reg = 1,
+ .mask = WM831X_TEMP_THW_EINT,
+ },
+ [WM831X_IRQ_GPIO_1] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP1_EINT,
+ },
+ [WM831X_IRQ_GPIO_2] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP2_EINT,
+ },
+ [WM831X_IRQ_GPIO_3] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP3_EINT,
+ },
+ [WM831X_IRQ_GPIO_4] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP4_EINT,
+ },
+ [WM831X_IRQ_GPIO_5] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP5_EINT,
+ },
+ [WM831X_IRQ_GPIO_6] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP6_EINT,
+ },
+ [WM831X_IRQ_GPIO_7] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP7_EINT,
+ },
+ [WM831X_IRQ_GPIO_8] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP8_EINT,
+ },
+ [WM831X_IRQ_GPIO_9] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP9_EINT,
+ },
+ [WM831X_IRQ_GPIO_10] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP10_EINT,
+ },
+ [WM831X_IRQ_GPIO_11] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP11_EINT,
+ },
+ [WM831X_IRQ_GPIO_12] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP12_EINT,
+ },
+ [WM831X_IRQ_GPIO_13] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP13_EINT,
+ },
+ [WM831X_IRQ_GPIO_14] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP14_EINT,
+ },
+ [WM831X_IRQ_GPIO_15] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP15_EINT,
+ },
+ [WM831X_IRQ_GPIO_16] = {
+ .primary = WM831X_GP_INT,
+ .reg = 5,
+ .mask = WM831X_GP16_EINT,
+ },
+ [WM831X_IRQ_ON] = {
+ .primary = WM831X_ON_PIN_INT,
+ .reg = 1,
+ .mask = WM831X_ON_PIN_EINT,
+ },
+ [WM831X_IRQ_PPM_SYSLO] = {
+ .primary = WM831X_PPM_INT,
+ .reg = 1,
+ .mask = WM831X_PPM_SYSLO_EINT,
+ },
+ [WM831X_IRQ_PPM_PWR_SRC] = {
+ .primary = WM831X_PPM_INT,
+ .reg = 1,
+ .mask = WM831X_PPM_PWR_SRC_EINT,
+ },
+ [WM831X_IRQ_PPM_USB_CURR] = {
+ .primary = WM831X_PPM_INT,
+ .reg = 1,
+ .mask = WM831X_PPM_USB_CURR_EINT,
+ },
+ [WM831X_IRQ_WDOG_TO] = {
+ .primary = WM831X_WDOG_INT,
+ .reg = 1,
+ .mask = WM831X_WDOG_TO_EINT,
+ },
+ [WM831X_IRQ_RTC_PER] = {
+ .primary = WM831X_RTC_INT,
+ .reg = 1,
+ .mask = WM831X_RTC_PER_EINT,
+ },
+ [WM831X_IRQ_RTC_ALM] = {
+ .primary = WM831X_RTC_INT,
+ .reg = 1,
+ .mask = WM831X_RTC_ALM_EINT,
+ },
+ [WM831X_IRQ_CHG_BATT_HOT] = {
+ .primary = WM831X_CHG_INT,
+ .reg = 2,
+ .mask = WM831X_CHG_BATT_HOT_EINT,
+ },
+ [WM831X_IRQ_CHG_BATT_COLD] = {
+ .primary = WM831X_CHG_INT,
+ .reg = 2,
+ .mask = WM831X_CHG_BATT_COLD_EINT,
+ },
+ [WM831X_IRQ_CHG_BATT_FAIL] = {
+ .primary = WM831X_CHG_INT,
+ .reg = 2,
+ .mask = WM831X_CHG_BATT_FAIL_EINT,
+ },
+ [WM831X_IRQ_CHG_OV] = {
+ .primary = WM831X_CHG_INT,
+ .reg = 2,
+ .mask = WM831X_CHG_OV_EINT,
+ },
+ [WM831X_IRQ_CHG_END] = {
+ .primary = WM831X_CHG_INT,
+ .reg = 2,
+ .mask = WM831X_CHG_END_EINT,
+ },
+ [WM831X_IRQ_CHG_TO] = {
+ .primary = WM831X_CHG_INT,
+ .reg = 2,
+ .mask = WM831X_CHG_TO_EINT,
+ },
+ [WM831X_IRQ_CHG_MODE] = {
+ .primary = WM831X_CHG_INT,
+ .reg = 2,
+ .mask = WM831X_CHG_MODE_EINT,
+ },
+ [WM831X_IRQ_CHG_START] = {
+ .primary = WM831X_CHG_INT,
+ .reg = 2,
+ .mask = WM831X_CHG_START_EINT,
+ },
+ [WM831X_IRQ_TCHDATA] = {
+ .primary = WM831X_TCHDATA_INT,
+ .reg = 1,
+ .mask = WM831X_TCHDATA_EINT,
+ },
+ [WM831X_IRQ_TCHPD] = {
+ .primary = WM831X_TCHPD_INT,
+ .reg = 1,
+ .mask = WM831X_TCHPD_EINT,
+ },
+ [WM831X_IRQ_AUXADC_DATA] = {
+ .primary = WM831X_AUXADC_INT,
+ .reg = 1,
+ .mask = WM831X_AUXADC_DATA_EINT,
+ },
+ [WM831X_IRQ_AUXADC_DCOMP1] = {
+ .primary = WM831X_AUXADC_INT,
+ .reg = 1,
+ .mask = WM831X_AUXADC_DCOMP1_EINT,
+ },
+ [WM831X_IRQ_AUXADC_DCOMP2] = {
+ .primary = WM831X_AUXADC_INT,
+ .reg = 1,
+ .mask = WM831X_AUXADC_DCOMP2_EINT,
+ },
+ [WM831X_IRQ_AUXADC_DCOMP3] = {
+ .primary = WM831X_AUXADC_INT,
+ .reg = 1,
+ .mask = WM831X_AUXADC_DCOMP3_EINT,
+ },
+ [WM831X_IRQ_AUXADC_DCOMP4] = {
+ .primary = WM831X_AUXADC_INT,
+ .reg = 1,
+ .mask = WM831X_AUXADC_DCOMP4_EINT,
+ },
+ [WM831X_IRQ_CS1] = {
+ .primary = WM831X_CS_INT,
+ .reg = 2,
+ .mask = WM831X_CS1_EINT,
+ },
+ [WM831X_IRQ_CS2] = {
+ .primary = WM831X_CS_INT,
+ .reg = 2,
+ .mask = WM831X_CS2_EINT,
+ },
+ [WM831X_IRQ_HC_DC1] = {
+ .primary = WM831X_HC_INT,
+ .reg = 4,
+ .mask = WM831X_HC_DC1_EINT,
+ },
+ [WM831X_IRQ_HC_DC2] = {
+ .primary = WM831X_HC_INT,
+ .reg = 4,
+ .mask = WM831X_HC_DC2_EINT,
+ },
+ [WM831X_IRQ_UV_LDO1] = {
+ .primary = WM831X_UV_INT,
+ .reg = 3,
+ .mask = WM831X_UV_LDO1_EINT,
+ },
+ [WM831X_IRQ_UV_LDO2] = {
+ .primary = WM831X_UV_INT,
+ .reg = 3,
+ .mask = WM831X_UV_LDO2_EINT,
+ },
+ [WM831X_IRQ_UV_LDO3] = {
+ .primary = WM831X_UV_INT,
+ .reg = 3,
+ .mask = WM831X_UV_LDO3_EINT,
+ },
+ [WM831X_IRQ_UV_LDO4] = {
+ .primary = WM831X_UV_INT,
+ .reg = 3,
+ .mask = WM831X_UV_LDO4_EINT,
+ },
+ [WM831X_IRQ_UV_LDO5] = {
+ .primary = WM831X_UV_INT,
+ .reg = 3,
+ .mask = WM831X_UV_LDO5_EINT,
+ },
+ [WM831X_IRQ_UV_LDO6] = {
+ .primary = WM831X_UV_INT,
+ .reg = 3,
+ .mask = WM831X_UV_LDO6_EINT,
+ },
+ [WM831X_IRQ_UV_LDO7] = {
+ .primary = WM831X_UV_INT,
+ .reg = 3,
+ .mask = WM831X_UV_LDO7_EINT,
+ },
+ [WM831X_IRQ_UV_LDO8] = {
+ .primary = WM831X_UV_INT,
+ .reg = 3,
+ .mask = WM831X_UV_LDO8_EINT,
+ },
+ [WM831X_IRQ_UV_LDO9] = {
+ .primary = WM831X_UV_INT,
+ .reg = 3,
+ .mask = WM831X_UV_LDO9_EINT,
+ },
+ [WM831X_IRQ_UV_LDO10] = {
+ .primary = WM831X_UV_INT,
+ .reg = 3,
+ .mask = WM831X_UV_LDO10_EINT,
+ },
+ [WM831X_IRQ_UV_DC1] = {
+ .primary = WM831X_UV_INT,
+ .reg = 4,
+ .mask = WM831X_UV_DC1_EINT,
+ },
+ [WM831X_IRQ_UV_DC2] = {
+ .primary = WM831X_UV_INT,
+ .reg = 4,
+ .mask = WM831X_UV_DC2_EINT,
+ },
+ [WM831X_IRQ_UV_DC3] = {
+ .primary = WM831X_UV_INT,
+ .reg = 4,
+ .mask = WM831X_UV_DC3_EINT,
+ },
+ [WM831X_IRQ_UV_DC4] = {
+ .primary = WM831X_UV_INT,
+ .reg = 4,
+ .mask = WM831X_UV_DC4_EINT,
+ },
+};
+
+static inline int irq_data_to_status_reg(struct wm831x_irq_data *irq_data)
+{
+ return WM831X_INTERRUPT_STATUS_1 - 1 + irq_data->reg;
+}
+
+static inline int irq_data_to_mask_reg(struct wm831x_irq_data *irq_data)
+{
+ return WM831X_INTERRUPT_STATUS_1_MASK - 1 + irq_data->reg;
+}
+
+static void __wm831x_enable_irq(struct wm831x *wm831x, int irq)
+{
+ struct wm831x_irq_data *irq_data = &wm831x_irqs[irq];
+
+ wm831x->irq_masks[irq_data->reg - 1] &= ~irq_data->mask;
+ wm831x_reg_write(wm831x, irq_data_to_mask_reg(irq_data),
+ wm831x->irq_masks[irq_data->reg - 1]);
+}
+
+void wm831x_enable_irq(struct wm831x *wm831x, int irq)
+{
+ mutex_lock(&wm831x->irq_lock);
+ __wm831x_enable_irq(wm831x, irq);
+ mutex_unlock(&wm831x->irq_lock);
+}
+EXPORT_SYMBOL_GPL(wm831x_enable_irq);
+
+static void __wm831x_disable_irq(struct wm831x *wm831x, int irq)
+{
+ struct wm831x_irq_data *irq_data = &wm831x_irqs[irq];
+
+ wm831x->irq_masks[irq_data->reg - 1] |= irq_data->mask;
+ wm831x_reg_write(wm831x, irq_data_to_mask_reg(irq_data),
+ wm831x->irq_masks[irq_data->reg - 1]);
+}
+
+void wm831x_disable_irq(struct wm831x *wm831x, int irq)
+{
+ mutex_lock(&wm831x->irq_lock);
+ __wm831x_disable_irq(wm831x, irq);
+ mutex_unlock(&wm831x->irq_lock);
+}
+EXPORT_SYMBOL_GPL(wm831x_disable_irq);
+
+int wm831x_request_irq(struct wm831x *wm831x,
+ unsigned int irq, irq_handler_t handler,
+ unsigned long flags, const char *name,
+ void *dev)
+{
+ int ret = 0;
+
+ if (irq < 0 || irq >= WM831X_NUM_IRQS)
+ return -EINVAL;
+
+ mutex_lock(&wm831x->irq_lock);
+
+ if (wm831x_irqs[irq].handler) {
+ dev_err(wm831x->dev, "Already have handler for IRQ %d\n", irq);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ wm831x_irqs[irq].handler = handler;
+ wm831x_irqs[irq].handler_data = dev;
+
+ __wm831x_enable_irq(wm831x, irq);
+
+out:
+ mutex_unlock(&wm831x->irq_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(wm831x_request_irq);
+
+void wm831x_free_irq(struct wm831x *wm831x, unsigned int irq, void *data)
+{
+ if (irq < 0 || irq >= WM831X_NUM_IRQS)
+ return;
+
+ mutex_lock(&wm831x->irq_lock);
+
+ wm831x_irqs[irq].handler = NULL;
+ wm831x_irqs[irq].handler_data = NULL;
+
+ __wm831x_disable_irq(wm831x, irq);
+
+ mutex_unlock(&wm831x->irq_lock);
+}
+EXPORT_SYMBOL_GPL(wm831x_free_irq);
+
+
+static void wm831x_handle_irq(struct wm831x *wm831x, int irq, int status)
+{
+ struct wm831x_irq_data *irq_data = &wm831x_irqs[irq];
+
+ if (irq_data->handler) {
+ irq_data->handler(irq, irq_data->handler_data);
+ wm831x_reg_write(wm831x, irq_data_to_status_reg(irq_data),
+ irq_data->mask);
+ } else {
+ dev_err(wm831x->dev, "Unhandled IRQ %d, masking\n", irq);
+ __wm831x_disable_irq(wm831x, irq);
+ }
+}
+
+/* Main interrupt handling occurs in a workqueue since we need
+ * interrupts enabled to interact with the chip. */
+static void wm831x_irq_worker(struct work_struct *work)
+{
+ struct wm831x *wm831x = container_of(work, struct wm831x, irq_work);
+ unsigned int i;
+ int primary;
+ int status_regs[5];
+ int read[5] = { 0 };
+ int *status;
+
+ primary = wm831x_reg_read(wm831x, WM831X_SYSTEM_INTERRUPTS);
+ if (primary < 0) {
+ dev_err(wm831x->dev, "Failed to read system interrupt: %d\n",
+ primary);
+ goto out;
+ }
+
+ mutex_lock(&wm831x->irq_lock);
+
+ for (i = 0; i < ARRAY_SIZE(wm831x_irqs); i++) {
+ int offset = wm831x_irqs[i].reg - 1;
+
+ if (!(primary & wm831x_irqs[i].primary))
+ continue;
+
+ status = &status_regs[offset];
+
+ /* Hopefully there should only be one register to read
+ * each time otherwise we ought to do a block read. */
+ if (!read[offset]) {
+ *status = wm831x_reg_read(wm831x,
+ irq_data_to_status_reg(&wm831x_irqs[i]));
+ if (*status < 0) {
+ dev_err(wm831x->dev,
+ "Failed to read IRQ status: %d\n",
+ *status);
+ goto out_lock;
+ }
+
+ /* Mask out the disabled IRQs */
+ *status &= ~wm831x->irq_masks[offset];
+ read[offset] = 1;
+ }
+
+ if (*status & wm831x_irqs[i].mask)
+ wm831x_handle_irq(wm831x, i, *status);
+ }
+
+out_lock:
+ mutex_unlock(&wm831x->irq_lock);
+out:
+ enable_irq(wm831x->irq);
+}
+
+
+static irqreturn_t wm831x_cpu_irq(int irq, void *data)
+{
+ struct wm831x *wm831x = data;
+
+ /* Shut the interrupt to the CPU up and schedule the actual
+ * handler; we can't check that the IRQ is asserted. */
+ disable_irq_nosync(irq);
+
+ queue_work(wm831x->irq_wq, &wm831x->irq_work);
+
+ return IRQ_HANDLED;
+}
+
+int wm831x_irq_init(struct wm831x *wm831x, int irq)
+{
+ int i, ret;
+
+ if (!irq) {
+ dev_warn(wm831x->dev,
+ "No interrupt specified - functionality limited\n");
+ return 0;
+ }
+
+
+ wm831x->irq_wq = create_singlethread_workqueue("wm831x-irq");
+ if (!wm831x->irq_wq) {
+ dev_err(wm831x->dev, "Failed to allocate IRQ worker\n");
+ return -ESRCH;
+ }
+
+ wm831x->irq = irq;
+ mutex_init(&wm831x->irq_lock);
+ INIT_WORK(&wm831x->irq_work, wm831x_irq_worker);
+
+ /* Mask the individual interrupt sources */
+ for (i = 0; i < ARRAY_SIZE(wm831x->irq_masks); i++) {
+ wm831x->irq_masks[i] = 0xffff;
+ wm831x_reg_write(wm831x, WM831X_INTERRUPT_STATUS_1_MASK + i,
+ 0xffff);
+ }
+
+ /* Enable top level interrupts, we mask at secondary level */
+ wm831x_reg_write(wm831x, WM831X_SYSTEM_INTERRUPTS_MASK, 0);
+
+ /* We're good to go. We set IRQF_SHARED since there's a
+ * chance the driver will interoperate with another driver but
+ * the need to disable the IRQ while handing via I2C/SPI means
+ * that this may break and performance will be impacted. If
+ * this does happen it's a hardware design issue and the only
+ * other alternative would be polling.
+ */
+ ret = request_irq(irq, wm831x_cpu_irq, IRQF_TRIGGER_LOW | IRQF_SHARED,
+ "wm831x", wm831x);
+ if (ret != 0) {
+ dev_err(wm831x->dev, "Failed to request IRQ %d: %d\n",
+ irq, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+void wm831x_irq_exit(struct wm831x *wm831x)
+{
+ if (wm831x->irq)
+ free_irq(wm831x->irq, wm831x);
+}
diff --git a/drivers/mfd/wm831x-otp.c b/drivers/mfd/wm831x-otp.c
new file mode 100644
index 00000000000..f742745ff35
--- /dev/null
+++ b/drivers/mfd/wm831x-otp.c
@@ -0,0 +1,83 @@
+/*
+ * wm831x-otp.c -- OTP for Wolfson WM831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/bcd.h>
+#include <linux/delay.h>
+#include <linux/mfd/core.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/otp.h>
+
+/* In bytes */
+#define WM831X_UNIQUE_ID_LEN 16
+
+/* Read the unique ID from the chip into id */
+static int wm831x_unique_id_read(struct wm831x *wm831x, char *id)
+{
+ int i, val;
+
+ for (i = 0; i < WM831X_UNIQUE_ID_LEN / 2; i++) {
+ val = wm831x_reg_read(wm831x, WM831X_UNIQUE_ID_1 + i);
+ if (val < 0)
+ return val;
+
+ id[i * 2] = (val >> 8) & 0xff;
+ id[(i * 2) + 1] = val & 0xff;
+ }
+
+ return 0;
+}
+
+static ssize_t wm831x_unique_id_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wm831x *wm831x = dev_get_drvdata(dev);
+ int i, rval;
+ char id[WM831X_UNIQUE_ID_LEN];
+ ssize_t ret = 0;
+
+ rval = wm831x_unique_id_read(wm831x, id);
+ if (rval < 0)
+ return 0;
+
+ for (i = 0; i < WM831X_UNIQUE_ID_LEN; i++)
+ ret += sprintf(&buf[ret], "%02x", buf[i]);
+
+ ret += sprintf(&buf[ret], "\n");
+
+ return ret;
+}
+
+static DEVICE_ATTR(unique_id, 0444, wm831x_unique_id_show, NULL);
+
+int wm831x_otp_init(struct wm831x *wm831x)
+{
+ int ret;
+
+ ret = device_create_file(wm831x->dev, &dev_attr_unique_id);
+ if (ret != 0)
+ dev_err(wm831x->dev, "Unique ID attribute not created: %d\n",
+ ret);
+
+ return ret;
+}
+
+void wm831x_otp_exit(struct wm831x *wm831x)
+{
+ device_remove_file(wm831x->dev, &dev_attr_unique_id);
+}
+
diff --git a/drivers/mfd/wm8350-core.c b/drivers/mfd/wm8350-core.c
index fe24079387c..ba27c9dc1ad 100644
--- a/drivers/mfd/wm8350-core.c
+++ b/drivers/mfd/wm8350-core.c
@@ -353,15 +353,15 @@ static void wm8350_irq_call_handler(struct wm8350 *wm8350, int irq)
}
/*
- * wm8350_irq_worker actually handles the interrupts. Since all
+ * This is a threaded IRQ handler so can access I2C/SPI. Since all
* interrupts are clear on read the IRQ line will be reasserted and
* the physical IRQ will be handled again if another interrupt is
* asserted while we run - in the normal course of events this is a
* rare occurrence so we save I2C/SPI reads.
*/
-static void wm8350_irq_worker(struct work_struct *work)
+static irqreturn_t wm8350_irq(int irq, void *data)
{
- struct wm8350 *wm8350 = container_of(work, struct wm8350, irq_work);
+ struct wm8350 *wm8350 = data;
u16 level_one, status1, status2, comp;
/* TODO: Use block reads to improve performance? */
@@ -552,16 +552,6 @@ static void wm8350_irq_worker(struct work_struct *work)
}
}
- enable_irq(wm8350->chip_irq);
-}
-
-static irqreturn_t wm8350_irq(int irq, void *data)
-{
- struct wm8350 *wm8350 = data;
-
- disable_irq_nosync(irq);
- schedule_work(&wm8350->irq_work);
-
return IRQ_HANDLED;
}
@@ -1428,9 +1418,8 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq,
mutex_init(&wm8350->auxadc_mutex);
mutex_init(&wm8350->irq_mutex);
- INIT_WORK(&wm8350->irq_work, wm8350_irq_worker);
if (irq) {
- int flags = 0;
+ int flags = IRQF_ONESHOT;
if (pdata && pdata->irq_high) {
flags |= IRQF_TRIGGER_HIGH;
@@ -1444,8 +1433,8 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq,
WM8350_IRQ_POL);
}
- ret = request_irq(irq, wm8350_irq, flags,
- "wm8350", wm8350);
+ ret = request_threaded_irq(irq, NULL, wm8350_irq, flags,
+ "wm8350", wm8350);
if (ret != 0) {
dev_err(wm8350->dev, "Failed to request IRQ: %d\n",
ret);
@@ -1472,6 +1461,8 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq,
&(wm8350->codec.pdev));
wm8350_client_dev_register(wm8350, "wm8350-gpio",
&(wm8350->gpio.pdev));
+ wm8350_client_dev_register(wm8350, "wm8350-hwmon",
+ &(wm8350->hwmon.pdev));
wm8350_client_dev_register(wm8350, "wm8350-power",
&(wm8350->power.pdev));
wm8350_client_dev_register(wm8350, "wm8350-rtc", &(wm8350->rtc.pdev));
@@ -1498,11 +1489,11 @@ void wm8350_device_exit(struct wm8350 *wm8350)
platform_device_unregister(wm8350->wdt.pdev);
platform_device_unregister(wm8350->rtc.pdev);
platform_device_unregister(wm8350->power.pdev);
+ platform_device_unregister(wm8350->hwmon.pdev);
platform_device_unregister(wm8350->gpio.pdev);
platform_device_unregister(wm8350->codec.pdev);
free_irq(wm8350->chip_irq, wm8350);
- flush_work(&wm8350->irq_work);
kfree(wm8350->reg_cache);
}
EXPORT_SYMBOL_GPL(wm8350_device_exit);
diff --git a/drivers/misc/sgi-xp/xpc_sn2.c b/drivers/misc/sgi-xp/xpc_sn2.c
index 915a3b495da..8b70e03f939 100644
--- a/drivers/misc/sgi-xp/xpc_sn2.c
+++ b/drivers/misc/sgi-xp/xpc_sn2.c
@@ -279,7 +279,7 @@ xpc_check_for_sent_chctl_flags_sn2(struct xpc_partition *part)
spin_unlock_irqrestore(&part->chctl_lock, irq_flags);
dev_dbg(xpc_chan, "received notify IRQ from partid=%d, chctl.all_flags="
- "0x%lx\n", XPC_PARTID(part), chctl.all_flags);
+ "0x%llx\n", XPC_PARTID(part), chctl.all_flags);
xpc_wakeup_channel_mgr(part);
}
@@ -615,7 +615,8 @@ xpc_get_partition_rsvd_page_pa_sn2(void *buf, u64 *cookie, unsigned long *rp_pa,
s64 status;
enum xp_retval ret;
- status = sn_partition_reserved_page_pa((u64)buf, cookie, rp_pa, len);
+ status = sn_partition_reserved_page_pa((u64)buf, cookie,
+ (u64 *)rp_pa, (u64 *)len);
if (status == SALRET_OK)
ret = xpSuccess;
else if (status == SALRET_MORE_PASSES)
@@ -777,8 +778,8 @@ xpc_get_remote_heartbeat_sn2(struct xpc_partition *part)
if (ret != xpSuccess)
return ret;
- dev_dbg(xpc_part, "partid=%d, heartbeat=%ld, last_heartbeat=%ld, "
- "heartbeat_offline=%ld, HB_mask[0]=0x%lx\n", XPC_PARTID(part),
+ dev_dbg(xpc_part, "partid=%d, heartbeat=%lld, last_heartbeat=%lld, "
+ "heartbeat_offline=%lld, HB_mask[0]=0x%lx\n", XPC_PARTID(part),
remote_vars->heartbeat, part->last_heartbeat,
remote_vars->heartbeat_offline,
remote_vars->heartbeating_to_mask[0]);
@@ -940,7 +941,7 @@ xpc_update_partition_info_sn2(struct xpc_partition *part, u8 remote_rp_version,
part_sn2->remote_vars_pa);
part->last_heartbeat = remote_vars->heartbeat - 1;
- dev_dbg(xpc_part, " last_heartbeat = 0x%016lx\n",
+ dev_dbg(xpc_part, " last_heartbeat = 0x%016llx\n",
part->last_heartbeat);
part_sn2->remote_vars_part_pa = remote_vars->vars_part_pa;
@@ -1029,7 +1030,8 @@ xpc_identify_activate_IRQ_req_sn2(int nasid)
part->activate_IRQ_rcvd++;
dev_dbg(xpc_part, "partid for nasid %d is %d; IRQs = %d; HB = "
- "%ld:0x%lx\n", (int)nasid, (int)partid, part->activate_IRQ_rcvd,
+ "%lld:0x%lx\n", (int)nasid, (int)partid,
+ part->activate_IRQ_rcvd,
remote_vars->heartbeat, remote_vars->heartbeating_to_mask[0]);
if (xpc_partition_disengaged(part) &&
@@ -1129,7 +1131,7 @@ xpc_identify_activate_IRQ_sender_sn2(void)
do {
n_IRQs_detected++;
nasid = (l * BITS_PER_LONG + b) * 2;
- dev_dbg(xpc_part, "interrupt from nasid %ld\n", nasid);
+ dev_dbg(xpc_part, "interrupt from nasid %lld\n", nasid);
xpc_identify_activate_IRQ_req_sn2(nasid);
b = find_next_bit(&nasid_mask_long, BITS_PER_LONG,
@@ -1386,7 +1388,7 @@ xpc_pull_remote_vars_part_sn2(struct xpc_partition *part)
if (pulled_entry->magic != 0) {
dev_dbg(xpc_chan, "partition %d's XPC vars_part for "
- "partition %d has bad magic value (=0x%lx)\n",
+ "partition %d has bad magic value (=0x%llx)\n",
partid, sn_partition_id, pulled_entry->magic);
return xpBadMagic;
}
@@ -1730,14 +1732,14 @@ xpc_notify_senders_sn2(struct xpc_channel *ch, enum xp_retval reason, s64 put)
if (notify->func != NULL) {
dev_dbg(xpc_chan, "notify->func() called, notify=0x%p "
- "msg_number=%ld partid=%d channel=%d\n",
+ "msg_number=%lld partid=%d channel=%d\n",
(void *)notify, get, ch->partid, ch->number);
notify->func(reason, ch->partid, ch->number,
notify->key);
dev_dbg(xpc_chan, "notify->func() returned, notify=0x%p"
- " msg_number=%ld partid=%d channel=%d\n",
+ " msg_number=%lld partid=%d channel=%d\n",
(void *)notify, get, ch->partid, ch->number);
}
}
@@ -1858,7 +1860,7 @@ xpc_process_msg_chctl_flags_sn2(struct xpc_partition *part, int ch_number)
ch_sn2->w_remote_GP.get = ch_sn2->remote_GP.get;
- dev_dbg(xpc_chan, "w_remote_GP.get changed to %ld, partid=%d, "
+ dev_dbg(xpc_chan, "w_remote_GP.get changed to %lld, partid=%d, "
"channel=%d\n", ch_sn2->w_remote_GP.get, ch->partid,
ch->number);
@@ -1885,7 +1887,7 @@ xpc_process_msg_chctl_flags_sn2(struct xpc_partition *part, int ch_number)
smp_wmb(); /* ensure flags have been cleared before bte_copy */
ch_sn2->w_remote_GP.put = ch_sn2->remote_GP.put;
- dev_dbg(xpc_chan, "w_remote_GP.put changed to %ld, partid=%d, "
+ dev_dbg(xpc_chan, "w_remote_GP.put changed to %lld, partid=%d, "
"channel=%d\n", ch_sn2->w_remote_GP.put, ch->partid,
ch->number);
@@ -1943,7 +1945,7 @@ xpc_pull_remote_msg_sn2(struct xpc_channel *ch, s64 get)
if (ret != xpSuccess) {
dev_dbg(xpc_chan, "failed to pull %d msgs starting with"
- " msg %ld from partition %d, channel=%d, "
+ " msg %lld from partition %d, channel=%d, "
"ret=%d\n", nmsgs, ch_sn2->next_msg_to_pull,
ch->partid, ch->number, ret);
@@ -1995,7 +1997,7 @@ xpc_get_deliverable_payload_sn2(struct xpc_channel *ch)
if (cmpxchg(&ch_sn2->w_local_GP.get, get, get + 1) == get) {
/* we got the entry referenced by get */
- dev_dbg(xpc_chan, "w_local_GP.get changed to %ld, "
+ dev_dbg(xpc_chan, "w_local_GP.get changed to %lld, "
"partid=%d, channel=%d\n", get + 1,
ch->partid, ch->number);
@@ -2062,7 +2064,7 @@ xpc_send_msgs_sn2(struct xpc_channel *ch, s64 initial_put)
/* we just set the new value of local_GP->put */
- dev_dbg(xpc_chan, "local_GP->put changed to %ld, partid=%d, "
+ dev_dbg(xpc_chan, "local_GP->put changed to %lld, partid=%d, "
"channel=%d\n", put, ch->partid, ch->number);
send_msgrequest = 1;
@@ -2147,8 +2149,8 @@ xpc_allocate_msg_sn2(struct xpc_channel *ch, u32 flags,
DBUG_ON(msg->flags != 0);
msg->number = put;
- dev_dbg(xpc_chan, "w_local_GP.put changed to %ld; msg=0x%p, "
- "msg_number=%ld, partid=%d, channel=%d\n", put + 1,
+ dev_dbg(xpc_chan, "w_local_GP.put changed to %lld; msg=0x%p, "
+ "msg_number=%lld, partid=%d, channel=%d\n", put + 1,
(void *)msg, msg->number, ch->partid, ch->number);
*address_of_msg = msg;
@@ -2296,7 +2298,7 @@ xpc_acknowledge_msgs_sn2(struct xpc_channel *ch, s64 initial_get, u8 msg_flags)
/* we just set the new value of local_GP->get */
- dev_dbg(xpc_chan, "local_GP->get changed to %ld, partid=%d, "
+ dev_dbg(xpc_chan, "local_GP->get changed to %lld, partid=%d, "
"channel=%d\n", get, ch->partid, ch->number);
send_msgrequest = (msg_flags & XPC_M_SN2_INTERRUPT);
@@ -2323,7 +2325,7 @@ xpc_received_payload_sn2(struct xpc_channel *ch, void *payload)
msg = container_of(payload, struct xpc_msg_sn2, payload);
msg_number = msg->number;
- dev_dbg(xpc_chan, "msg=0x%p, msg_number=%ld, partid=%d, channel=%d\n",
+ dev_dbg(xpc_chan, "msg=0x%p, msg_number=%lld, partid=%d, channel=%d\n",
(void *)msg, msg_number, ch->partid, ch->number);
DBUG_ON((((u64)msg - (u64)ch->sn.sn2.remote_msgqueue) / ch->entry_size) !=
diff --git a/drivers/mtd/nand/ams-delta.c b/drivers/mtd/nand/ams-delta.c
index 782994ead0e..005b91f096f 100644
--- a/drivers/mtd/nand/ams-delta.c
+++ b/drivers/mtd/nand/ams-delta.c
@@ -63,7 +63,7 @@ static void ams_delta_write_byte(struct mtd_info *mtd, u_char byte)
{
struct nand_chip *this = mtd->priv;
- omap_writew(0, (OMAP_MPUIO_BASE + OMAP_MPUIO_IO_CNTL));
+ omap_writew(0, (OMAP1_MPUIO_BASE + OMAP_MPUIO_IO_CNTL));
omap_writew(byte, this->IO_ADDR_W);
ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_NWE, 0);
ndelay(40);
@@ -78,7 +78,7 @@ static u_char ams_delta_read_byte(struct mtd_info *mtd)
ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_NRE, 0);
ndelay(40);
- omap_writew(~0, (OMAP_MPUIO_BASE + OMAP_MPUIO_IO_CNTL));
+ omap_writew(~0, (OMAP1_MPUIO_BASE + OMAP_MPUIO_IO_CNTL));
res = omap_readw(this->IO_ADDR_R);
ams_delta_latch2_write(AMS_DELTA_LATCH2_NAND_NRE,
AMS_DELTA_LATCH2_NAND_NRE);
@@ -178,8 +178,8 @@ static int __init ams_delta_init(void)
ams_delta_mtd->priv = this;
/* Set address of NAND IO lines */
- this->IO_ADDR_R = (OMAP_MPUIO_BASE + OMAP_MPUIO_INPUT_LATCH);
- this->IO_ADDR_W = (OMAP_MPUIO_BASE + OMAP_MPUIO_OUTPUT);
+ this->IO_ADDR_R = (OMAP1_MPUIO_BASE + OMAP_MPUIO_INPUT_LATCH);
+ this->IO_ADDR_W = (OMAP1_MPUIO_BASE + OMAP_MPUIO_OUTPUT);
this->read_byte = ams_delta_read_byte;
this->write_buf = ams_delta_write_buf;
this->read_buf = ams_delta_read_buf;
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index f4317798e47..2dc42bbf6fe 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -82,6 +82,13 @@ config REGULATOR_TWL4030
This driver supports the voltage regulators provided by
this family of companion chips.
+config REGULATOR_WM831X
+ tristate "Wolfson Microelcronics WM831x PMIC regulators"
+ depends on MFD_WM831X
+ help
+ Support the voltage and current regulators of the WM831x series
+ of PMIC devices.
+
config REGULATOR_WM8350
tristate "Wolfson Microelectroncis WM8350 AudioPlus PMIC"
depends on MFD_WM8350
@@ -117,4 +124,28 @@ config REGULATOR_LP3971
Say Y here to support the voltage regulators and convertors
on National Semiconductors LP3971 PMIC
+config REGULATOR_PCAP
+ tristate "PCAP2 regulator driver"
+ depends on EZX_PCAP
+ help
+ This driver provides support for the voltage regulators of the
+ PCAP2 PMIC.
+
+config REGULATOR_MC13783
+ tristate "Support regulators on Freescale MC13783 PMIC"
+ depends on MFD_MC13783
+ help
+ Say y here to support the regulators found on the Freescale MC13783
+ PMIC.
+
+config REGULATOR_AB3100
+ tristate "ST-Ericsson AB3100 Regulator functions"
+ depends on AB3100_CORE
+ default y if AB3100_CORE
+ help
+ These regulators correspond to functionality in the
+ AB3100 analog baseband dealing with power regulators
+ for the system.
+
endif
+
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 4d762c4cccf..768b3316d6e 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -12,9 +12,15 @@ obj-$(CONFIG_REGULATOR_BQ24022) += bq24022.o
obj-$(CONFIG_REGULATOR_LP3971) += lp3971.o
obj-$(CONFIG_REGULATOR_MAX1586) += max1586.o
obj-$(CONFIG_REGULATOR_TWL4030) += twl4030-regulator.o
+obj-$(CONFIG_REGULATOR_WM831X) += wm831x-dcdc.o
+obj-$(CONFIG_REGULATOR_WM831X) += wm831x-isink.o
+obj-$(CONFIG_REGULATOR_WM831X) += wm831x-ldo.o
obj-$(CONFIG_REGULATOR_WM8350) += wm8350-regulator.o
obj-$(CONFIG_REGULATOR_WM8400) += wm8400-regulator.o
obj-$(CONFIG_REGULATOR_DA903X) += da903x.o
obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o
+obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o
+obj-$(CONFIG_REGULATOR_MC13783) += mc13783.o
+obj-$(CONFIG_REGULATOR_AB3100) += ab3100.o
ccflags-$(CONFIG_REGULATOR_DEBUG) += -DDEBUG
diff --git a/drivers/regulator/ab3100.c b/drivers/regulator/ab3100.c
new file mode 100644
index 00000000000..49aeee823a2
--- /dev/null
+++ b/drivers/regulator/ab3100.c
@@ -0,0 +1,700 @@
+/*
+ * drivers/regulator/ab3100.c
+ *
+ * Copyright (C) 2008-2009 ST-Ericsson AB
+ * License terms: GNU General Public License (GPL) version 2
+ * Low-level control of the AB3100 IC Low Dropout (LDO)
+ * regulators, external regulator and buck converter
+ * Author: Mattias Wallin <mattias.wallin@stericsson.com>
+ * Author: Linus Walleij <linus.walleij@stericsson.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/mfd/ab3100.h>
+
+/* LDO registers and some handy masking definitions for AB3100 */
+#define AB3100_LDO_A 0x40
+#define AB3100_LDO_C 0x41
+#define AB3100_LDO_D 0x42
+#define AB3100_LDO_E 0x43
+#define AB3100_LDO_E_SLEEP 0x44
+#define AB3100_LDO_F 0x45
+#define AB3100_LDO_G 0x46
+#define AB3100_LDO_H 0x47
+#define AB3100_LDO_H_SLEEP_MODE 0
+#define AB3100_LDO_H_SLEEP_EN 2
+#define AB3100_LDO_ON 4
+#define AB3100_LDO_H_VSEL_AC 5
+#define AB3100_LDO_K 0x48
+#define AB3100_LDO_EXT 0x49
+#define AB3100_BUCK 0x4A
+#define AB3100_BUCK_SLEEP 0x4B
+#define AB3100_REG_ON_MASK 0x10
+
+/**
+ * struct ab3100_regulator
+ * A struct passed around the individual regulator functions
+ * @platform_device: platform device holding this regulator
+ * @ab3100: handle to the AB3100 parent chip
+ * @plfdata: AB3100 platform data passed in at probe time
+ * @regreg: regulator register number in the AB3100
+ * @fixed_voltage: a fixed voltage for this regulator, if this
+ * 0 the voltages array is used instead.
+ * @typ_voltages: an array of available typical voltages for
+ * this regulator
+ * @voltages_len: length of the array of available voltages
+ */
+struct ab3100_regulator {
+ struct regulator_dev *rdev;
+ struct ab3100 *ab3100;
+ struct ab3100_platform_data *plfdata;
+ u8 regreg;
+ int fixed_voltage;
+ int const *typ_voltages;
+ u8 voltages_len;
+};
+
+/* The order in which registers are initialized */
+static const u8 ab3100_reg_init_order[AB3100_NUM_REGULATORS+2] = {
+ AB3100_LDO_A,
+ AB3100_LDO_C,
+ AB3100_LDO_E,
+ AB3100_LDO_E_SLEEP,
+ AB3100_LDO_F,
+ AB3100_LDO_G,
+ AB3100_LDO_H,
+ AB3100_LDO_K,
+ AB3100_LDO_EXT,
+ AB3100_BUCK,
+ AB3100_BUCK_SLEEP,
+ AB3100_LDO_D,
+};
+
+/* Preset (hardware defined) voltages for these regulators */
+#define LDO_A_VOLTAGE 2750000
+#define LDO_C_VOLTAGE 2650000
+#define LDO_D_VOLTAGE 2650000
+
+static const int const ldo_e_buck_typ_voltages[] = {
+ 1800000,
+ 1400000,
+ 1300000,
+ 1200000,
+ 1100000,
+ 1050000,
+ 900000,
+};
+
+static const int const ldo_f_typ_voltages[] = {
+ 1800000,
+ 1400000,
+ 1300000,
+ 1200000,
+ 1100000,
+ 1050000,
+ 2500000,
+ 2650000,
+};
+
+static const int const ldo_g_typ_voltages[] = {
+ 2850000,
+ 2750000,
+ 1800000,
+ 1500000,
+};
+
+static const int const ldo_h_typ_voltages[] = {
+ 2750000,
+ 1800000,
+ 1500000,
+ 1200000,
+};
+
+static const int const ldo_k_typ_voltages[] = {
+ 2750000,
+ 1800000,
+};
+
+
+/* The regulator devices */
+static struct ab3100_regulator
+ab3100_regulators[AB3100_NUM_REGULATORS] = {
+ {
+ .regreg = AB3100_LDO_A,
+ .fixed_voltage = LDO_A_VOLTAGE,
+ },
+ {
+ .regreg = AB3100_LDO_C,
+ .fixed_voltage = LDO_C_VOLTAGE,
+ },
+ {
+ .regreg = AB3100_LDO_D,
+ .fixed_voltage = LDO_D_VOLTAGE,
+ },
+ {
+ .regreg = AB3100_LDO_E,
+ .typ_voltages = ldo_e_buck_typ_voltages,
+ .voltages_len = ARRAY_SIZE(ldo_e_buck_typ_voltages),
+ },
+ {
+ .regreg = AB3100_LDO_F,
+ .typ_voltages = ldo_f_typ_voltages,
+ .voltages_len = ARRAY_SIZE(ldo_f_typ_voltages),
+ },
+ {
+ .regreg = AB3100_LDO_G,
+ .typ_voltages = ldo_g_typ_voltages,
+ .voltages_len = ARRAY_SIZE(ldo_g_typ_voltages),
+ },
+ {
+ .regreg = AB3100_LDO_H,
+ .typ_voltages = ldo_h_typ_voltages,
+ .voltages_len = ARRAY_SIZE(ldo_h_typ_voltages),
+ },
+ {
+ .regreg = AB3100_LDO_K,
+ .typ_voltages = ldo_k_typ_voltages,
+ .voltages_len = ARRAY_SIZE(ldo_k_typ_voltages),
+ },
+ {
+ .regreg = AB3100_LDO_EXT,
+ /* No voltages for the external regulator */
+ },
+ {
+ .regreg = AB3100_BUCK,
+ .typ_voltages = ldo_e_buck_typ_voltages,
+ .voltages_len = ARRAY_SIZE(ldo_e_buck_typ_voltages),
+ },
+};
+
+/*
+ * General functions for enable, disable and is_enabled used for
+ * LDO: A,C,E,F,G,H,K,EXT and BUCK
+ */
+static int ab3100_enable_regulator(struct regulator_dev *reg)
+{
+ struct ab3100_regulator *abreg = reg->reg_data;
+ int err;
+ u8 regval;
+
+ err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg,
+ &regval);
+ if (err) {
+ dev_warn(&reg->dev, "failed to get regid %d value\n",
+ abreg->regreg);
+ return err;
+ }
+
+ /* The regulator is already on, no reason to go further */
+ if (regval & AB3100_REG_ON_MASK)
+ return 0;
+
+ regval |= AB3100_REG_ON_MASK;
+
+ err = ab3100_set_register_interruptible(abreg->ab3100, abreg->regreg,
+ regval);
+ if (err) {
+ dev_warn(&reg->dev, "failed to set regid %d value\n",
+ abreg->regreg);
+ return err;
+ }
+
+ /* Per-regulator power on delay from spec */
+ switch (abreg->regreg) {
+ case AB3100_LDO_A: /* Fallthrough */
+ case AB3100_LDO_C: /* Fallthrough */
+ case AB3100_LDO_D: /* Fallthrough */
+ case AB3100_LDO_E: /* Fallthrough */
+ case AB3100_LDO_H: /* Fallthrough */
+ case AB3100_LDO_K:
+ udelay(200);
+ break;
+ case AB3100_LDO_F:
+ udelay(600);
+ break;
+ case AB3100_LDO_G:
+ udelay(400);
+ break;
+ case AB3100_BUCK:
+ mdelay(1);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int ab3100_disable_regulator(struct regulator_dev *reg)
+{
+ struct ab3100_regulator *abreg = reg->reg_data;
+ int err;
+ u8 regval;
+
+ /*
+ * LDO D is a special regulator. When it is disabled, the entire
+ * system is shut down. So this is handled specially.
+ */
+ if (abreg->regreg == AB3100_LDO_D) {
+ int i;
+
+ dev_info(&reg->dev, "disabling LDO D - shut down system\n");
+ /*
+ * Set regulators to default values, ignore any errors,
+ * we're going DOWN
+ */
+ for (i = 0; i < ARRAY_SIZE(ab3100_reg_init_order); i++) {
+ (void) ab3100_set_register_interruptible(abreg->ab3100,
+ ab3100_reg_init_order[i],
+ abreg->plfdata->reg_initvals[i]);
+ }
+
+ /* Setting LDO D to 0x00 cuts the power to the SoC */
+ return ab3100_set_register_interruptible(abreg->ab3100,
+ AB3100_LDO_D, 0x00U);
+
+ }
+
+ /*
+ * All other regulators are handled here
+ */
+ err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg,
+ &regval);
+ if (err) {
+ dev_err(&reg->dev, "unable to get register 0x%x\n",
+ abreg->regreg);
+ return err;
+ }
+ regval &= ~AB3100_REG_ON_MASK;
+ return ab3100_set_register_interruptible(abreg->ab3100, abreg->regreg,
+ regval);
+}
+
+static int ab3100_is_enabled_regulator(struct regulator_dev *reg)
+{
+ struct ab3100_regulator *abreg = reg->reg_data;
+ u8 regval;
+ int err;
+
+ err = ab3100_get_register_interruptible(abreg->ab3100, abreg->regreg,
+ &regval);
+ if (err) {
+ dev_err(&reg->dev, "unable to get register 0x%x\n",
+ abreg->regreg);
+ return err;
+ }
+
+ return regval & AB3100_REG_ON_MASK;
+}
+
+static int ab3100_list_voltage_regulator(struct regulator_dev *reg,
+ unsigned selector)
+{
+ struct ab3100_regulator *abreg = reg->reg_data;
+
+ if (selector > abreg->voltages_len)
+ return -EINVAL;
+ return abreg->typ_voltages[selector];
+}
+
+static int ab3100_get_voltage_regulator(struct regulator_dev *reg)
+{
+ struct ab3100_regulator *abreg = reg->reg_data;
+ u8 regval;
+ int err;
+
+ /* Return the voltage for fixed regulators immediately */
+ if (abreg->fixed_voltage)
+ return abreg->fixed_voltage;
+
+ /*
+ * For variable types, read out setting and index into
+ * supplied voltage list.
+ */
+ err = ab3100_get_register_interruptible(abreg->ab3100,
+ abreg->regreg, &regval);
+ if (err) {
+ dev_warn(&reg->dev,
+ "failed to get regulator value in register %02x\n",
+ abreg->regreg);
+ return err;
+ }
+
+ /* The 3 highest bits index voltages */
+ regval &= 0xE0;
+ regval >>= 5;
+
+ if (regval > abreg->voltages_len) {
+ dev_err(&reg->dev,
+ "regulator register %02x contains an illegal voltage setting\n",
+ abreg->regreg);
+ return -EINVAL;
+ }
+
+ return abreg->typ_voltages[regval];
+}
+
+static int ab3100_get_best_voltage_index(struct regulator_dev *reg,
+ int min_uV, int max_uV)
+{
+ struct ab3100_regulator *abreg = reg->reg_data;
+ int i;
+ int bestmatch;
+ int bestindex;
+
+ /*
+ * Locate the minimum voltage fitting the criteria on
+ * this regulator. The switchable voltages are not
+ * in strict falling order so we need to check them
+ * all for the best match.
+ */
+ bestmatch = INT_MAX;
+ bestindex = -1;
+ for (i = 0; i < abreg->voltages_len; i++) {
+ if (abreg->typ_voltages[i] <= max_uV &&
+ abreg->typ_voltages[i] >= min_uV &&
+ abreg->typ_voltages[i] < bestmatch) {
+ bestmatch = abreg->typ_voltages[i];
+ bestindex = i;
+ }
+ }
+
+ if (bestindex < 0) {
+ dev_warn(&reg->dev, "requested %d<=x<=%d uV, out of range!\n",
+ min_uV, max_uV);
+ return -EINVAL;
+ }
+ return bestindex;
+}
+
+static int ab3100_set_voltage_regulator(struct regulator_dev *reg,
+ int min_uV, int max_uV)
+{
+ struct ab3100_regulator *abreg = reg->reg_data;
+ u8 regval;
+ int err;
+ int bestindex;
+
+ bestindex = ab3100_get_best_voltage_index(reg, min_uV, max_uV);
+ if (bestindex < 0)
+ return bestindex;
+
+ err = ab3100_get_register_interruptible(abreg->ab3100,
+ abreg->regreg, &regval);
+ if (err) {
+ dev_warn(&reg->dev,
+ "failed to get regulator register %02x\n",
+ abreg->regreg);
+ return err;
+ }
+
+ /* The highest three bits control the variable regulators */
+ regval &= ~0xE0;
+ regval |= (bestindex << 5);
+
+ err = ab3100_set_register_interruptible(abreg->ab3100,
+ abreg->regreg, regval);
+ if (err)
+ dev_warn(&reg->dev, "failed to set regulator register %02x\n",
+ abreg->regreg);
+
+ return err;
+}
+
+static int ab3100_set_suspend_voltage_regulator(struct regulator_dev *reg,
+ int uV)
+{
+ struct ab3100_regulator *abreg = reg->reg_data;
+ u8 regval;
+ int err;
+ int bestindex;
+ u8 targetreg;
+
+ if (abreg->regreg == AB3100_LDO_E)
+ targetreg = AB3100_LDO_E_SLEEP;
+ else if (abreg->regreg == AB3100_BUCK)
+ targetreg = AB3100_BUCK_SLEEP;
+ else
+ return -EINVAL;
+
+ /* LDO E and BUCK have special suspend voltages you can set */
+ bestindex = ab3100_get_best_voltage_index(reg, uV, uV);
+
+ err = ab3100_get_register_interruptible(abreg->ab3100,
+ targetreg, &regval);
+ if (err) {
+ dev_warn(&reg->dev,
+ "failed to get regulator register %02x\n",
+ targetreg);
+ return err;
+ }
+
+ /* The highest three bits control the variable regulators */
+ regval &= ~0xE0;
+ regval |= (bestindex << 5);
+
+ err = ab3100_set_register_interruptible(abreg->ab3100,
+ targetreg, regval);
+ if (err)
+ dev_warn(&reg->dev, "failed to set regulator register %02x\n",
+ abreg->regreg);
+
+ return err;
+}
+
+/*
+ * The external regulator can just define a fixed voltage.
+ */
+static int ab3100_get_voltage_regulator_external(struct regulator_dev *reg)
+{
+ struct ab3100_regulator *abreg = reg->reg_data;
+
+ return abreg->plfdata->external_voltage;
+}
+
+static struct regulator_ops regulator_ops_fixed = {
+ .enable = ab3100_enable_regulator,
+ .disable = ab3100_disable_regulator,
+ .is_enabled = ab3100_is_enabled_regulator,
+ .get_voltage = ab3100_get_voltage_regulator,
+};
+
+static struct regulator_ops regulator_ops_variable = {
+ .enable = ab3100_enable_regulator,
+ .disable = ab3100_disable_regulator,
+ .is_enabled = ab3100_is_enabled_regulator,
+ .get_voltage = ab3100_get_voltage_regulator,
+ .set_voltage = ab3100_set_voltage_regulator,
+ .list_voltage = ab3100_list_voltage_regulator,
+};
+
+static struct regulator_ops regulator_ops_variable_sleepable = {
+ .enable = ab3100_enable_regulator,
+ .disable = ab3100_disable_regulator,
+ .is_enabled = ab3100_is_enabled_regulator,
+ .get_voltage = ab3100_get_voltage_regulator,
+ .set_voltage = ab3100_set_voltage_regulator,
+ .set_suspend_voltage = ab3100_set_suspend_voltage_regulator,
+ .list_voltage = ab3100_list_voltage_regulator,
+};
+
+/*
+ * LDO EXT is an external regulator so it is really
+ * not possible to set any voltage locally here, AB3100
+ * is an on/off switch plain an simple. The external
+ * voltage is defined in the board set-up if any.
+ */
+static struct regulator_ops regulator_ops_external = {
+ .enable = ab3100_enable_regulator,
+ .disable = ab3100_disable_regulator,
+ .is_enabled = ab3100_is_enabled_regulator,
+ .get_voltage = ab3100_get_voltage_regulator_external,
+};
+
+static struct regulator_desc
+ab3100_regulator_desc[AB3100_NUM_REGULATORS] = {
+ {
+ .name = "LDO_A",
+ .id = AB3100_LDO_A,
+ .ops = &regulator_ops_fixed,
+ .type = REGULATOR_VOLTAGE,
+ },
+ {
+ .name = "LDO_C",
+ .id = AB3100_LDO_C,
+ .ops = &regulator_ops_fixed,
+ .type = REGULATOR_VOLTAGE,
+ },
+ {
+ .name = "LDO_D",
+ .id = AB3100_LDO_D,
+ .ops = &regulator_ops_fixed,
+ .type = REGULATOR_VOLTAGE,
+ },
+ {
+ .name = "LDO_E",
+ .id = AB3100_LDO_E,
+ .ops = &regulator_ops_variable_sleepable,
+ .n_voltages = ARRAY_SIZE(ldo_e_buck_typ_voltages),
+ .type = REGULATOR_VOLTAGE,
+ },
+ {
+ .name = "LDO_F",
+ .id = AB3100_LDO_F,
+ .ops = &regulator_ops_variable,
+ .n_voltages = ARRAY_SIZE(ldo_f_typ_voltages),
+ .type = REGULATOR_VOLTAGE,
+ },
+ {
+ .name = "LDO_G",
+ .id = AB3100_LDO_G,
+ .ops = &regulator_ops_variable,
+ .n_voltages = ARRAY_SIZE(ldo_g_typ_voltages),
+ .type = REGULATOR_VOLTAGE,
+ },
+ {
+ .name = "LDO_H",
+ .id = AB3100_LDO_H,
+ .ops = &regulator_ops_variable,
+ .n_voltages = ARRAY_SIZE(ldo_h_typ_voltages),
+ .type = REGULATOR_VOLTAGE,
+ },
+ {
+ .name = "LDO_K",
+ .id = AB3100_LDO_K,
+ .ops = &regulator_ops_variable,
+ .n_voltages = ARRAY_SIZE(ldo_k_typ_voltages),
+ .type = REGULATOR_VOLTAGE,
+ },
+ {
+ .name = "LDO_EXT",
+ .id = AB3100_LDO_EXT,
+ .ops = &regulator_ops_external,
+ .type = REGULATOR_VOLTAGE,
+ },
+ {
+ .name = "BUCK",
+ .id = AB3100_BUCK,
+ .ops = &regulator_ops_variable_sleepable,
+ .n_voltages = ARRAY_SIZE(ldo_e_buck_typ_voltages),
+ .type = REGULATOR_VOLTAGE,
+ },
+};
+
+/*
+ * NOTE: the following functions are regulators pluralis - it is the
+ * binding to the AB3100 core driver and the parent platform device
+ * for all the different regulators.
+ */
+
+static int __init ab3100_regulators_probe(struct platform_device *pdev)
+{
+ struct ab3100_platform_data *plfdata = pdev->dev.platform_data;
+ struct ab3100 *ab3100 = platform_get_drvdata(pdev);
+ int err = 0;
+ u8 data;
+ int i;
+
+ /* Check chip state */
+ err = ab3100_get_register_interruptible(ab3100,
+ AB3100_LDO_D, &data);
+ if (err) {
+ dev_err(&pdev->dev, "could not read initial status of LDO_D\n");
+ return err;
+ }
+ if (data & 0x10)
+ dev_notice(&pdev->dev,
+ "chip is already in active mode (Warm start)\n");
+ else
+ dev_notice(&pdev->dev,
+ "chip is in inactive mode (Cold start)\n");
+
+ /* Set up regulators */
+ for (i = 0; i < ARRAY_SIZE(ab3100_reg_init_order); i++) {
+ err = ab3100_set_register_interruptible(ab3100,
+ ab3100_reg_init_order[i],
+ plfdata->reg_initvals[i]);
+ if (err) {
+ dev_err(&pdev->dev, "regulator initialization failed with error %d\n",
+ err);
+ return err;
+ }
+ }
+
+ if (err) {
+ dev_err(&pdev->dev,
+ "LDO D regulator initialization failed with error %d\n",
+ err);
+ return err;
+ }
+
+ /* Register the regulators */
+ for (i = 0; i < AB3100_NUM_REGULATORS; i++) {
+ struct ab3100_regulator *reg = &ab3100_regulators[i];
+ struct regulator_dev *rdev;
+
+ /*
+ * Initialize per-regulator struct.
+ * Inherit platform data, this comes down from the
+ * i2c boarddata, from the machine. So if you want to
+ * see what it looks like for a certain machine, go
+ * into the machine I2C setup.
+ */
+ reg->ab3100 = ab3100;
+ reg->plfdata = plfdata;
+
+ /*
+ * Register the regulator, pass around
+ * the ab3100_regulator struct
+ */
+ rdev = regulator_register(&ab3100_regulator_desc[i],
+ &pdev->dev,
+ &plfdata->reg_constraints[i],
+ reg);
+
+ if (IS_ERR(rdev)) {
+ err = PTR_ERR(rdev);
+ dev_err(&pdev->dev,
+ "%s: failed to register regulator %s err %d\n",
+ __func__, ab3100_regulator_desc[i].name,
+ err);
+ i--;
+ /* remove the already registered regulators */
+ while (i > 0) {
+ regulator_unregister(ab3100_regulators[i].rdev);
+ i--;
+ }
+ return err;
+ }
+
+ /* Then set a pointer back to the registered regulator */
+ reg->rdev = rdev;
+ }
+
+ return 0;
+}
+
+static int __exit ab3100_regulators_remove(struct platform_device *pdev)
+{
+ int i;
+
+ for (i = 0; i < AB3100_NUM_REGULATORS; i++) {
+ struct ab3100_regulator *reg = &ab3100_regulators[i];
+
+ regulator_unregister(reg->rdev);
+ }
+ return 0;
+}
+
+static struct platform_driver ab3100_regulators_driver = {
+ .driver = {
+ .name = "ab3100-regulators",
+ .owner = THIS_MODULE,
+ },
+ .probe = ab3100_regulators_probe,
+ .remove = __exit_p(ab3100_regulators_remove),
+};
+
+static __init int ab3100_regulators_init(void)
+{
+ return platform_driver_register(&ab3100_regulators_driver);
+}
+
+static __exit void ab3100_regulators_exit(void)
+{
+ platform_driver_register(&ab3100_regulators_driver);
+}
+
+subsys_initcall(ab3100_regulators_init);
+module_exit(ab3100_regulators_exit);
+
+MODULE_AUTHOR("Mattias Wallin <mattias.wallin@stericsson.com>");
+MODULE_DESCRIPTION("AB3100 Regulator driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ab3100-regulators");
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index 98c3a74e994..91ba9bfaa70 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -1864,6 +1864,30 @@ int regulator_notifier_call_chain(struct regulator_dev *rdev,
}
EXPORT_SYMBOL_GPL(regulator_notifier_call_chain);
+/**
+ * regulator_mode_to_status - convert a regulator mode into a status
+ *
+ * @mode: Mode to convert
+ *
+ * Convert a regulator mode into a status.
+ */
+int regulator_mode_to_status(unsigned int mode)
+{
+ switch (mode) {
+ case REGULATOR_MODE_FAST:
+ return REGULATOR_STATUS_FAST;
+ case REGULATOR_MODE_NORMAL:
+ return REGULATOR_STATUS_NORMAL;
+ case REGULATOR_MODE_IDLE:
+ return REGULATOR_STATUS_IDLE;
+ case REGULATOR_STATUS_STANDBY:
+ return REGULATOR_STATUS_STANDBY;
+ default:
+ return 0;
+ }
+}
+EXPORT_SYMBOL_GPL(regulator_mode_to_status);
+
/*
* To avoid cluttering sysfs (and memory) with useless state, only
* create attributes that can be meaningfully displayed.
diff --git a/drivers/regulator/mc13783.c b/drivers/regulator/mc13783.c
new file mode 100644
index 00000000000..710211f6744
--- /dev/null
+++ b/drivers/regulator/mc13783.c
@@ -0,0 +1,410 @@
+/*
+ * Regulator Driver for Freescale MC13783 PMIC
+ *
+ * Copyright (C) 2008 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/mfd/mc13783-private.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/driver.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/mc13783.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/err.h>
+
+struct mc13783_regulator {
+ struct regulator_desc desc;
+ int reg;
+ int enable_bit;
+};
+
+static struct regulator_ops mc13783_regulator_ops;
+
+static struct mc13783_regulator mc13783_regulators[] = {
+ [MC13783_SW_SW3] = {
+ .desc = {
+ .name = "SW_SW3",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_SW_SW3,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_SWITCHERS_5,
+ .enable_bit = MC13783_SWCTRL_SW3_EN,
+ },
+ [MC13783_SW_PLL] = {
+ .desc = {
+ .name = "SW_PLL",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_SW_PLL,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_SWITCHERS_4,
+ .enable_bit = MC13783_SWCTRL_PLL_EN,
+ },
+ [MC13783_REGU_VAUDIO] = {
+ .desc = {
+ .name = "REGU_VAUDIO",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VAUDIO,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_0,
+ .enable_bit = MC13783_REGCTRL_VAUDIO_EN,
+ },
+ [MC13783_REGU_VIOHI] = {
+ .desc = {
+ .name = "REGU_VIOHI",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VIOHI,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_0,
+ .enable_bit = MC13783_REGCTRL_VIOHI_EN,
+ },
+ [MC13783_REGU_VIOLO] = {
+ .desc = {
+ .name = "REGU_VIOLO",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VIOLO,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_0,
+ .enable_bit = MC13783_REGCTRL_VIOLO_EN,
+ },
+ [MC13783_REGU_VDIG] = {
+ .desc = {
+ .name = "REGU_VDIG",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VDIG,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_0,
+ .enable_bit = MC13783_REGCTRL_VDIG_EN,
+ },
+ [MC13783_REGU_VGEN] = {
+ .desc = {
+ .name = "REGU_VGEN",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VGEN,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_0,
+ .enable_bit = MC13783_REGCTRL_VGEN_EN,
+ },
+ [MC13783_REGU_VRFDIG] = {
+ .desc = {
+ .name = "REGU_VRFDIG",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VRFDIG,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_0,
+ .enable_bit = MC13783_REGCTRL_VRFDIG_EN,
+ },
+ [MC13783_REGU_VRFREF] = {
+ .desc = {
+ .name = "REGU_VRFREF",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VRFREF,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_0,
+ .enable_bit = MC13783_REGCTRL_VRFREF_EN,
+ },
+ [MC13783_REGU_VRFCP] = {
+ .desc = {
+ .name = "REGU_VRFCP",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VRFCP,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_0,
+ .enable_bit = MC13783_REGCTRL_VRFCP_EN,
+ },
+ [MC13783_REGU_VSIM] = {
+ .desc = {
+ .name = "REGU_VSIM",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VSIM,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_1,
+ .enable_bit = MC13783_REGCTRL_VSIM_EN,
+ },
+ [MC13783_REGU_VESIM] = {
+ .desc = {
+ .name = "REGU_VESIM",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VESIM,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_1,
+ .enable_bit = MC13783_REGCTRL_VESIM_EN,
+ },
+ [MC13783_REGU_VCAM] = {
+ .desc = {
+ .name = "REGU_VCAM",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VCAM,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_1,
+ .enable_bit = MC13783_REGCTRL_VCAM_EN,
+ },
+ [MC13783_REGU_VRFBG] = {
+ .desc = {
+ .name = "REGU_VRFBG",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VRFBG,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_1,
+ .enable_bit = MC13783_REGCTRL_VRFBG_EN,
+ },
+ [MC13783_REGU_VVIB] = {
+ .desc = {
+ .name = "REGU_VVIB",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VVIB,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_1,
+ .enable_bit = MC13783_REGCTRL_VVIB_EN,
+ },
+ [MC13783_REGU_VRF1] = {
+ .desc = {
+ .name = "REGU_VRF1",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VRF1,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_1,
+ .enable_bit = MC13783_REGCTRL_VRF1_EN,
+ },
+ [MC13783_REGU_VRF2] = {
+ .desc = {
+ .name = "REGU_VRF2",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VRF2,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_1,
+ .enable_bit = MC13783_REGCTRL_VRF2_EN,
+ },
+ [MC13783_REGU_VMMC1] = {
+ .desc = {
+ .name = "REGU_VMMC1",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VMMC1,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_1,
+ .enable_bit = MC13783_REGCTRL_VMMC1_EN,
+ },
+ [MC13783_REGU_VMMC2] = {
+ .desc = {
+ .name = "REGU_VMMC2",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_VMMC2,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_REGULATOR_MODE_1,
+ .enable_bit = MC13783_REGCTRL_VMMC2_EN,
+ },
+ [MC13783_REGU_GPO1] = {
+ .desc = {
+ .name = "REGU_GPO1",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_GPO1,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_POWER_MISCELLANEOUS,
+ .enable_bit = MC13783_REGCTRL_GPO1_EN,
+ },
+ [MC13783_REGU_GPO2] = {
+ .desc = {
+ .name = "REGU_GPO2",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_GPO2,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_POWER_MISCELLANEOUS,
+ .enable_bit = MC13783_REGCTRL_GPO2_EN,
+ },
+ [MC13783_REGU_GPO3] = {
+ .desc = {
+ .name = "REGU_GPO3",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_GPO3,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_POWER_MISCELLANEOUS,
+ .enable_bit = MC13783_REGCTRL_GPO3_EN,
+ },
+ [MC13783_REGU_GPO4] = {
+ .desc = {
+ .name = "REGU_GPO4",
+ .ops = &mc13783_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .id = MC13783_REGU_GPO4,
+ .owner = THIS_MODULE,
+ },
+ .reg = MC13783_REG_POWER_MISCELLANEOUS,
+ .enable_bit = MC13783_REGCTRL_GPO4_EN,
+ },
+};
+
+struct mc13783_priv {
+ struct regulator_desc desc[ARRAY_SIZE(mc13783_regulators)];
+ struct mc13783 *mc13783;
+ struct regulator_dev *regulators[0];
+};
+
+static int mc13783_enable(struct regulator_dev *rdev)
+{
+ struct mc13783_priv *priv = rdev_get_drvdata(rdev);
+ int id = rdev_get_id(rdev);
+
+ dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id);
+
+ return mc13783_set_bits(priv->mc13783, mc13783_regulators[id].reg,
+ mc13783_regulators[id].enable_bit,
+ mc13783_regulators[id].enable_bit);
+}
+
+static int mc13783_disable(struct regulator_dev *rdev)
+{
+ struct mc13783_priv *priv = rdev_get_drvdata(rdev);
+ int id = rdev_get_id(rdev);
+
+ dev_dbg(rdev_get_dev(rdev), "%s id: %d\n", __func__, id);
+
+ return mc13783_set_bits(priv->mc13783, mc13783_regulators[id].reg,
+ mc13783_regulators[id].enable_bit, 0);
+}
+
+static int mc13783_is_enabled(struct regulator_dev *rdev)
+{
+ struct mc13783_priv *priv = rdev_get_drvdata(rdev);
+ int ret, id = rdev_get_id(rdev);
+ unsigned int val;
+
+ ret = mc13783_reg_read(priv->mc13783, mc13783_regulators[id].reg, &val);
+ if (ret)
+ return ret;
+
+ return (val & mc13783_regulators[id].enable_bit) != 0;
+}
+
+static struct regulator_ops mc13783_regulator_ops = {
+ .enable = mc13783_enable,
+ .disable = mc13783_disable,
+ .is_enabled = mc13783_is_enabled,
+};
+
+static int __devinit mc13783_regulator_probe(struct platform_device *pdev)
+{
+ struct mc13783_priv *priv;
+ struct mc13783 *mc13783 = dev_get_drvdata(pdev->dev.parent);
+ struct mc13783_regulator_init_data *init_data;
+ int i, ret;
+
+ dev_dbg(&pdev->dev, "mc13783_regulator_probe id %d\n", pdev->id);
+
+ priv = kzalloc(sizeof(*priv) + mc13783->num_regulators * sizeof(void *),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->mc13783 = mc13783;
+
+ for (i = 0; i < mc13783->num_regulators; i++) {
+ init_data = &mc13783->regulators[i];
+ priv->regulators[i] = regulator_register(
+ &mc13783_regulators[init_data->id].desc,
+ &pdev->dev, init_data->init_data, priv);
+
+ if (IS_ERR(priv->regulators[i])) {
+ dev_err(&pdev->dev, "failed to register regulator %s\n",
+ mc13783_regulators[i].desc.name);
+ ret = PTR_ERR(priv->regulators[i]);
+ goto err;
+ }
+ }
+
+ platform_set_drvdata(pdev, priv);
+
+ return 0;
+err:
+ while (--i >= 0)
+ regulator_unregister(priv->regulators[i]);
+
+ kfree(priv);
+
+ return ret;
+}
+
+static int __devexit mc13783_regulator_remove(struct platform_device *pdev)
+{
+ struct mc13783_priv *priv = platform_get_drvdata(pdev);
+ struct mc13783 *mc13783 = priv->mc13783;
+ int i;
+
+ for (i = 0; i < mc13783->num_regulators; i++)
+ regulator_unregister(priv->regulators[i]);
+
+ return 0;
+}
+
+static struct platform_driver mc13783_regulator_driver = {
+ .driver = {
+ .name = "mc13783-regulator",
+ .owner = THIS_MODULE,
+ },
+ .remove = __devexit_p(mc13783_regulator_remove),
+};
+
+static int __init mc13783_regulator_init(void)
+{
+ return platform_driver_probe(&mc13783_regulator_driver,
+ mc13783_regulator_probe);
+}
+subsys_initcall(mc13783_regulator_init);
+
+static void __exit mc13783_regulator_exit(void)
+{
+ platform_driver_unregister(&mc13783_regulator_driver);
+}
+module_exit(mc13783_regulator_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de");
+MODULE_DESCRIPTION("Regulator Driver for Freescale MC13783 PMIC");
+MODULE_ALIAS("platform:mc13783-regulator");
diff --git a/drivers/regulator/pcap-regulator.c b/drivers/regulator/pcap-regulator.c
new file mode 100644
index 00000000000..33d7d899e03
--- /dev/null
+++ b/drivers/regulator/pcap-regulator.c
@@ -0,0 +1,318 @@
+/*
+ * PCAP2 Regulator Driver
+ *
+ * Copyright (c) 2009 Daniel Ribeiro <drwyrm@gmail.com>
+ *
+ * This program 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/mfd/ezx-pcap.h>
+
+static const u16 V1_table[] = {
+ 2775, 1275, 1600, 1725, 1825, 1925, 2075, 2275,
+};
+
+static const u16 V2_table[] = {
+ 2500, 2775,
+};
+
+static const u16 V3_table[] = {
+ 1075, 1275, 1550, 1725, 1876, 1950, 2075, 2275,
+};
+
+static const u16 V4_table[] = {
+ 1275, 1550, 1725, 1875, 1950, 2075, 2275, 2775,
+};
+
+static const u16 V5_table[] = {
+ 1875, 2275, 2475, 2775,
+};
+
+static const u16 V6_table[] = {
+ 2475, 2775,
+};
+
+static const u16 V7_table[] = {
+ 1875, 2775,
+};
+
+#define V8_table V4_table
+
+static const u16 V9_table[] = {
+ 1575, 1875, 2475, 2775,
+};
+
+static const u16 V10_table[] = {
+ 5000,
+};
+
+static const u16 VAUX1_table[] = {
+ 1875, 2475, 2775, 3000,
+};
+
+#define VAUX2_table VAUX1_table
+
+static const u16 VAUX3_table[] = {
+ 1200, 1200, 1200, 1200, 1400, 1600, 1800, 2000,
+ 2200, 2400, 2600, 2800, 3000, 3200, 3400, 3600,
+};
+
+static const u16 VAUX4_table[] = {
+ 1800, 1800, 3000, 5000,
+};
+
+static const u16 VSIM_table[] = {
+ 1875, 3000,
+};
+
+static const u16 VSIM2_table[] = {
+ 1875,
+};
+
+static const u16 VVIB_table[] = {
+ 1300, 1800, 2000, 3000,
+};
+
+static const u16 SW1_table[] = {
+ 900, 950, 1000, 1050, 1100, 1150, 1200, 1250,
+ 1300, 1350, 1400, 1450, 1500, 1600, 1875, 2250,
+};
+
+#define SW2_table SW1_table
+
+static const u16 SW3_table[] = {
+ 4000, 4500, 5000, 5500,
+};
+
+struct pcap_regulator {
+ const u8 reg;
+ const u8 en;
+ const u8 index;
+ const u8 stby;
+ const u8 lowpwr;
+ const u8 n_voltages;
+ const u16 *voltage_table;
+};
+
+#define NA 0xff
+
+#define VREG_INFO(_vreg, _reg, _en, _index, _stby, _lowpwr) \
+ [_vreg] = { \
+ .reg = _reg, \
+ .en = _en, \
+ .index = _index, \
+ .stby = _stby, \
+ .lowpwr = _lowpwr, \
+ .n_voltages = ARRAY_SIZE(_vreg##_table), \
+ .voltage_table = _vreg##_table, \
+ }
+
+static struct pcap_regulator vreg_table[] = {
+ VREG_INFO(V1, PCAP_REG_VREG1, 1, 2, 18, 0),
+ VREG_INFO(V2, PCAP_REG_VREG1, 5, 6, 19, 22),
+ VREG_INFO(V3, PCAP_REG_VREG1, 7, 8, 20, 23),
+ VREG_INFO(V4, PCAP_REG_VREG1, 11, 12, 21, 24),
+ /* V5 STBY and LOWPWR are on PCAP_REG_VREG2 */
+ VREG_INFO(V5, PCAP_REG_VREG1, 15, 16, 12, 19),
+
+ VREG_INFO(V6, PCAP_REG_VREG2, 1, 2, 14, 20),
+ VREG_INFO(V7, PCAP_REG_VREG2, 3, 4, 15, 21),
+ VREG_INFO(V8, PCAP_REG_VREG2, 5, 6, 16, 22),
+ VREG_INFO(V9, PCAP_REG_VREG2, 9, 10, 17, 23),
+ VREG_INFO(V10, PCAP_REG_VREG2, 10, NA, 18, 24),
+
+ VREG_INFO(VAUX1, PCAP_REG_AUXVREG, 1, 2, 22, 23),
+ /* VAUX2 ... VSIM2 STBY and LOWPWR are on PCAP_REG_LOWPWR */
+ VREG_INFO(VAUX2, PCAP_REG_AUXVREG, 4, 5, 0, 1),
+ VREG_INFO(VAUX3, PCAP_REG_AUXVREG, 7, 8, 2, 3),
+ VREG_INFO(VAUX4, PCAP_REG_AUXVREG, 12, 13, 4, 5),
+ VREG_INFO(VSIM, PCAP_REG_AUXVREG, 17, 18, NA, 6),
+ VREG_INFO(VSIM2, PCAP_REG_AUXVREG, 16, NA, NA, 7),
+ VREG_INFO(VVIB, PCAP_REG_AUXVREG, 19, 20, NA, NA),
+
+ VREG_INFO(SW1, PCAP_REG_SWCTRL, 1, 2, NA, NA),
+ VREG_INFO(SW2, PCAP_REG_SWCTRL, 6, 7, NA, NA),
+ /* SW3 STBY is on PCAP_REG_AUXVREG */
+ VREG_INFO(SW3, PCAP_REG_SWCTRL, 11, 12, 24, NA),
+
+ /* SWxS used to control SWx voltage on standby */
+/* VREG_INFO(SW1S, PCAP_REG_LOWPWR, NA, 12, NA, NA),
+ VREG_INFO(SW2S, PCAP_REG_LOWPWR, NA, 20, NA, NA), */
+};
+
+static int pcap_regulator_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV)
+{
+ struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)];
+ void *pcap = rdev_get_drvdata(rdev);
+ int uV;
+ u8 i;
+
+ /* the regulator doesn't support voltage switching */
+ if (vreg->n_voltages == 1)
+ return -EINVAL;
+
+ for (i = 0; i < vreg->n_voltages; i++) {
+ /* For V1 the first is not the best match */
+ if (i == 0 && rdev_get_id(rdev) == V1)
+ i = 1;
+ else if (i + 1 == vreg->n_voltages && rdev_get_id(rdev) == V1)
+ i = 0;
+
+ uV = vreg->voltage_table[i] * 1000;
+ if (min_uV <= uV && uV <= max_uV)
+ return ezx_pcap_set_bits(pcap, vreg->reg,
+ (vreg->n_voltages - 1) << vreg->index,
+ i << vreg->index);
+
+ if (i == 0 && rdev_get_id(rdev) == V1)
+ i = vreg->n_voltages - 1;
+ }
+
+ /* the requested voltage range is not supported by this regulator */
+ return -EINVAL;
+}
+
+static int pcap_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)];
+ void *pcap = rdev_get_drvdata(rdev);
+ u32 tmp;
+ int mV;
+
+ if (vreg->n_voltages == 1)
+ return vreg->voltage_table[0] * 1000;
+
+ ezx_pcap_read(pcap, vreg->reg, &tmp);
+ tmp = ((tmp >> vreg->index) & (vreg->n_voltages - 1));
+ mV = vreg->voltage_table[tmp];
+
+ return mV * 1000;
+}
+
+static int pcap_regulator_enable(struct regulator_dev *rdev)
+{
+ struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)];
+ void *pcap = rdev_get_drvdata(rdev);
+
+ if (vreg->en == NA)
+ return -EINVAL;
+
+ return ezx_pcap_set_bits(pcap, vreg->reg, 1 << vreg->en, 1 << vreg->en);
+}
+
+static int pcap_regulator_disable(struct regulator_dev *rdev)
+{
+ struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)];
+ void *pcap = rdev_get_drvdata(rdev);
+
+ if (vreg->en == NA)
+ return -EINVAL;
+
+ return ezx_pcap_set_bits(pcap, vreg->reg, 1 << vreg->en, 0);
+}
+
+static int pcap_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)];
+ void *pcap = rdev_get_drvdata(rdev);
+ u32 tmp;
+
+ if (vreg->en == NA)
+ return -EINVAL;
+
+ ezx_pcap_read(pcap, vreg->reg, &tmp);
+ return (tmp >> vreg->en) & 1;
+}
+
+static int pcap_regulator_list_voltage(struct regulator_dev *rdev,
+ unsigned int index)
+{
+ struct pcap_regulator *vreg = &vreg_table[rdev_get_id(rdev)];
+
+ return vreg->voltage_table[index] * 1000;
+}
+
+static struct regulator_ops pcap_regulator_ops = {
+ .list_voltage = pcap_regulator_list_voltage,
+ .set_voltage = pcap_regulator_set_voltage,
+ .get_voltage = pcap_regulator_get_voltage,
+ .enable = pcap_regulator_enable,
+ .disable = pcap_regulator_disable,
+ .is_enabled = pcap_regulator_is_enabled,
+};
+
+#define VREG(_vreg) \
+ [_vreg] = { \
+ .name = #_vreg, \
+ .id = _vreg, \
+ .n_voltages = ARRAY_SIZE(_vreg##_table), \
+ .ops = &pcap_regulator_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+ }
+
+static struct regulator_desc pcap_regulators[] = {
+ VREG(V1), VREG(V2), VREG(V3), VREG(V4), VREG(V5), VREG(V6), VREG(V7),
+ VREG(V8), VREG(V9), VREG(V10), VREG(VAUX1), VREG(VAUX2), VREG(VAUX3),
+ VREG(VAUX4), VREG(VSIM), VREG(VSIM2), VREG(VVIB), VREG(SW1), VREG(SW2),
+};
+
+static int __devinit pcap_regulator_probe(struct platform_device *pdev)
+{
+ struct regulator_dev *rdev;
+ void *pcap = dev_get_drvdata(pdev->dev.parent);
+
+ rdev = regulator_register(&pcap_regulators[pdev->id], &pdev->dev,
+ pdev->dev.platform_data, pcap);
+ if (IS_ERR(rdev))
+ return PTR_ERR(rdev);
+
+ platform_set_drvdata(pdev, rdev);
+
+ return 0;
+}
+
+static int __devexit pcap_regulator_remove(struct platform_device *pdev)
+{
+ struct regulator_dev *rdev = platform_get_drvdata(pdev);
+
+ regulator_unregister(rdev);
+
+ return 0;
+}
+
+static struct platform_driver pcap_regulator_driver = {
+ .driver = {
+ .name = "pcap-regulator",
+ },
+ .probe = pcap_regulator_probe,
+ .remove = __devexit_p(pcap_regulator_remove),
+};
+
+static int __init pcap_regulator_init(void)
+{
+ return platform_driver_register(&pcap_regulator_driver);
+}
+
+static void __exit pcap_regulator_exit(void)
+{
+ platform_driver_unregister(&pcap_regulator_driver);
+}
+
+subsys_initcall(pcap_regulator_init);
+module_exit(pcap_regulator_exit);
+
+MODULE_AUTHOR("Daniel Ribeiro <drwyrm@gmail.com>");
+MODULE_DESCRIPTION("PCAP2 Regulator Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/regulator/wm831x-dcdc.c b/drivers/regulator/wm831x-dcdc.c
new file mode 100644
index 00000000000..2eefc1a0cf0
--- /dev/null
+++ b/drivers/regulator/wm831x-dcdc.c
@@ -0,0 +1,862 @@
+/*
+ * wm831x-dcdc.c -- DC-DC buck convertor driver for the WM831x series
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program 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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/regulator.h>
+#include <linux/mfd/wm831x/pdata.h>
+
+#define WM831X_BUCKV_MAX_SELECTOR 0x68
+#define WM831X_BUCKP_MAX_SELECTOR 0x66
+
+#define WM831X_DCDC_MODE_FAST 0
+#define WM831X_DCDC_MODE_NORMAL 1
+#define WM831X_DCDC_MODE_IDLE 2
+#define WM831X_DCDC_MODE_STANDBY 3
+
+#define WM831X_DCDC_MAX_NAME 6
+
+/* Register offsets in control block */
+#define WM831X_DCDC_CONTROL_1 0
+#define WM831X_DCDC_CONTROL_2 1
+#define WM831X_DCDC_ON_CONFIG 2
+#define WM831X_DCDC_SLEEP_CONTROL 3
+
+/*
+ * Shared
+ */
+
+struct wm831x_dcdc {
+ char name[WM831X_DCDC_MAX_NAME];
+ struct regulator_desc desc;
+ int base;
+ struct wm831x *wm831x;
+ struct regulator_dev *regulator;
+};
+
+static int wm831x_dcdc_is_enabled(struct regulator_dev *rdev)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ int mask = 1 << rdev_get_id(rdev);
+ int reg;
+
+ reg = wm831x_reg_read(wm831x, WM831X_DCDC_ENABLE);
+ if (reg < 0)
+ return reg;
+
+ if (reg & mask)
+ return 1;
+ else
+ return 0;
+}
+
+static int wm831x_dcdc_enable(struct regulator_dev *rdev)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ int mask = 1 << rdev_get_id(rdev);
+
+ return wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, mask, mask);
+}
+
+static int wm831x_dcdc_disable(struct regulator_dev *rdev)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ int mask = 1 << rdev_get_id(rdev);
+
+ return wm831x_set_bits(wm831x, WM831X_DCDC_ENABLE, mask, 0);
+}
+
+static unsigned int wm831x_dcdc_get_mode(struct regulator_dev *rdev)
+
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG;
+ int val;
+
+ val = wm831x_reg_read(wm831x, reg);
+ if (val < 0)
+ return val;
+
+ val = (val & WM831X_DC1_ON_MODE_MASK) >> WM831X_DC1_ON_MODE_SHIFT;
+
+ switch (val) {
+ case WM831X_DCDC_MODE_FAST:
+ return REGULATOR_MODE_FAST;
+ case WM831X_DCDC_MODE_NORMAL:
+ return REGULATOR_MODE_NORMAL;
+ case WM831X_DCDC_MODE_STANDBY:
+ return REGULATOR_MODE_STANDBY;
+ case WM831X_DCDC_MODE_IDLE:
+ return REGULATOR_MODE_IDLE;
+ default:
+ BUG();
+ }
+}
+
+static int wm831x_dcdc_set_mode_int(struct wm831x *wm831x, int reg,
+ unsigned int mode)
+{
+ int val;
+
+ switch (mode) {
+ case REGULATOR_MODE_FAST:
+ val = WM831X_DCDC_MODE_FAST;
+ break;
+ case REGULATOR_MODE_NORMAL:
+ val = WM831X_DCDC_MODE_NORMAL;
+ break;
+ case REGULATOR_MODE_STANDBY:
+ val = WM831X_DCDC_MODE_STANDBY;
+ break;
+ case REGULATOR_MODE_IDLE:
+ val = WM831X_DCDC_MODE_IDLE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return wm831x_set_bits(wm831x, reg, WM831X_DC1_ON_MODE_MASK,
+ val << WM831X_DC1_ON_MODE_SHIFT);
+}
+
+static int wm831x_dcdc_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG;
+
+ return wm831x_dcdc_set_mode_int(wm831x, reg, mode);
+}
+
+static int wm831x_dcdc_set_suspend_mode(struct regulator_dev *rdev,
+ unsigned int mode)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL;
+
+ return wm831x_dcdc_set_mode_int(wm831x, reg, mode);
+}
+
+static int wm831x_dcdc_get_status(struct regulator_dev *rdev)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ int ret;
+
+ /* First, check for errors */
+ ret = wm831x_reg_read(wm831x, WM831X_DCDC_UV_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & (1 << rdev_get_id(rdev))) {
+ dev_dbg(wm831x->dev, "DCDC%d under voltage\n",
+ rdev_get_id(rdev) + 1);
+ return REGULATOR_STATUS_ERROR;
+ }
+
+ /* DCDC1 and DCDC2 can additionally detect high voltage/current */
+ if (rdev_get_id(rdev) < 2) {
+ if (ret & (WM831X_DC1_OV_STS << rdev_get_id(rdev))) {
+ dev_dbg(wm831x->dev, "DCDC%d over voltage\n",
+ rdev_get_id(rdev) + 1);
+ return REGULATOR_STATUS_ERROR;
+ }
+
+ if (ret & (WM831X_DC1_HC_STS << rdev_get_id(rdev))) {
+ dev_dbg(wm831x->dev, "DCDC%d over current\n",
+ rdev_get_id(rdev) + 1);
+ return REGULATOR_STATUS_ERROR;
+ }
+ }
+
+ /* Is the regulator on? */
+ ret = wm831x_reg_read(wm831x, WM831X_DCDC_STATUS);
+ if (ret < 0)
+ return ret;
+ if (!(ret & (1 << rdev_get_id(rdev))))
+ return REGULATOR_STATUS_OFF;
+
+ /* TODO: When we handle hardware control modes so we can report the
+ * current mode. */
+ return REGULATOR_STATUS_ON;
+}
+
+static irqreturn_t wm831x_dcdc_uv_irq(int irq, void *data)
+{
+ struct wm831x_dcdc *dcdc = data;
+
+ regulator_notifier_call_chain(dcdc->regulator,
+ REGULATOR_EVENT_UNDER_VOLTAGE,
+ NULL);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t wm831x_dcdc_oc_irq(int irq, void *data)
+{
+ struct wm831x_dcdc *dcdc = data;
+
+ regulator_notifier_call_chain(dcdc->regulator,
+ REGULATOR_EVENT_OVER_CURRENT,
+ NULL);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * BUCKV specifics
+ */
+
+static int wm831x_buckv_list_voltage(struct regulator_dev *rdev,
+ unsigned selector)
+{
+ if (selector <= 0x8)
+ return 600000;
+ if (selector <= WM831X_BUCKV_MAX_SELECTOR)
+ return 600000 + ((selector - 0x8) * 12500);
+ return -EINVAL;
+}
+
+static int wm831x_buckv_set_voltage_int(struct regulator_dev *rdev, int reg,
+ int min_uV, int max_uV)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ u16 vsel;
+
+ if (min_uV < 600000)
+ vsel = 0;
+ else if (min_uV <= 1800000)
+ vsel = ((min_uV - 600000) / 12500) + 8;
+ else
+ return -EINVAL;
+
+ if (wm831x_buckv_list_voltage(rdev, vsel) > max_uV)
+ return -EINVAL;
+
+ return wm831x_set_bits(wm831x, reg, WM831X_DC1_ON_VSEL_MASK, vsel);
+}
+
+static int wm831x_buckv_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG;
+
+ return wm831x_buckv_set_voltage_int(rdev, reg, min_uV, max_uV);
+}
+
+static int wm831x_buckv_set_suspend_voltage(struct regulator_dev *rdev,
+ int uV)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL;
+
+ return wm831x_buckv_set_voltage_int(rdev, reg, uV, uV);
+}
+
+static int wm831x_buckv_get_voltage(struct regulator_dev *rdev)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG;
+ int val;
+
+ val = wm831x_reg_read(wm831x, reg);
+ if (val < 0)
+ return val;
+
+ return wm831x_buckv_list_voltage(rdev, val & WM831X_DC1_ON_VSEL_MASK);
+}
+
+/* Current limit options */
+static u16 wm831x_dcdc_ilim[] = {
+ 125, 250, 375, 500, 625, 750, 875, 1000
+};
+
+static int wm831x_buckv_set_current_limit(struct regulator_dev *rdev,
+ int min_uA, int max_uA)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ u16 reg = dcdc->base + WM831X_DCDC_CONTROL_2;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(wm831x_dcdc_ilim); i++) {
+ if (max_uA <= wm831x_dcdc_ilim[i])
+ break;
+ }
+ if (i == ARRAY_SIZE(wm831x_dcdc_ilim))
+ return -EINVAL;
+
+ return wm831x_set_bits(wm831x, reg, WM831X_DC1_HC_THR_MASK, i);
+}
+
+static int wm831x_buckv_get_current_limit(struct regulator_dev *rdev)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ u16 reg = dcdc->base + WM831X_DCDC_CONTROL_2;
+ int val;
+
+ val = wm831x_reg_read(wm831x, reg);
+ if (val < 0)
+ return val;
+
+ return wm831x_dcdc_ilim[val & WM831X_DC1_HC_THR_MASK];
+}
+
+static struct regulator_ops wm831x_buckv_ops = {
+ .set_voltage = wm831x_buckv_set_voltage,
+ .get_voltage = wm831x_buckv_get_voltage,
+ .list_voltage = wm831x_buckv_list_voltage,
+ .set_suspend_voltage = wm831x_buckv_set_suspend_voltage,
+ .set_current_limit = wm831x_buckv_set_current_limit,
+ .get_current_limit = wm831x_buckv_get_current_limit,
+
+ .is_enabled = wm831x_dcdc_is_enabled,
+ .enable = wm831x_dcdc_enable,
+ .disable = wm831x_dcdc_disable,
+ .get_status = wm831x_dcdc_get_status,
+ .get_mode = wm831x_dcdc_get_mode,
+ .set_mode = wm831x_dcdc_set_mode,
+ .set_suspend_mode = wm831x_dcdc_set_suspend_mode,
+};
+
+static __devinit int wm831x_buckv_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *pdata = wm831x->dev->platform_data;
+ int id = pdev->id % ARRAY_SIZE(pdata->dcdc);
+ struct wm831x_dcdc *dcdc;
+ struct resource *res;
+ int ret, irq;
+
+ dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1);
+
+ if (pdata == NULL || pdata->dcdc[id] == NULL)
+ return -ENODEV;
+
+ dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL);
+ if (dcdc == NULL) {
+ dev_err(&pdev->dev, "Unable to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ dcdc->wm831x = wm831x;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "No I/O resource\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ dcdc->base = res->start;
+
+ snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1);
+ dcdc->desc.name = dcdc->name;
+ dcdc->desc.id = id;
+ dcdc->desc.type = REGULATOR_VOLTAGE;
+ dcdc->desc.n_voltages = WM831X_BUCKV_MAX_SELECTOR + 1;
+ dcdc->desc.ops = &wm831x_buckv_ops;
+ dcdc->desc.owner = THIS_MODULE;
+
+ dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev,
+ pdata->dcdc[id], dcdc);
+ if (IS_ERR(dcdc->regulator)) {
+ ret = PTR_ERR(dcdc->regulator);
+ dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n",
+ id + 1, ret);
+ goto err;
+ }
+
+ irq = platform_get_irq_byname(pdev, "UV");
+ ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_uv_irq,
+ IRQF_TRIGGER_RISING, dcdc->name,
+ dcdc);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n",
+ irq, ret);
+ goto err_regulator;
+ }
+
+ irq = platform_get_irq_byname(pdev, "HC");
+ ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_oc_irq,
+ IRQF_TRIGGER_RISING, dcdc->name,
+ dcdc);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request HC IRQ %d: %d\n",
+ irq, ret);
+ goto err_uv;
+ }
+
+ platform_set_drvdata(pdev, dcdc);
+
+ return 0;
+
+err_uv:
+ wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc);
+err_regulator:
+ regulator_unregister(dcdc->regulator);
+err:
+ kfree(dcdc);
+ return ret;
+}
+
+static __devexit int wm831x_buckv_remove(struct platform_device *pdev)
+{
+ struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+
+ wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "HC"), dcdc);
+ wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc);
+ regulator_unregister(dcdc->regulator);
+ kfree(dcdc);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_buckv_driver = {
+ .probe = wm831x_buckv_probe,
+ .remove = __devexit_p(wm831x_buckv_remove),
+ .driver = {
+ .name = "wm831x-buckv",
+ },
+};
+
+/*
+ * BUCKP specifics
+ */
+
+static int wm831x_buckp_list_voltage(struct regulator_dev *rdev,
+ unsigned selector)
+{
+ if (selector <= WM831X_BUCKP_MAX_SELECTOR)
+ return 850000 + (selector * 25000);
+ else
+ return -EINVAL;
+}
+
+static int wm831x_buckp_set_voltage_int(struct regulator_dev *rdev, int reg,
+ int min_uV, int max_uV)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ u16 vsel;
+
+ if (min_uV <= 34000000)
+ vsel = (min_uV - 850000) / 25000;
+ else
+ return -EINVAL;
+
+ if (wm831x_buckp_list_voltage(rdev, vsel) > max_uV)
+ return -EINVAL;
+
+ return wm831x_set_bits(wm831x, reg, WM831X_DC3_ON_VSEL_MASK, vsel);
+}
+
+static int wm831x_buckp_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG;
+
+ return wm831x_buckp_set_voltage_int(rdev, reg, min_uV, max_uV);
+}
+
+static int wm831x_buckp_set_suspend_voltage(struct regulator_dev *rdev,
+ int uV)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ u16 reg = dcdc->base + WM831X_DCDC_SLEEP_CONTROL;
+
+ return wm831x_buckp_set_voltage_int(rdev, reg, uV, uV);
+}
+
+static int wm831x_buckp_get_voltage(struct regulator_dev *rdev)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ u16 reg = dcdc->base + WM831X_DCDC_ON_CONFIG;
+ int val;
+
+ val = wm831x_reg_read(wm831x, reg);
+ if (val < 0)
+ return val;
+
+ return wm831x_buckp_list_voltage(rdev, val & WM831X_DC3_ON_VSEL_MASK);
+}
+
+static struct regulator_ops wm831x_buckp_ops = {
+ .set_voltage = wm831x_buckp_set_voltage,
+ .get_voltage = wm831x_buckp_get_voltage,
+ .list_voltage = wm831x_buckp_list_voltage,
+ .set_suspend_voltage = wm831x_buckp_set_suspend_voltage,
+
+ .is_enabled = wm831x_dcdc_is_enabled,
+ .enable = wm831x_dcdc_enable,
+ .disable = wm831x_dcdc_disable,
+ .get_status = wm831x_dcdc_get_status,
+ .get_mode = wm831x_dcdc_get_mode,
+ .set_mode = wm831x_dcdc_set_mode,
+ .set_suspend_mode = wm831x_dcdc_set_suspend_mode,
+};
+
+static __devinit int wm831x_buckp_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *pdata = wm831x->dev->platform_data;
+ int id = pdev->id % ARRAY_SIZE(pdata->dcdc);
+ struct wm831x_dcdc *dcdc;
+ struct resource *res;
+ int ret, irq;
+
+ dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1);
+
+ if (pdata == NULL || pdata->dcdc[id] == NULL)
+ return -ENODEV;
+
+ dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL);
+ if (dcdc == NULL) {
+ dev_err(&pdev->dev, "Unable to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ dcdc->wm831x = wm831x;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "No I/O resource\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ dcdc->base = res->start;
+
+ snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1);
+ dcdc->desc.name = dcdc->name;
+ dcdc->desc.id = id;
+ dcdc->desc.type = REGULATOR_VOLTAGE;
+ dcdc->desc.n_voltages = WM831X_BUCKP_MAX_SELECTOR + 1;
+ dcdc->desc.ops = &wm831x_buckp_ops;
+ dcdc->desc.owner = THIS_MODULE;
+
+ dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev,
+ pdata->dcdc[id], dcdc);
+ if (IS_ERR(dcdc->regulator)) {
+ ret = PTR_ERR(dcdc->regulator);
+ dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n",
+ id + 1, ret);
+ goto err;
+ }
+
+ irq = platform_get_irq_byname(pdev, "UV");
+ ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_uv_irq,
+ IRQF_TRIGGER_RISING, dcdc->name,
+ dcdc);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n",
+ irq, ret);
+ goto err_regulator;
+ }
+
+ platform_set_drvdata(pdev, dcdc);
+
+ return 0;
+
+err_regulator:
+ regulator_unregister(dcdc->regulator);
+err:
+ kfree(dcdc);
+ return ret;
+}
+
+static __devexit int wm831x_buckp_remove(struct platform_device *pdev)
+{
+ struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+
+ wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc);
+ regulator_unregister(dcdc->regulator);
+ kfree(dcdc);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_buckp_driver = {
+ .probe = wm831x_buckp_probe,
+ .remove = __devexit_p(wm831x_buckp_remove),
+ .driver = {
+ .name = "wm831x-buckp",
+ },
+};
+
+/*
+ * DCDC boost convertors
+ */
+
+static int wm831x_boostp_get_status(struct regulator_dev *rdev)
+{
+ struct wm831x_dcdc *dcdc = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+ int ret;
+
+ /* First, check for errors */
+ ret = wm831x_reg_read(wm831x, WM831X_DCDC_UV_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & (1 << rdev_get_id(rdev))) {
+ dev_dbg(wm831x->dev, "DCDC%d under voltage\n",
+ rdev_get_id(rdev) + 1);
+ return REGULATOR_STATUS_ERROR;
+ }
+
+ /* Is the regulator on? */
+ ret = wm831x_reg_read(wm831x, WM831X_DCDC_STATUS);
+ if (ret < 0)
+ return ret;
+ if (ret & (1 << rdev_get_id(rdev)))
+ return REGULATOR_STATUS_ON;
+ else
+ return REGULATOR_STATUS_OFF;
+}
+
+static struct regulator_ops wm831x_boostp_ops = {
+ .get_status = wm831x_boostp_get_status,
+
+ .is_enabled = wm831x_dcdc_is_enabled,
+ .enable = wm831x_dcdc_enable,
+ .disable = wm831x_dcdc_disable,
+};
+
+static __devinit int wm831x_boostp_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *pdata = wm831x->dev->platform_data;
+ int id = pdev->id % ARRAY_SIZE(pdata->dcdc);
+ struct wm831x_dcdc *dcdc;
+ struct resource *res;
+ int ret, irq;
+
+ dev_dbg(&pdev->dev, "Probing DCDC%d\n", id + 1);
+
+ if (pdata == NULL || pdata->dcdc[id] == NULL)
+ return -ENODEV;
+
+ dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL);
+ if (dcdc == NULL) {
+ dev_err(&pdev->dev, "Unable to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ dcdc->wm831x = wm831x;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "No I/O resource\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ dcdc->base = res->start;
+
+ snprintf(dcdc->name, sizeof(dcdc->name), "DCDC%d", id + 1);
+ dcdc->desc.name = dcdc->name;
+ dcdc->desc.id = id;
+ dcdc->desc.type = REGULATOR_VOLTAGE;
+ dcdc->desc.ops = &wm831x_boostp_ops;
+ dcdc->desc.owner = THIS_MODULE;
+
+ dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev,
+ pdata->dcdc[id], dcdc);
+ if (IS_ERR(dcdc->regulator)) {
+ ret = PTR_ERR(dcdc->regulator);
+ dev_err(wm831x->dev, "Failed to register DCDC%d: %d\n",
+ id + 1, ret);
+ goto err;
+ }
+
+ irq = platform_get_irq_byname(pdev, "UV");
+ ret = wm831x_request_irq(wm831x, irq, wm831x_dcdc_uv_irq,
+ IRQF_TRIGGER_RISING, dcdc->name,
+ dcdc);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n",
+ irq, ret);
+ goto err_regulator;
+ }
+
+ platform_set_drvdata(pdev, dcdc);
+
+ return 0;
+
+err_regulator:
+ regulator_unregister(dcdc->regulator);
+err:
+ kfree(dcdc);
+ return ret;
+}
+
+static __devexit int wm831x_boostp_remove(struct platform_device *pdev)
+{
+ struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev);
+ struct wm831x *wm831x = dcdc->wm831x;
+
+ wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), dcdc);
+ regulator_unregister(dcdc->regulator);
+ kfree(dcdc);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_boostp_driver = {
+ .probe = wm831x_boostp_probe,
+ .remove = __devexit_p(wm831x_boostp_remove),
+ .driver = {
+ .name = "wm831x-boostp",
+ },
+};
+
+/*
+ * External Power Enable
+ *
+ * These aren't actually DCDCs but look like them in hardware so share
+ * code.
+ */
+
+#define WM831X_EPE_BASE 6
+
+static struct regulator_ops wm831x_epe_ops = {
+ .is_enabled = wm831x_dcdc_is_enabled,
+ .enable = wm831x_dcdc_enable,
+ .disable = wm831x_dcdc_disable,
+ .get_status = wm831x_dcdc_get_status,
+};
+
+static __devinit int wm831x_epe_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *pdata = wm831x->dev->platform_data;
+ int id = pdev->id % ARRAY_SIZE(pdata->epe);
+ struct wm831x_dcdc *dcdc;
+ int ret;
+
+ dev_dbg(&pdev->dev, "Probing EPE%d\n", id + 1);
+
+ if (pdata == NULL || pdata->epe[id] == NULL)
+ return -ENODEV;
+
+ dcdc = kzalloc(sizeof(struct wm831x_dcdc), GFP_KERNEL);
+ if (dcdc == NULL) {
+ dev_err(&pdev->dev, "Unable to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ dcdc->wm831x = wm831x;
+
+ /* For current parts this is correct; probably need to revisit
+ * in future.
+ */
+ snprintf(dcdc->name, sizeof(dcdc->name), "EPE%d", id + 1);
+ dcdc->desc.name = dcdc->name;
+ dcdc->desc.id = id + WM831X_EPE_BASE; /* Offset in DCDC registers */
+ dcdc->desc.ops = &wm831x_epe_ops;
+ dcdc->desc.type = REGULATOR_VOLTAGE;
+ dcdc->desc.owner = THIS_MODULE;
+
+ dcdc->regulator = regulator_register(&dcdc->desc, &pdev->dev,
+ pdata->epe[id], dcdc);
+ if (IS_ERR(dcdc->regulator)) {
+ ret = PTR_ERR(dcdc->regulator);
+ dev_err(wm831x->dev, "Failed to register EPE%d: %d\n",
+ id + 1, ret);
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, dcdc);
+
+ return 0;
+
+err:
+ kfree(dcdc);
+ return ret;
+}
+
+static __devexit int wm831x_epe_remove(struct platform_device *pdev)
+{
+ struct wm831x_dcdc *dcdc = platform_get_drvdata(pdev);
+
+ regulator_unregister(dcdc->regulator);
+ kfree(dcdc);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_epe_driver = {
+ .probe = wm831x_epe_probe,
+ .remove = __devexit_p(wm831x_epe_remove),
+ .driver = {
+ .name = "wm831x-epe",
+ },
+};
+
+static int __init wm831x_dcdc_init(void)
+{
+ int ret;
+ ret = platform_driver_register(&wm831x_buckv_driver);
+ if (ret != 0)
+ pr_err("Failed to register WM831x BUCKV driver: %d\n", ret);
+
+ ret = platform_driver_register(&wm831x_buckp_driver);
+ if (ret != 0)
+ pr_err("Failed to register WM831x BUCKP driver: %d\n", ret);
+
+ ret = platform_driver_register(&wm831x_boostp_driver);
+ if (ret != 0)
+ pr_err("Failed to register WM831x BOOST driver: %d\n", ret);
+
+ ret = platform_driver_register(&wm831x_epe_driver);
+ if (ret != 0)
+ pr_err("Failed to register WM831x EPE driver: %d\n", ret);
+
+ return 0;
+}
+subsys_initcall(wm831x_dcdc_init);
+
+static void __exit wm831x_dcdc_exit(void)
+{
+ platform_driver_unregister(&wm831x_epe_driver);
+ platform_driver_unregister(&wm831x_boostp_driver);
+ platform_driver_unregister(&wm831x_buckp_driver);
+ platform_driver_unregister(&wm831x_buckv_driver);
+}
+module_exit(wm831x_dcdc_exit);
+
+/* Module information */
+MODULE_AUTHOR("Mark Brown");
+MODULE_DESCRIPTION("WM831x DC-DC convertor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-buckv");
+MODULE_ALIAS("platform:wm831x-buckp");
diff --git a/drivers/regulator/wm831x-isink.c b/drivers/regulator/wm831x-isink.c
new file mode 100644
index 00000000000..1d8d9879d3a
--- /dev/null
+++ b/drivers/regulator/wm831x-isink.c
@@ -0,0 +1,260 @@
+/*
+ * wm831x-isink.c -- Current sink driver for the WM831x series
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program 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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/regulator.h>
+#include <linux/mfd/wm831x/pdata.h>
+
+#define WM831X_ISINK_MAX_NAME 7
+
+struct wm831x_isink {
+ char name[WM831X_ISINK_MAX_NAME];
+ struct regulator_desc desc;
+ int reg;
+ struct wm831x *wm831x;
+ struct regulator_dev *regulator;
+};
+
+static int wm831x_isink_enable(struct regulator_dev *rdev)
+{
+ struct wm831x_isink *isink = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = isink->wm831x;
+ int ret;
+
+ /* We have a two stage enable: first start the ISINK... */
+ ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA,
+ WM831X_CS1_ENA);
+ if (ret != 0)
+ return ret;
+
+ /* ...then enable drive */
+ ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_DRIVE,
+ WM831X_CS1_DRIVE);
+ if (ret != 0)
+ wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, 0);
+
+ return ret;
+
+}
+
+static int wm831x_isink_disable(struct regulator_dev *rdev)
+{
+ struct wm831x_isink *isink = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = isink->wm831x;
+ int ret;
+
+ ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_DRIVE, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = wm831x_set_bits(wm831x, isink->reg, WM831X_CS1_ENA, 0);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+
+}
+
+static int wm831x_isink_is_enabled(struct regulator_dev *rdev)
+{
+ struct wm831x_isink *isink = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = isink->wm831x;
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, isink->reg);
+ if (ret < 0)
+ return ret;
+
+ if ((ret & (WM831X_CS1_ENA | WM831X_CS1_DRIVE)) ==
+ (WM831X_CS1_ENA | WM831X_CS1_DRIVE))
+ return 1;
+ else
+ return 0;
+}
+
+static int wm831x_isink_set_current(struct regulator_dev *rdev,
+ int min_uA, int max_uA)
+{
+ struct wm831x_isink *isink = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = isink->wm831x;
+ int ret, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm831x_isinkv_values); i++) {
+ int val = wm831x_isinkv_values[i];
+ if (min_uA >= val && val <= max_uA) {
+ ret = wm831x_set_bits(wm831x, isink->reg,
+ WM831X_CS1_ISEL_MASK, i);
+ return ret;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int wm831x_isink_get_current(struct regulator_dev *rdev)
+{
+ struct wm831x_isink *isink = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = isink->wm831x;
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, isink->reg);
+ if (ret < 0)
+ return ret;
+
+ ret &= WM831X_CS1_ISEL_MASK;
+ if (ret > WM831X_ISINK_MAX_ISEL)
+ ret = WM831X_ISINK_MAX_ISEL;
+
+ return wm831x_isinkv_values[ret];
+}
+
+static struct regulator_ops wm831x_isink_ops = {
+ .is_enabled = wm831x_isink_is_enabled,
+ .enable = wm831x_isink_enable,
+ .disable = wm831x_isink_disable,
+ .set_current_limit = wm831x_isink_set_current,
+ .get_current_limit = wm831x_isink_get_current,
+};
+
+static irqreturn_t wm831x_isink_irq(int irq, void *data)
+{
+ struct wm831x_isink *isink = data;
+
+ regulator_notifier_call_chain(isink->regulator,
+ REGULATOR_EVENT_OVER_CURRENT,
+ NULL);
+
+ return IRQ_HANDLED;
+}
+
+
+static __devinit int wm831x_isink_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *pdata = wm831x->dev->platform_data;
+ struct wm831x_isink *isink;
+ int id = pdev->id % ARRAY_SIZE(pdata->isink);
+ struct resource *res;
+ int ret, irq;
+
+ dev_dbg(&pdev->dev, "Probing ISINK%d\n", id + 1);
+
+ if (pdata == NULL || pdata->isink[id] == NULL)
+ return -ENODEV;
+
+ isink = kzalloc(sizeof(struct wm831x_isink), GFP_KERNEL);
+ if (isink == NULL) {
+ dev_err(&pdev->dev, "Unable to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "No I/O resource\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ isink->reg = res->start;
+
+ /* For current parts this is correct; probably need to revisit
+ * in future.
+ */
+ snprintf(isink->name, sizeof(isink->name), "ISINK%d", id + 1);
+ isink->desc.name = isink->name;
+ isink->desc.id = id;
+ isink->desc.ops = &wm831x_isink_ops;
+ isink->desc.type = REGULATOR_CURRENT;
+ isink->desc.owner = THIS_MODULE;
+
+ isink->regulator = regulator_register(&isink->desc, &pdev->dev,
+ pdata->isink[id], isink);
+ if (IS_ERR(isink->regulator)) {
+ ret = PTR_ERR(isink->regulator);
+ dev_err(wm831x->dev, "Failed to register ISINK%d: %d\n",
+ id + 1, ret);
+ goto err;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ ret = wm831x_request_irq(wm831x, irq, wm831x_isink_irq,
+ IRQF_TRIGGER_RISING, isink->name,
+ isink);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request ISINK IRQ %d: %d\n",
+ irq, ret);
+ goto err_regulator;
+ }
+
+ platform_set_drvdata(pdev, isink);
+
+ return 0;
+
+err_regulator:
+ regulator_unregister(isink->regulator);
+err:
+ kfree(isink);
+ return ret;
+}
+
+static __devexit int wm831x_isink_remove(struct platform_device *pdev)
+{
+ struct wm831x_isink *isink = platform_get_drvdata(pdev);
+ struct wm831x *wm831x = isink->wm831x;
+
+ wm831x_free_irq(wm831x, platform_get_irq(pdev, 0), isink);
+
+ regulator_unregister(isink->regulator);
+ kfree(isink);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_isink_driver = {
+ .probe = wm831x_isink_probe,
+ .remove = __devexit_p(wm831x_isink_remove),
+ .driver = {
+ .name = "wm831x-isink",
+ },
+};
+
+static int __init wm831x_isink_init(void)
+{
+ int ret;
+ ret = platform_driver_register(&wm831x_isink_driver);
+ if (ret != 0)
+ pr_err("Failed to register WM831x ISINK driver: %d\n", ret);
+
+ return ret;
+}
+subsys_initcall(wm831x_isink_init);
+
+static void __exit wm831x_isink_exit(void)
+{
+ platform_driver_unregister(&wm831x_isink_driver);
+}
+module_exit(wm831x_isink_exit);
+
+/* Module information */
+MODULE_AUTHOR("Mark Brown");
+MODULE_DESCRIPTION("WM831x current sink driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-isink");
diff --git a/drivers/regulator/wm831x-ldo.c b/drivers/regulator/wm831x-ldo.c
new file mode 100644
index 00000000000..bb61aede480
--- /dev/null
+++ b/drivers/regulator/wm831x-ldo.c
@@ -0,0 +1,852 @@
+/*
+ * wm831x-ldo.c -- LDO driver for the WM831x series
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program 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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/driver.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/regulator.h>
+#include <linux/mfd/wm831x/pdata.h>
+
+#define WM831X_LDO_MAX_NAME 6
+
+#define WM831X_LDO_CONTROL 0
+#define WM831X_LDO_ON_CONTROL 1
+#define WM831X_LDO_SLEEP_CONTROL 2
+
+#define WM831X_ALIVE_LDO_ON_CONTROL 0
+#define WM831X_ALIVE_LDO_SLEEP_CONTROL 1
+
+struct wm831x_ldo {
+ char name[WM831X_LDO_MAX_NAME];
+ struct regulator_desc desc;
+ int base;
+ struct wm831x *wm831x;
+ struct regulator_dev *regulator;
+};
+
+/*
+ * Shared
+ */
+
+static int wm831x_ldo_is_enabled(struct regulator_dev *rdev)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int mask = 1 << rdev_get_id(rdev);
+ int reg;
+
+ reg = wm831x_reg_read(wm831x, WM831X_LDO_ENABLE);
+ if (reg < 0)
+ return reg;
+
+ if (reg & mask)
+ return 1;
+ else
+ return 0;
+}
+
+static int wm831x_ldo_enable(struct regulator_dev *rdev)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int mask = 1 << rdev_get_id(rdev);
+
+ return wm831x_set_bits(wm831x, WM831X_LDO_ENABLE, mask, mask);
+}
+
+static int wm831x_ldo_disable(struct regulator_dev *rdev)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int mask = 1 << rdev_get_id(rdev);
+
+ return wm831x_set_bits(wm831x, WM831X_LDO_ENABLE, mask, 0);
+}
+
+static irqreturn_t wm831x_ldo_uv_irq(int irq, void *data)
+{
+ struct wm831x_ldo *ldo = data;
+
+ regulator_notifier_call_chain(ldo->regulator,
+ REGULATOR_EVENT_UNDER_VOLTAGE,
+ NULL);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * General purpose LDOs
+ */
+
+#define WM831X_GP_LDO_SELECTOR_LOW 0xe
+#define WM831X_GP_LDO_MAX_SELECTOR 0x1f
+
+static int wm831x_gp_ldo_list_voltage(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ /* 0.9-1.6V in 50mV steps */
+ if (selector <= WM831X_GP_LDO_SELECTOR_LOW)
+ return 900000 + (selector * 50000);
+ /* 1.7-3.3V in 50mV steps */
+ if (selector <= WM831X_GP_LDO_MAX_SELECTOR)
+ return 1600000 + ((selector - WM831X_GP_LDO_SELECTOR_LOW)
+ * 100000);
+ return -EINVAL;
+}
+
+static int wm831x_gp_ldo_set_voltage_int(struct regulator_dev *rdev, int reg,
+ int min_uV, int max_uV)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int vsel, ret;
+
+ if (min_uV < 900000)
+ vsel = 0;
+ else if (min_uV < 1700000)
+ vsel = ((min_uV - 900000) / 50000);
+ else
+ vsel = ((min_uV - 1700000) / 100000)
+ + WM831X_GP_LDO_SELECTOR_LOW + 1;
+
+ ret = wm831x_gp_ldo_list_voltage(rdev, vsel);
+ if (ret < 0)
+ return ret;
+ if (ret < min_uV || ret > max_uV)
+ return -EINVAL;
+
+ return wm831x_set_bits(wm831x, reg, WM831X_LDO1_ON_VSEL_MASK, vsel);
+}
+
+static int wm831x_gp_ldo_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ int reg = ldo->base + WM831X_LDO_ON_CONTROL;
+
+ return wm831x_gp_ldo_set_voltage_int(rdev, reg, min_uV, max_uV);
+}
+
+static int wm831x_gp_ldo_set_suspend_voltage(struct regulator_dev *rdev,
+ int uV)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ int reg = ldo->base + WM831X_LDO_SLEEP_CONTROL;
+
+ return wm831x_gp_ldo_set_voltage_int(rdev, reg, uV, uV);
+}
+
+static int wm831x_gp_ldo_get_voltage(struct regulator_dev *rdev)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int reg = ldo->base + WM831X_LDO_ON_CONTROL;
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, reg);
+ if (ret < 0)
+ return ret;
+
+ ret &= WM831X_LDO1_ON_VSEL_MASK;
+
+ return wm831x_gp_ldo_list_voltage(rdev, ret);
+}
+
+static unsigned int wm831x_gp_ldo_get_mode(struct regulator_dev *rdev)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int ctrl_reg = ldo->base + WM831X_LDO_CONTROL;
+ int on_reg = ldo->base + WM831X_LDO_ON_CONTROL;
+ unsigned int ret;
+
+ ret = wm831x_reg_read(wm831x, on_reg);
+ if (ret < 0)
+ return 0;
+
+ if (!(ret & WM831X_LDO1_ON_MODE))
+ return REGULATOR_MODE_NORMAL;
+
+ ret = wm831x_reg_read(wm831x, ctrl_reg);
+ if (ret < 0)
+ return 0;
+
+ if (ret & WM831X_LDO1_LP_MODE)
+ return REGULATOR_MODE_STANDBY;
+ else
+ return REGULATOR_MODE_IDLE;
+}
+
+static int wm831x_gp_ldo_set_mode(struct regulator_dev *rdev,
+ unsigned int mode)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int ctrl_reg = ldo->base + WM831X_LDO_CONTROL;
+ int on_reg = ldo->base + WM831X_LDO_ON_CONTROL;
+ int ret;
+
+
+ switch (mode) {
+ case REGULATOR_MODE_NORMAL:
+ ret = wm831x_set_bits(wm831x, on_reg,
+ WM831X_LDO1_ON_MODE, 0);
+ if (ret < 0)
+ return ret;
+ break;
+
+ case REGULATOR_MODE_IDLE:
+ ret = wm831x_set_bits(wm831x, ctrl_reg,
+ WM831X_LDO1_LP_MODE,
+ WM831X_LDO1_LP_MODE);
+ if (ret < 0)
+ return ret;
+
+ ret = wm831x_set_bits(wm831x, on_reg,
+ WM831X_LDO1_ON_MODE,
+ WM831X_LDO1_ON_MODE);
+ if (ret < 0)
+ return ret;
+
+ case REGULATOR_MODE_STANDBY:
+ ret = wm831x_set_bits(wm831x, ctrl_reg,
+ WM831X_LDO1_LP_MODE, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = wm831x_set_bits(wm831x, on_reg,
+ WM831X_LDO1_ON_MODE,
+ WM831X_LDO1_ON_MODE);
+ if (ret < 0)
+ return ret;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm831x_gp_ldo_get_status(struct regulator_dev *rdev)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int mask = 1 << rdev_get_id(rdev);
+ int ret;
+
+ /* Is the regulator on? */
+ ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS);
+ if (ret < 0)
+ return ret;
+ if (!(ret & mask))
+ return REGULATOR_STATUS_OFF;
+
+ /* Is it reporting under voltage? */
+ ret = wm831x_reg_read(wm831x, WM831X_LDO_UV_STATUS);
+ if (ret & mask)
+ return REGULATOR_STATUS_ERROR;
+
+ ret = wm831x_gp_ldo_get_mode(rdev);
+ if (ret < 0)
+ return ret;
+ else
+ return regulator_mode_to_status(ret);
+}
+
+static unsigned int wm831x_gp_ldo_get_optimum_mode(struct regulator_dev *rdev,
+ int input_uV,
+ int output_uV, int load_uA)
+{
+ if (load_uA < 20000)
+ return REGULATOR_MODE_STANDBY;
+ if (load_uA < 50000)
+ return REGULATOR_MODE_IDLE;
+ return REGULATOR_MODE_NORMAL;
+}
+
+
+static struct regulator_ops wm831x_gp_ldo_ops = {
+ .list_voltage = wm831x_gp_ldo_list_voltage,
+ .get_voltage = wm831x_gp_ldo_get_voltage,
+ .set_voltage = wm831x_gp_ldo_set_voltage,
+ .set_suspend_voltage = wm831x_gp_ldo_set_suspend_voltage,
+ .get_mode = wm831x_gp_ldo_get_mode,
+ .set_mode = wm831x_gp_ldo_set_mode,
+ .get_status = wm831x_gp_ldo_get_status,
+ .get_optimum_mode = wm831x_gp_ldo_get_optimum_mode,
+
+ .is_enabled = wm831x_ldo_is_enabled,
+ .enable = wm831x_ldo_enable,
+ .disable = wm831x_ldo_disable,
+};
+
+static __devinit int wm831x_gp_ldo_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *pdata = wm831x->dev->platform_data;
+ int id = pdev->id % ARRAY_SIZE(pdata->ldo);
+ struct wm831x_ldo *ldo;
+ struct resource *res;
+ int ret, irq;
+
+ dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1);
+
+ if (pdata == NULL || pdata->ldo[id] == NULL)
+ return -ENODEV;
+
+ ldo = kzalloc(sizeof(struct wm831x_ldo), GFP_KERNEL);
+ if (ldo == NULL) {
+ dev_err(&pdev->dev, "Unable to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ ldo->wm831x = wm831x;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "No I/O resource\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ ldo->base = res->start;
+
+ snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1);
+ ldo->desc.name = ldo->name;
+ ldo->desc.id = id;
+ ldo->desc.type = REGULATOR_VOLTAGE;
+ ldo->desc.n_voltages = WM831X_GP_LDO_MAX_SELECTOR + 1;
+ ldo->desc.ops = &wm831x_gp_ldo_ops;
+ ldo->desc.owner = THIS_MODULE;
+
+ ldo->regulator = regulator_register(&ldo->desc, &pdev->dev,
+ pdata->ldo[id], ldo);
+ if (IS_ERR(ldo->regulator)) {
+ ret = PTR_ERR(ldo->regulator);
+ dev_err(wm831x->dev, "Failed to register LDO%d: %d\n",
+ id + 1, ret);
+ goto err;
+ }
+
+ irq = platform_get_irq_byname(pdev, "UV");
+ ret = wm831x_request_irq(wm831x, irq, wm831x_ldo_uv_irq,
+ IRQF_TRIGGER_RISING, ldo->name,
+ ldo);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n",
+ irq, ret);
+ goto err_regulator;
+ }
+
+ platform_set_drvdata(pdev, ldo);
+
+ return 0;
+
+err_regulator:
+ regulator_unregister(ldo->regulator);
+err:
+ kfree(ldo);
+ return ret;
+}
+
+static __devexit int wm831x_gp_ldo_remove(struct platform_device *pdev)
+{
+ struct wm831x_ldo *ldo = platform_get_drvdata(pdev);
+ struct wm831x *wm831x = ldo->wm831x;
+
+ wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), ldo);
+ regulator_unregister(ldo->regulator);
+ kfree(ldo);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_gp_ldo_driver = {
+ .probe = wm831x_gp_ldo_probe,
+ .remove = __devexit_p(wm831x_gp_ldo_remove),
+ .driver = {
+ .name = "wm831x-ldo",
+ },
+};
+
+/*
+ * Analogue LDOs
+ */
+
+
+#define WM831X_ALDO_SELECTOR_LOW 0xc
+#define WM831X_ALDO_MAX_SELECTOR 0x1f
+
+static int wm831x_aldo_list_voltage(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ /* 1-1.6V in 50mV steps */
+ if (selector <= WM831X_ALDO_SELECTOR_LOW)
+ return 1000000 + (selector * 50000);
+ /* 1.7-3.5V in 50mV steps */
+ if (selector <= WM831X_ALDO_MAX_SELECTOR)
+ return 1600000 + ((selector - WM831X_ALDO_SELECTOR_LOW)
+ * 100000);
+ return -EINVAL;
+}
+
+static int wm831x_aldo_set_voltage_int(struct regulator_dev *rdev, int reg,
+ int min_uV, int max_uV)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int vsel, ret;
+
+ if (min_uV < 1000000)
+ vsel = 0;
+ else if (min_uV < 1700000)
+ vsel = ((min_uV - 1000000) / 50000);
+ else
+ vsel = ((min_uV - 1700000) / 100000)
+ + WM831X_ALDO_SELECTOR_LOW + 1;
+
+ ret = wm831x_aldo_list_voltage(rdev, vsel);
+ if (ret < 0)
+ return ret;
+ if (ret < min_uV || ret > max_uV)
+ return -EINVAL;
+
+ return wm831x_set_bits(wm831x, reg, WM831X_LDO7_ON_VSEL_MASK, vsel);
+}
+
+static int wm831x_aldo_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ int reg = ldo->base + WM831X_LDO_ON_CONTROL;
+
+ return wm831x_aldo_set_voltage_int(rdev, reg, min_uV, max_uV);
+}
+
+static int wm831x_aldo_set_suspend_voltage(struct regulator_dev *rdev,
+ int uV)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ int reg = ldo->base + WM831X_LDO_SLEEP_CONTROL;
+
+ return wm831x_aldo_set_voltage_int(rdev, reg, uV, uV);
+}
+
+static int wm831x_aldo_get_voltage(struct regulator_dev *rdev)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int reg = ldo->base + WM831X_LDO_ON_CONTROL;
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, reg);
+ if (ret < 0)
+ return ret;
+
+ ret &= WM831X_LDO7_ON_VSEL_MASK;
+
+ return wm831x_aldo_list_voltage(rdev, ret);
+}
+
+static unsigned int wm831x_aldo_get_mode(struct regulator_dev *rdev)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int on_reg = ldo->base + WM831X_LDO_ON_CONTROL;
+ unsigned int ret;
+
+ ret = wm831x_reg_read(wm831x, on_reg);
+ if (ret < 0)
+ return 0;
+
+ if (ret & WM831X_LDO7_ON_MODE)
+ return REGULATOR_MODE_IDLE;
+ else
+ return REGULATOR_MODE_NORMAL;
+}
+
+static int wm831x_aldo_set_mode(struct regulator_dev *rdev,
+ unsigned int mode)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int ctrl_reg = ldo->base + WM831X_LDO_CONTROL;
+ int on_reg = ldo->base + WM831X_LDO_ON_CONTROL;
+ int ret;
+
+
+ switch (mode) {
+ case REGULATOR_MODE_NORMAL:
+ ret = wm831x_set_bits(wm831x, on_reg,
+ WM831X_LDO7_ON_MODE, 0);
+ if (ret < 0)
+ return ret;
+ break;
+
+ case REGULATOR_MODE_IDLE:
+ ret = wm831x_set_bits(wm831x, ctrl_reg,
+ WM831X_LDO7_ON_MODE,
+ WM831X_LDO7_ON_MODE);
+ if (ret < 0)
+ return ret;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm831x_aldo_get_status(struct regulator_dev *rdev)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int mask = 1 << rdev_get_id(rdev);
+ int ret;
+
+ /* Is the regulator on? */
+ ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS);
+ if (ret < 0)
+ return ret;
+ if (!(ret & mask))
+ return REGULATOR_STATUS_OFF;
+
+ /* Is it reporting under voltage? */
+ ret = wm831x_reg_read(wm831x, WM831X_LDO_UV_STATUS);
+ if (ret & mask)
+ return REGULATOR_STATUS_ERROR;
+
+ ret = wm831x_aldo_get_mode(rdev);
+ if (ret < 0)
+ return ret;
+ else
+ return regulator_mode_to_status(ret);
+}
+
+static struct regulator_ops wm831x_aldo_ops = {
+ .list_voltage = wm831x_aldo_list_voltage,
+ .get_voltage = wm831x_aldo_get_voltage,
+ .set_voltage = wm831x_aldo_set_voltage,
+ .set_suspend_voltage = wm831x_aldo_set_suspend_voltage,
+ .get_mode = wm831x_aldo_get_mode,
+ .set_mode = wm831x_aldo_set_mode,
+ .get_status = wm831x_aldo_get_status,
+
+ .is_enabled = wm831x_ldo_is_enabled,
+ .enable = wm831x_ldo_enable,
+ .disable = wm831x_ldo_disable,
+};
+
+static __devinit int wm831x_aldo_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *pdata = wm831x->dev->platform_data;
+ int id = pdev->id % ARRAY_SIZE(pdata->ldo);
+ struct wm831x_ldo *ldo;
+ struct resource *res;
+ int ret, irq;
+
+ dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1);
+
+ if (pdata == NULL || pdata->ldo[id] == NULL)
+ return -ENODEV;
+
+ ldo = kzalloc(sizeof(struct wm831x_ldo), GFP_KERNEL);
+ if (ldo == NULL) {
+ dev_err(&pdev->dev, "Unable to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ ldo->wm831x = wm831x;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "No I/O resource\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ ldo->base = res->start;
+
+ snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1);
+ ldo->desc.name = ldo->name;
+ ldo->desc.id = id;
+ ldo->desc.type = REGULATOR_VOLTAGE;
+ ldo->desc.n_voltages = WM831X_ALDO_MAX_SELECTOR + 1;
+ ldo->desc.ops = &wm831x_aldo_ops;
+ ldo->desc.owner = THIS_MODULE;
+
+ ldo->regulator = regulator_register(&ldo->desc, &pdev->dev,
+ pdata->ldo[id], ldo);
+ if (IS_ERR(ldo->regulator)) {
+ ret = PTR_ERR(ldo->regulator);
+ dev_err(wm831x->dev, "Failed to register LDO%d: %d\n",
+ id + 1, ret);
+ goto err;
+ }
+
+ irq = platform_get_irq_byname(pdev, "UV");
+ ret = wm831x_request_irq(wm831x, irq, wm831x_ldo_uv_irq,
+ IRQF_TRIGGER_RISING, ldo->name,
+ ldo);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request UV IRQ %d: %d\n",
+ irq, ret);
+ goto err_regulator;
+ }
+
+ platform_set_drvdata(pdev, ldo);
+
+ return 0;
+
+err_regulator:
+ regulator_unregister(ldo->regulator);
+err:
+ kfree(ldo);
+ return ret;
+}
+
+static __devexit int wm831x_aldo_remove(struct platform_device *pdev)
+{
+ struct wm831x_ldo *ldo = platform_get_drvdata(pdev);
+ struct wm831x *wm831x = ldo->wm831x;
+
+ wm831x_free_irq(wm831x, platform_get_irq_byname(pdev, "UV"), ldo);
+ regulator_unregister(ldo->regulator);
+ kfree(ldo);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_aldo_driver = {
+ .probe = wm831x_aldo_probe,
+ .remove = __devexit_p(wm831x_aldo_remove),
+ .driver = {
+ .name = "wm831x-aldo",
+ },
+};
+
+/*
+ * Alive LDO
+ */
+
+#define WM831X_ALIVE_LDO_MAX_SELECTOR 0xf
+
+static int wm831x_alive_ldo_list_voltage(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ /* 0.8-1.55V in 50mV steps */
+ if (selector <= WM831X_ALIVE_LDO_MAX_SELECTOR)
+ return 800000 + (selector * 50000);
+ return -EINVAL;
+}
+
+static int wm831x_alive_ldo_set_voltage_int(struct regulator_dev *rdev,
+ int reg,
+ int min_uV, int max_uV)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int vsel, ret;
+
+ vsel = (min_uV - 800000) / 50000;
+
+ ret = wm831x_alive_ldo_list_voltage(rdev, vsel);
+ if (ret < 0)
+ return ret;
+ if (ret < min_uV || ret > max_uV)
+ return -EINVAL;
+
+ return wm831x_set_bits(wm831x, reg, WM831X_LDO11_ON_VSEL_MASK, vsel);
+}
+
+static int wm831x_alive_ldo_set_voltage(struct regulator_dev *rdev,
+ int min_uV, int max_uV)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ int reg = ldo->base + WM831X_ALIVE_LDO_ON_CONTROL;
+
+ return wm831x_alive_ldo_set_voltage_int(rdev, reg, min_uV, max_uV);
+}
+
+static int wm831x_alive_ldo_set_suspend_voltage(struct regulator_dev *rdev,
+ int uV)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ int reg = ldo->base + WM831X_ALIVE_LDO_SLEEP_CONTROL;
+
+ return wm831x_alive_ldo_set_voltage_int(rdev, reg, uV, uV);
+}
+
+static int wm831x_alive_ldo_get_voltage(struct regulator_dev *rdev)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int reg = ldo->base + WM831X_ALIVE_LDO_ON_CONTROL;
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, reg);
+ if (ret < 0)
+ return ret;
+
+ ret &= WM831X_LDO11_ON_VSEL_MASK;
+
+ return wm831x_alive_ldo_list_voltage(rdev, ret);
+}
+
+static int wm831x_alive_ldo_get_status(struct regulator_dev *rdev)
+{
+ struct wm831x_ldo *ldo = rdev_get_drvdata(rdev);
+ struct wm831x *wm831x = ldo->wm831x;
+ int mask = 1 << rdev_get_id(rdev);
+ int ret;
+
+ /* Is the regulator on? */
+ ret = wm831x_reg_read(wm831x, WM831X_LDO_STATUS);
+ if (ret < 0)
+ return ret;
+ if (ret & mask)
+ return REGULATOR_STATUS_ON;
+ else
+ return REGULATOR_STATUS_OFF;
+}
+
+static struct regulator_ops wm831x_alive_ldo_ops = {
+ .list_voltage = wm831x_alive_ldo_list_voltage,
+ .get_voltage = wm831x_alive_ldo_get_voltage,
+ .set_voltage = wm831x_alive_ldo_set_voltage,
+ .set_suspend_voltage = wm831x_alive_ldo_set_suspend_voltage,
+ .get_status = wm831x_alive_ldo_get_status,
+
+ .is_enabled = wm831x_ldo_is_enabled,
+ .enable = wm831x_ldo_enable,
+ .disable = wm831x_ldo_disable,
+};
+
+static __devinit int wm831x_alive_ldo_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *pdata = wm831x->dev->platform_data;
+ int id = pdev->id % ARRAY_SIZE(pdata->ldo);
+ struct wm831x_ldo *ldo;
+ struct resource *res;
+ int ret;
+
+ dev_dbg(&pdev->dev, "Probing LDO%d\n", id + 1);
+
+ if (pdata == NULL || pdata->ldo[id] == NULL)
+ return -ENODEV;
+
+ ldo = kzalloc(sizeof(struct wm831x_ldo), GFP_KERNEL);
+ if (ldo == NULL) {
+ dev_err(&pdev->dev, "Unable to allocate private data\n");
+ return -ENOMEM;
+ }
+
+ ldo->wm831x = wm831x;
+
+ res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (res == NULL) {
+ dev_err(&pdev->dev, "No I/O resource\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ ldo->base = res->start;
+
+ snprintf(ldo->name, sizeof(ldo->name), "LDO%d", id + 1);
+ ldo->desc.name = ldo->name;
+ ldo->desc.id = id;
+ ldo->desc.type = REGULATOR_VOLTAGE;
+ ldo->desc.n_voltages = WM831X_ALIVE_LDO_MAX_SELECTOR + 1;
+ ldo->desc.ops = &wm831x_alive_ldo_ops;
+ ldo->desc.owner = THIS_MODULE;
+
+ ldo->regulator = regulator_register(&ldo->desc, &pdev->dev,
+ pdata->ldo[id], ldo);
+ if (IS_ERR(ldo->regulator)) {
+ ret = PTR_ERR(ldo->regulator);
+ dev_err(wm831x->dev, "Failed to register LDO%d: %d\n",
+ id + 1, ret);
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, ldo);
+
+ return 0;
+
+err:
+ kfree(ldo);
+ return ret;
+}
+
+static __devexit int wm831x_alive_ldo_remove(struct platform_device *pdev)
+{
+ struct wm831x_ldo *ldo = platform_get_drvdata(pdev);
+
+ regulator_unregister(ldo->regulator);
+ kfree(ldo);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_alive_ldo_driver = {
+ .probe = wm831x_alive_ldo_probe,
+ .remove = __devexit_p(wm831x_alive_ldo_remove),
+ .driver = {
+ .name = "wm831x-alive-ldo",
+ },
+};
+
+static int __init wm831x_ldo_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&wm831x_gp_ldo_driver);
+ if (ret != 0)
+ pr_err("Failed to register WM831x GP LDO driver: %d\n", ret);
+
+ ret = platform_driver_register(&wm831x_aldo_driver);
+ if (ret != 0)
+ pr_err("Failed to register WM831x ALDO driver: %d\n", ret);
+
+ ret = platform_driver_register(&wm831x_alive_ldo_driver);
+ if (ret != 0)
+ pr_err("Failed to register WM831x alive LDO driver: %d\n",
+ ret);
+
+ return 0;
+}
+subsys_initcall(wm831x_ldo_init);
+
+static void __exit wm831x_ldo_exit(void)
+{
+ platform_driver_unregister(&wm831x_alive_ldo_driver);
+ platform_driver_unregister(&wm831x_aldo_driver);
+ platform_driver_unregister(&wm831x_gp_ldo_driver);
+}
+module_exit(wm831x_ldo_exit);
+
+/* Module information */
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("WM831x LDO driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-ldo");
+MODULE_ALIAS("platform:wm831x-aldo");
+MODULE_ALIAS("platform:wm831x-aliveldo");
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 81adbdbd504..73771b09fbd 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -518,6 +518,16 @@ config RTC_DRV_V3020
This driver can also be built as a module. If so, the module
will be called rtc-v3020.
+config RTC_DRV_WM831X
+ tristate "Wolfson Microelectronics WM831x RTC"
+ depends on MFD_WM831X
+ help
+ If you say yes here you will get support for the RTC subsystem
+ of the Wolfson Microelectronics WM831X series PMICs.
+
+ This driver can also be built as a module. If so, the module
+ will be called "rtc-wm831x".
+
config RTC_DRV_WM8350
tristate "Wolfson Microelectronics WM8350 RTC"
depends on MFD_WM8350
@@ -535,6 +545,15 @@ 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_AB3100
+ tristate "ST-Ericsson AB3100 RTC"
+ depends on AB3100_CORE
+ default y if AB3100_CORE
+ help
+ Select this to enable the ST-Ericsson AB3100 Mixed Signal IC RTC
+ support. This chip contains a battery- and capacitor-backed RTC.
+
+
comment "on-CPU RTC drivers"
config RTC_DRV_OMAP
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 3c0f2b2ac92..5e152ffe505 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -17,6 +17,7 @@ rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o
# Keep the list ordered.
+obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o
obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o
obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o
obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o
@@ -74,6 +75,7 @@ obj-$(CONFIG_RTC_DRV_TWL4030) += rtc-twl4030.o
obj-$(CONFIG_RTC_DRV_TX4939) += rtc-tx4939.o
obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o
obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o
+obj-$(CONFIG_RTC_DRV_WM831X) += rtc-wm831x.o
obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o
obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
diff --git a/drivers/rtc/rtc-ab3100.c b/drivers/rtc/rtc-ab3100.c
new file mode 100644
index 00000000000..4704aac2b5a
--- /dev/null
+++ b/drivers/rtc/rtc-ab3100.c
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2007-2009 ST-Ericsson AB
+ * License terms: GNU General Public License (GPL) version 2
+ * RTC clock driver for the AB3100 Analog Baseband Chip
+ * Author: Linus Walleij <linus.walleij@stericsson.com>
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/mfd/ab3100.h>
+
+/* Clock rate in Hz */
+#define AB3100_RTC_CLOCK_RATE 32768
+
+/*
+ * The AB3100 RTC registers. These are the same for
+ * AB3000 and AB3100.
+ * Control register:
+ * Bit 0: RTC Monitor cleared=0, active=1, if you set it
+ * to 1 it remains active until RTC power is lost.
+ * Bit 1: 32 kHz Oscillator, 0 = on, 1 = bypass
+ * Bit 2: Alarm on, 0 = off, 1 = on
+ * Bit 3: 32 kHz buffer disabling, 0 = enabled, 1 = disabled
+ */
+#define AB3100_RTC 0x53
+/* default setting, buffer disabled, alarm on */
+#define RTC_SETTING 0x30
+/* Alarm when AL0-AL3 == TI0-TI3 */
+#define AB3100_AL0 0x56
+#define AB3100_AL1 0x57
+#define AB3100_AL2 0x58
+#define AB3100_AL3 0x59
+/* This 48-bit register that counts up at 32768 Hz */
+#define AB3100_TI0 0x5a
+#define AB3100_TI1 0x5b
+#define AB3100_TI2 0x5c
+#define AB3100_TI3 0x5d
+#define AB3100_TI4 0x5e
+#define AB3100_TI5 0x5f
+
+/*
+ * RTC clock functions and device struct declaration
+ */
+static int ab3100_rtc_set_mmss(struct device *dev, unsigned long secs)
+{
+ struct ab3100 *ab3100_data = dev_get_drvdata(dev);
+ u8 regs[] = {AB3100_TI0, AB3100_TI1, AB3100_TI2,
+ AB3100_TI3, AB3100_TI4, AB3100_TI5};
+ unsigned char buf[6];
+ u64 fat_time = (u64) secs * AB3100_RTC_CLOCK_RATE * 2;
+ int err = 0;
+ int i;
+
+ buf[0] = (fat_time) & 0xFF;
+ buf[1] = (fat_time >> 8) & 0xFF;
+ buf[2] = (fat_time >> 16) & 0xFF;
+ buf[3] = (fat_time >> 24) & 0xFF;
+ buf[4] = (fat_time >> 32) & 0xFF;
+ buf[5] = (fat_time >> 40) & 0xFF;
+
+ for (i = 0; i < 6; i++) {
+ err = ab3100_set_register_interruptible(ab3100_data,
+ regs[i], buf[i]);
+ if (err)
+ return err;
+ }
+
+ /* Set the flag to mark that the clock is now set */
+ return ab3100_mask_and_set_register_interruptible(ab3100_data,
+ AB3100_RTC,
+ 0xFE, 0x01);
+
+}
+
+static int ab3100_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct ab3100 *ab3100_data = dev_get_drvdata(dev);
+ unsigned long time;
+ u8 rtcval;
+ int err;
+
+ err = ab3100_get_register_interruptible(ab3100_data,
+ AB3100_RTC, &rtcval);
+ if (err)
+ return err;
+
+ if (!(rtcval & 0x01)) {
+ dev_info(dev, "clock not set (lost power)");
+ return -EINVAL;
+ } else {
+ u64 fat_time;
+ u8 buf[6];
+
+ /* Read out time registers */
+ err = ab3100_get_register_page_interruptible(ab3100_data,
+ AB3100_TI0,
+ buf, 6);
+ if (err != 0)
+ return err;
+
+ fat_time = ((u64) buf[5] << 40) | ((u64) buf[4] << 32) |
+ ((u64) buf[3] << 24) | ((u64) buf[2] << 16) |
+ ((u64) buf[1] << 8) | (u64) buf[0];
+ time = (unsigned long) (fat_time /
+ (u64) (AB3100_RTC_CLOCK_RATE * 2));
+ }
+
+ rtc_time_to_tm(time, tm);
+
+ return rtc_valid_tm(tm);
+}
+
+static int ab3100_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct ab3100 *ab3100_data = dev_get_drvdata(dev);
+ unsigned long time;
+ u64 fat_time;
+ u8 buf[6];
+ u8 rtcval;
+ int err;
+
+ /* Figure out if alarm is enabled or not */
+ err = ab3100_get_register_interruptible(ab3100_data,
+ AB3100_RTC, &rtcval);
+ if (err)
+ return err;
+ if (rtcval & 0x04)
+ alarm->enabled = 1;
+ else
+ alarm->enabled = 0;
+ /* No idea how this could be represented */
+ alarm->pending = 0;
+ /* Read out alarm registers, only 4 bytes */
+ err = ab3100_get_register_page_interruptible(ab3100_data,
+ AB3100_AL0, buf, 4);
+ if (err)
+ return err;
+ fat_time = ((u64) buf[3] << 40) | ((u64) buf[2] << 32) |
+ ((u64) buf[1] << 24) | ((u64) buf[0] << 16);
+ time = (unsigned long) (fat_time / (u64) (AB3100_RTC_CLOCK_RATE * 2));
+
+ rtc_time_to_tm(time, &alarm->time);
+
+ return rtc_valid_tm(&alarm->time);
+}
+
+static int ab3100_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct ab3100 *ab3100_data = dev_get_drvdata(dev);
+ u8 regs[] = {AB3100_AL0, AB3100_AL1, AB3100_AL2, AB3100_AL3};
+ unsigned char buf[4];
+ unsigned long secs;
+ u64 fat_time;
+ int err;
+ int i;
+
+ rtc_tm_to_time(&alarm->time, &secs);
+ fat_time = (u64) secs * AB3100_RTC_CLOCK_RATE * 2;
+ buf[0] = (fat_time >> 16) & 0xFF;
+ buf[1] = (fat_time >> 24) & 0xFF;
+ buf[2] = (fat_time >> 32) & 0xFF;
+ buf[3] = (fat_time >> 40) & 0xFF;
+
+ /* Set the alarm */
+ for (i = 0; i < 4; i++) {
+ err = ab3100_set_register_interruptible(ab3100_data,
+ regs[i], buf[i]);
+ if (err)
+ return err;
+ }
+ /* Then enable the alarm */
+ return ab3100_mask_and_set_register_interruptible(ab3100_data,
+ AB3100_RTC, ~(1 << 2),
+ alarm->enabled << 2);
+}
+
+static int ab3100_rtc_irq_enable(struct device *dev, unsigned int enabled)
+{
+ struct ab3100 *ab3100_data = dev_get_drvdata(dev);
+
+ /*
+ * It's not possible to enable/disable the alarm IRQ for this RTC.
+ * It does not actually trigger any IRQ: instead its only function is
+ * to power up the system, if it wasn't on. This will manifest as
+ * a "power up cause" in the AB3100 power driver (battery charging etc)
+ * and need to be handled there instead.
+ */
+ if (enabled)
+ return ab3100_mask_and_set_register_interruptible(ab3100_data,
+ AB3100_RTC, ~(1 << 2),
+ 1 << 2);
+ else
+ return ab3100_mask_and_set_register_interruptible(ab3100_data,
+ AB3100_RTC, ~(1 << 2),
+ 0);
+}
+
+static const struct rtc_class_ops ab3100_rtc_ops = {
+ .read_time = ab3100_rtc_read_time,
+ .set_mmss = ab3100_rtc_set_mmss,
+ .read_alarm = ab3100_rtc_read_alarm,
+ .set_alarm = ab3100_rtc_set_alarm,
+ .alarm_irq_enable = ab3100_rtc_irq_enable,
+};
+
+static int __init ab3100_rtc_probe(struct platform_device *pdev)
+{
+ int err;
+ u8 regval;
+ struct rtc_device *rtc;
+ struct ab3100 *ab3100_data = platform_get_drvdata(pdev);
+
+ /* The first RTC register needs special treatment */
+ err = ab3100_get_register_interruptible(ab3100_data,
+ AB3100_RTC, &regval);
+ if (err) {
+ dev_err(&pdev->dev, "unable to read RTC register\n");
+ return -ENODEV;
+ }
+
+ if ((regval & 0xFE) != RTC_SETTING) {
+ dev_warn(&pdev->dev, "not default value in RTC reg 0x%x\n",
+ regval);
+ }
+
+ if ((regval & 1) == 0) {
+ /*
+ * Set bit to detect power loss.
+ * This bit remains until RTC power is lost.
+ */
+ regval = 1 | RTC_SETTING;
+ err = ab3100_set_register_interruptible(ab3100_data,
+ AB3100_RTC, regval);
+ /* Ignore any error on this write */
+ }
+
+ rtc = rtc_device_register("ab3100-rtc", &pdev->dev, &ab3100_rtc_ops,
+ THIS_MODULE);
+ if (IS_ERR(rtc)) {
+ err = PTR_ERR(rtc);
+ return err;
+ }
+
+ return 0;
+}
+
+static int __exit ab3100_rtc_remove(struct platform_device *pdev)
+{
+ struct rtc_device *rtc = platform_get_drvdata(pdev);
+
+ rtc_device_unregister(rtc);
+ return 0;
+}
+
+static struct platform_driver ab3100_rtc_driver = {
+ .driver = {
+ .name = "ab3100-rtc",
+ .owner = THIS_MODULE,
+ },
+ .remove = __exit_p(ab3100_rtc_remove),
+};
+
+static int __init ab3100_rtc_init(void)
+{
+ return platform_driver_probe(&ab3100_rtc_driver,
+ ab3100_rtc_probe);
+}
+
+static void __exit ab3100_rtc_exit(void)
+{
+ platform_driver_unregister(&ab3100_rtc_driver);
+}
+
+module_init(ab3100_rtc_init);
+module_exit(ab3100_rtc_exit);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>");
+MODULE_DESCRIPTION("AB3100 RTC Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/rtc/rtc-ds1302.c b/drivers/rtc/rtc-ds1302.c
index 18455662077..d490628b64d 100644
--- a/drivers/rtc/rtc-ds1302.c
+++ b/drivers/rtc/rtc-ds1302.c
@@ -1,26 +1,25 @@
/*
* Dallas DS1302 RTC Support
*
- * Copyright (C) 2002 David McCullough
- * Copyright (C) 2003 - 2007 Paul Mundt
+ * Copyright (C) 2002 David McCullough
+ * Copyright (C) 2003 - 2007 Paul Mundt
*
* This file is subject to the terms and conditions of the GNU General Public
- * License version 2. See the file "COPYING" in the main directory of
+ * License version 2. See the file "COPYING" in the main directory of
* this archive for more details.
*/
+
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
-#include <linux/time.h>
#include <linux/rtc.h>
-#include <linux/spinlock.h>
#include <linux/io.h>
#include <linux/bcd.h>
#include <asm/rtc.h>
#define DRV_NAME "rtc-ds1302"
-#define DRV_VERSION "0.1.0"
+#define DRV_VERSION "0.1.1"
#define RTC_CMD_READ 0x81 /* Read command */
#define RTC_CMD_WRITE 0x80 /* Write command */
@@ -47,11 +46,6 @@
#error "Add support for your platform"
#endif
-struct ds1302_rtc {
- struct rtc_device *rtc_dev;
- spinlock_t lock;
-};
-
static void ds1302_sendbits(unsigned int val)
{
int i;
@@ -103,10 +97,6 @@ static void ds1302_writebyte(unsigned int addr, unsigned int val)
static int ds1302_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
- struct ds1302_rtc *rtc = dev_get_drvdata(dev);
-
- spin_lock_irq(&rtc->lock);
-
tm->tm_sec = bcd2bin(ds1302_readbyte(RTC_ADDR_SEC));
tm->tm_min = bcd2bin(ds1302_readbyte(RTC_ADDR_MIN));
tm->tm_hour = bcd2bin(ds1302_readbyte(RTC_ADDR_HOUR));
@@ -118,26 +108,17 @@ static int ds1302_rtc_read_time(struct device *dev, struct rtc_time *tm)
if (tm->tm_year < 70)
tm->tm_year += 100;
- spin_unlock_irq(&rtc->lock);
-
dev_dbg(dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
"mday=%d, mon=%d, year=%d, wday=%d\n",
__func__,
tm->tm_sec, tm->tm_min, tm->tm_hour,
tm->tm_mday, tm->tm_mon + 1, tm->tm_year, tm->tm_wday);
- if (rtc_valid_tm(tm) < 0)
- dev_err(dev, "invalid date\n");
-
- return 0;
+ return rtc_valid_tm(tm);
}
static int ds1302_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
- struct ds1302_rtc *rtc = dev_get_drvdata(dev);
-
- spin_lock_irq(&rtc->lock);
-
/* Stop RTC */
ds1302_writebyte(RTC_ADDR_SEC, ds1302_readbyte(RTC_ADDR_SEC) | 0x80);
@@ -152,8 +133,6 @@ static int ds1302_rtc_set_time(struct device *dev, struct rtc_time *tm)
/* Start RTC */
ds1302_writebyte(RTC_ADDR_SEC, ds1302_readbyte(RTC_ADDR_SEC) & ~0x80);
- spin_unlock_irq(&rtc->lock);
-
return 0;
}
@@ -170,9 +149,7 @@ static int ds1302_rtc_ioctl(struct device *dev, unsigned int cmd,
if (copy_from_user(&tcs_val, (int __user *)arg, sizeof(int)))
return -EFAULT;
- spin_lock_irq(&rtc->lock);
ds1302_writebyte(RTC_ADDR_TCR, (0xa0 | tcs_val * 0xf));
- spin_unlock_irq(&rtc->lock);
return 0;
}
#endif
@@ -187,10 +164,9 @@ static struct rtc_class_ops ds1302_rtc_ops = {
.ioctl = ds1302_rtc_ioctl,
};
-static int __devinit ds1302_rtc_probe(struct platform_device *pdev)
+static int __init ds1302_rtc_probe(struct platform_device *pdev)
{
- struct ds1302_rtc *rtc;
- int ret;
+ struct rtc_device *rtc;
/* Reset */
set_dp(get_dp() & ~(RTC_RESET | RTC_IODATA | RTC_SCLK));
@@ -200,37 +176,23 @@ static int __devinit ds1302_rtc_probe(struct platform_device *pdev)
if (ds1302_readbyte(RTC_ADDR_RAM0) != 0x42)
return -ENODEV;
- rtc = kzalloc(sizeof(struct ds1302_rtc), GFP_KERNEL);
- if (unlikely(!rtc))
- return -ENOMEM;
-
- spin_lock_init(&rtc->lock);
- rtc->rtc_dev = rtc_device_register("ds1302", &pdev->dev,
+ rtc = rtc_device_register("ds1302", &pdev->dev,
&ds1302_rtc_ops, THIS_MODULE);
- if (IS_ERR(rtc->rtc_dev)) {
- ret = PTR_ERR(rtc->rtc_dev);
- goto out;
- }
+ if (IS_ERR(rtc))
+ return PTR_ERR(rtc);
platform_set_drvdata(pdev, rtc);
return 0;
-out:
- kfree(rtc);
- return ret;
}
static int __devexit ds1302_rtc_remove(struct platform_device *pdev)
{
- struct ds1302_rtc *rtc = platform_get_drvdata(pdev);
-
- if (likely(rtc->rtc_dev))
- rtc_device_unregister(rtc->rtc_dev);
+ struct rtc_device *rtc = platform_get_drvdata(pdev);
+ rtc_device_unregister(rtc);
platform_set_drvdata(pdev, NULL);
- kfree(rtc);
-
return 0;
}
@@ -239,13 +201,12 @@ static struct platform_driver ds1302_platform_driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
},
- .probe = ds1302_rtc_probe,
- .remove = __devexit_p(ds1302_rtc_remove),
+ .remove = __exit_p(ds1302_rtc_remove),
};
static int __init ds1302_rtc_init(void)
{
- return platform_driver_register(&ds1302_platform_driver);
+ return platform_driver_probe(&ds1302_platform_driver, ds1302_rtc_probe);
}
static void __exit ds1302_rtc_exit(void)
diff --git a/drivers/rtc/rtc-sh.c b/drivers/rtc/rtc-sh.c
index d7310adb715..e6ed5404bca 100644
--- a/drivers/rtc/rtc-sh.c
+++ b/drivers/rtc/rtc-sh.c
@@ -29,7 +29,7 @@
#include <asm/rtc.h>
#define DRV_NAME "sh-rtc"
-#define DRV_VERSION "0.2.2"
+#define DRV_VERSION "0.2.3"
#define RTC_REG(r) ((r) * rtc_reg_size)
@@ -215,7 +215,7 @@ static irqreturn_t sh_rtc_shared(int irq, void *dev_id)
return IRQ_RETVAL(ret);
}
-static inline void sh_rtc_setpie(struct device *dev, unsigned int enable)
+static int sh_rtc_irq_set_state(struct device *dev, int enable)
{
struct sh_rtc *rtc = dev_get_drvdata(dev);
unsigned int tmp;
@@ -225,17 +225,22 @@ static inline void sh_rtc_setpie(struct device *dev, unsigned int enable)
tmp = readb(rtc->regbase + RCR2);
if (enable) {
+ rtc->periodic_freq |= PF_KOU;
tmp &= ~RCR2_PEF; /* Clear PES bit */
tmp |= (rtc->periodic_freq & ~PF_HP); /* Set PES2-0 */
- } else
+ } else {
+ rtc->periodic_freq &= ~PF_KOU;
tmp &= ~(RCR2_PESMASK | RCR2_PEF);
+ }
writeb(tmp, rtc->regbase + RCR2);
spin_unlock_irq(&rtc->lock);
+
+ return 0;
}
-static inline int sh_rtc_setfreq(struct device *dev, unsigned int freq)
+static int sh_rtc_irq_set_freq(struct device *dev, int freq)
{
struct sh_rtc *rtc = dev_get_drvdata(dev);
int tmp, ret = 0;
@@ -278,10 +283,8 @@ static inline int sh_rtc_setfreq(struct device *dev, unsigned int freq)
ret = -ENOTSUPP;
}
- if (ret == 0) {
+ if (ret == 0)
rtc->periodic_freq |= tmp;
- rtc->rtc_dev->irq_freq = freq;
- }
spin_unlock_irq(&rtc->lock);
return ret;
@@ -346,10 +349,6 @@ static int sh_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
unsigned int ret = 0;
switch (cmd) {
- case RTC_PIE_OFF:
- case RTC_PIE_ON:
- sh_rtc_setpie(dev, cmd == RTC_PIE_ON);
- break;
case RTC_AIE_OFF:
case RTC_AIE_ON:
sh_rtc_setaie(dev, cmd == RTC_AIE_ON);
@@ -362,13 +361,6 @@ static int sh_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
rtc->periodic_freq |= PF_OXS;
sh_rtc_setcie(dev, 1);
break;
- case RTC_IRQP_READ:
- ret = put_user(rtc->rtc_dev->irq_freq,
- (unsigned long __user *)arg);
- break;
- case RTC_IRQP_SET:
- ret = sh_rtc_setfreq(dev, arg);
- break;
default:
ret = -ENOIOCTLCMD;
}
@@ -602,28 +594,6 @@ static int sh_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm)
return 0;
}
-static int sh_rtc_irq_set_state(struct device *dev, int enabled)
-{
- struct platform_device *pdev = to_platform_device(dev);
- struct sh_rtc *rtc = platform_get_drvdata(pdev);
-
- if (enabled) {
- rtc->periodic_freq |= PF_KOU;
- return sh_rtc_ioctl(dev, RTC_PIE_ON, 0);
- } else {
- rtc->periodic_freq &= ~PF_KOU;
- return sh_rtc_ioctl(dev, RTC_PIE_OFF, 0);
- }
-}
-
-static int sh_rtc_irq_set_freq(struct device *dev, int freq)
-{
- if (!is_power_of_2(freq))
- return -EINVAL;
-
- return sh_rtc_ioctl(dev, RTC_IRQP_SET, freq);
-}
-
static struct rtc_class_ops sh_rtc_ops = {
.ioctl = sh_rtc_ioctl,
.read_time = sh_rtc_read_time,
@@ -635,7 +605,7 @@ static struct rtc_class_ops sh_rtc_ops = {
.proc = sh_rtc_proc,
};
-static int __devinit sh_rtc_probe(struct platform_device *pdev)
+static int __init sh_rtc_probe(struct platform_device *pdev)
{
struct sh_rtc *rtc;
struct resource *res;
@@ -702,13 +672,6 @@ static int __devinit sh_rtc_probe(struct platform_device *pdev)
clk_enable(rtc->clk);
- rtc->rtc_dev = rtc_device_register("sh", &pdev->dev,
- &sh_rtc_ops, THIS_MODULE);
- if (IS_ERR(rtc->rtc_dev)) {
- ret = PTR_ERR(rtc->rtc_dev);
- goto err_unmap;
- }
-
rtc->capabilities = RTC_DEF_CAPABILITIES;
if (pdev->dev.platform_data) {
struct sh_rtc_platform_info *pinfo = pdev->dev.platform_data;
@@ -720,10 +683,6 @@ static int __devinit sh_rtc_probe(struct platform_device *pdev)
rtc->capabilities |= pinfo->capabilities;
}
- rtc->rtc_dev->max_user_freq = 256;
-
- platform_set_drvdata(pdev, rtc);
-
if (rtc->carry_irq <= 0) {
/* register shared periodic/carry/alarm irq */
ret = request_irq(rtc->periodic_irq, sh_rtc_shared,
@@ -767,13 +726,26 @@ static int __devinit sh_rtc_probe(struct platform_device *pdev)
}
}
+ platform_set_drvdata(pdev, rtc);
+
/* everything disabled by default */
- rtc->periodic_freq = 0;
- rtc->rtc_dev->irq_freq = 0;
- sh_rtc_setpie(&pdev->dev, 0);
+ sh_rtc_irq_set_freq(&pdev->dev, 0);
+ sh_rtc_irq_set_state(&pdev->dev, 0);
sh_rtc_setaie(&pdev->dev, 0);
sh_rtc_setcie(&pdev->dev, 0);
+ rtc->rtc_dev = rtc_device_register("sh", &pdev->dev,
+ &sh_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc->rtc_dev)) {
+ ret = PTR_ERR(rtc->rtc_dev);
+ free_irq(rtc->periodic_irq, rtc);
+ free_irq(rtc->carry_irq, rtc);
+ free_irq(rtc->alarm_irq, rtc);
+ goto err_unmap;
+ }
+
+ rtc->rtc_dev->max_user_freq = 256;
+
/* reset rtc to epoch 0 if time is invalid */
if (rtc_read_time(rtc->rtc_dev, &r) < 0) {
rtc_time_to_tm(0, &r);
@@ -795,14 +767,13 @@ err_badres:
return ret;
}
-static int __devexit sh_rtc_remove(struct platform_device *pdev)
+static int __exit sh_rtc_remove(struct platform_device *pdev)
{
struct sh_rtc *rtc = platform_get_drvdata(pdev);
- if (likely(rtc->rtc_dev))
- rtc_device_unregister(rtc->rtc_dev);
+ rtc_device_unregister(rtc->rtc_dev);
+ sh_rtc_irq_set_state(&pdev->dev, 0);
- sh_rtc_setpie(&pdev->dev, 0);
sh_rtc_setaie(&pdev->dev, 0);
sh_rtc_setcie(&pdev->dev, 0);
@@ -813,9 +784,8 @@ static int __devexit sh_rtc_remove(struct platform_device *pdev)
free_irq(rtc->alarm_irq, rtc);
}
- release_resource(rtc->res);
-
iounmap(rtc->regbase);
+ release_resource(rtc->res);
clk_disable(rtc->clk);
clk_put(rtc->clk);
@@ -867,13 +837,12 @@ static struct platform_driver sh_rtc_platform_driver = {
.owner = THIS_MODULE,
.pm = &sh_rtc_dev_pm_ops,
},
- .probe = sh_rtc_probe,
- .remove = __devexit_p(sh_rtc_remove),
+ .remove = __exit_p(sh_rtc_remove),
};
static int __init sh_rtc_init(void)
{
- return platform_driver_register(&sh_rtc_platform_driver);
+ return platform_driver_probe(&sh_rtc_platform_driver, sh_rtc_probe);
}
static void __exit sh_rtc_exit(void)
diff --git a/drivers/rtc/rtc-wm831x.c b/drivers/rtc/rtc-wm831x.c
new file mode 100644
index 00000000000..79795cdf6ed
--- /dev/null
+++ b/drivers/rtc/rtc-wm831x.c
@@ -0,0 +1,523 @@
+/*
+ * Real Time Clock driver for Wolfson Microelectronics WM831x
+ *
+ * Copyright (C) 2009 Wolfson Microelectronics PLC.
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/time.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+#include <linux/interrupt.h>
+#include <linux/ioctl.h>
+#include <linux/completion.h>
+#include <linux/mfd/wm831x/core.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+
+
+/*
+ * R16416 (0x4020) - RTC Write Counter
+ */
+#define WM831X_RTC_WR_CNT_MASK 0xFFFF /* RTC_WR_CNT - [15:0] */
+#define WM831X_RTC_WR_CNT_SHIFT 0 /* RTC_WR_CNT - [15:0] */
+#define WM831X_RTC_WR_CNT_WIDTH 16 /* RTC_WR_CNT - [15:0] */
+
+/*
+ * R16417 (0x4021) - RTC Time 1
+ */
+#define WM831X_RTC_TIME_MASK 0xFFFF /* RTC_TIME - [15:0] */
+#define WM831X_RTC_TIME_SHIFT 0 /* RTC_TIME - [15:0] */
+#define WM831X_RTC_TIME_WIDTH 16 /* RTC_TIME - [15:0] */
+
+/*
+ * R16418 (0x4022) - RTC Time 2
+ */
+#define WM831X_RTC_TIME_MASK 0xFFFF /* RTC_TIME - [15:0] */
+#define WM831X_RTC_TIME_SHIFT 0 /* RTC_TIME - [15:0] */
+#define WM831X_RTC_TIME_WIDTH 16 /* RTC_TIME - [15:0] */
+
+/*
+ * R16419 (0x4023) - RTC Alarm 1
+ */
+#define WM831X_RTC_ALM_MASK 0xFFFF /* RTC_ALM - [15:0] */
+#define WM831X_RTC_ALM_SHIFT 0 /* RTC_ALM - [15:0] */
+#define WM831X_RTC_ALM_WIDTH 16 /* RTC_ALM - [15:0] */
+
+/*
+ * R16420 (0x4024) - RTC Alarm 2
+ */
+#define WM831X_RTC_ALM_MASK 0xFFFF /* RTC_ALM - [15:0] */
+#define WM831X_RTC_ALM_SHIFT 0 /* RTC_ALM - [15:0] */
+#define WM831X_RTC_ALM_WIDTH 16 /* RTC_ALM - [15:0] */
+
+/*
+ * R16421 (0x4025) - RTC Control
+ */
+#define WM831X_RTC_VALID 0x8000 /* RTC_VALID */
+#define WM831X_RTC_VALID_MASK 0x8000 /* RTC_VALID */
+#define WM831X_RTC_VALID_SHIFT 15 /* RTC_VALID */
+#define WM831X_RTC_VALID_WIDTH 1 /* RTC_VALID */
+#define WM831X_RTC_SYNC_BUSY 0x4000 /* RTC_SYNC_BUSY */
+#define WM831X_RTC_SYNC_BUSY_MASK 0x4000 /* RTC_SYNC_BUSY */
+#define WM831X_RTC_SYNC_BUSY_SHIFT 14 /* RTC_SYNC_BUSY */
+#define WM831X_RTC_SYNC_BUSY_WIDTH 1 /* RTC_SYNC_BUSY */
+#define WM831X_RTC_ALM_ENA 0x0400 /* RTC_ALM_ENA */
+#define WM831X_RTC_ALM_ENA_MASK 0x0400 /* RTC_ALM_ENA */
+#define WM831X_RTC_ALM_ENA_SHIFT 10 /* RTC_ALM_ENA */
+#define WM831X_RTC_ALM_ENA_WIDTH 1 /* RTC_ALM_ENA */
+#define WM831X_RTC_PINT_FREQ_MASK 0x0070 /* RTC_PINT_FREQ - [6:4] */
+#define WM831X_RTC_PINT_FREQ_SHIFT 4 /* RTC_PINT_FREQ - [6:4] */
+#define WM831X_RTC_PINT_FREQ_WIDTH 3 /* RTC_PINT_FREQ - [6:4] */
+
+/*
+ * R16422 (0x4026) - RTC Trim
+ */
+#define WM831X_RTC_TRIM_MASK 0x03FF /* RTC_TRIM - [9:0] */
+#define WM831X_RTC_TRIM_SHIFT 0 /* RTC_TRIM - [9:0] */
+#define WM831X_RTC_TRIM_WIDTH 10 /* RTC_TRIM - [9:0] */
+
+#define WM831X_SET_TIME_RETRIES 5
+#define WM831X_GET_TIME_RETRIES 5
+
+struct wm831x_rtc {
+ struct wm831x *wm831x;
+ struct rtc_device *rtc;
+ unsigned int alarm_enabled:1;
+};
+
+/*
+ * Read current time and date in RTC
+ */
+static int wm831x_rtc_readtime(struct device *dev, struct rtc_time *tm)
+{
+ struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+ struct wm831x *wm831x = wm831x_rtc->wm831x;
+ u16 time1[2], time2[2];
+ int ret;
+ int count = 0;
+
+ /* Has the RTC been programmed? */
+ ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read RTC control: %d\n", ret);
+ return ret;
+ }
+ if (!(ret & WM831X_RTC_VALID)) {
+ dev_dbg(dev, "RTC not yet configured\n");
+ return -EINVAL;
+ }
+
+ /* Read twice to make sure we don't read a corrupt, partially
+ * incremented, value.
+ */
+ do {
+ ret = wm831x_bulk_read(wm831x, WM831X_RTC_TIME_1,
+ 2, time1);
+ if (ret != 0)
+ continue;
+
+ ret = wm831x_bulk_read(wm831x, WM831X_RTC_TIME_1,
+ 2, time2);
+ if (ret != 0)
+ continue;
+
+ if (memcmp(time1, time2, sizeof(time1)) == 0) {
+ u32 time = (time1[0] << 16) | time1[1];
+
+ rtc_time_to_tm(time, tm);
+ return rtc_valid_tm(tm);
+ }
+
+ } while (++count < WM831X_GET_TIME_RETRIES);
+
+ dev_err(dev, "Timed out reading current time\n");
+
+ return -EIO;
+}
+
+/*
+ * Set current time and date in RTC
+ */
+static int wm831x_rtc_set_mmss(struct device *dev, unsigned long time)
+{
+ struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+ struct wm831x *wm831x = wm831x_rtc->wm831x;
+ struct rtc_time new_tm;
+ unsigned long new_time;
+ int ret;
+ int count = 0;
+
+ ret = wm831x_reg_write(wm831x, WM831X_RTC_TIME_1,
+ (time >> 16) & 0xffff);
+ if (ret < 0) {
+ dev_err(dev, "Failed to write TIME_1: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm831x_reg_write(wm831x, WM831X_RTC_TIME_2, time & 0xffff);
+ if (ret < 0) {
+ dev_err(dev, "Failed to write TIME_2: %d\n", ret);
+ return ret;
+ }
+
+ /* Wait for the update to complete - should happen first time
+ * round but be conservative.
+ */
+ do {
+ msleep(1);
+
+ ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL);
+ if (ret < 0)
+ ret = WM831X_RTC_SYNC_BUSY;
+ } while (!(ret & WM831X_RTC_SYNC_BUSY) &&
+ ++count < WM831X_SET_TIME_RETRIES);
+
+ if (ret & WM831X_RTC_SYNC_BUSY) {
+ dev_err(dev, "Timed out writing RTC update\n");
+ return -EIO;
+ }
+
+ /* Check that the update was accepted; security features may
+ * have caused the update to be ignored.
+ */
+ ret = wm831x_rtc_readtime(dev, &new_tm);
+ if (ret < 0)
+ return ret;
+
+ ret = rtc_tm_to_time(&new_tm, &new_time);
+ if (ret < 0) {
+ dev_err(dev, "Failed to convert time: %d\n", ret);
+ return ret;
+ }
+
+ /* Allow a second of change in case of tick */
+ if (new_time - time > 1) {
+ dev_err(dev, "RTC update not permitted by hardware\n");
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+/*
+ * Read alarm time and date in RTC
+ */
+static int wm831x_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+ int ret;
+ u16 data[2];
+ u32 time;
+
+ ret = wm831x_bulk_read(wm831x_rtc->wm831x, WM831X_RTC_ALARM_1,
+ 2, data);
+ if (ret != 0) {
+ dev_err(dev, "Failed to read alarm time: %d\n", ret);
+ return ret;
+ }
+
+ time = (data[0] << 16) | data[1];
+
+ rtc_time_to_tm(time, &alrm->time);
+
+ ret = wm831x_reg_read(wm831x_rtc->wm831x, WM831X_RTC_CONTROL);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read RTC control: %d\n", ret);
+ return ret;
+ }
+
+ if (ret & WM831X_RTC_ALM_ENA)
+ alrm->enabled = 1;
+ else
+ alrm->enabled = 0;
+
+ return 0;
+}
+
+static int wm831x_rtc_stop_alarm(struct wm831x_rtc *wm831x_rtc)
+{
+ wm831x_rtc->alarm_enabled = 0;
+
+ return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
+ WM831X_RTC_ALM_ENA, 0);
+}
+
+static int wm831x_rtc_start_alarm(struct wm831x_rtc *wm831x_rtc)
+{
+ wm831x_rtc->alarm_enabled = 1;
+
+ return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
+ WM831X_RTC_ALM_ENA, WM831X_RTC_ALM_ENA);
+}
+
+static int wm831x_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+ struct wm831x *wm831x = wm831x_rtc->wm831x;
+ int ret;
+ unsigned long time;
+
+ ret = rtc_tm_to_time(&alrm->time, &time);
+ if (ret < 0) {
+ dev_err(dev, "Failed to convert time: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm831x_rtc_stop_alarm(wm831x_rtc);
+ if (ret < 0) {
+ dev_err(dev, "Failed to stop alarm: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm831x_reg_write(wm831x, WM831X_RTC_ALARM_1,
+ (time >> 16) & 0xffff);
+ if (ret < 0) {
+ dev_err(dev, "Failed to write ALARM_1: %d\n", ret);
+ return ret;
+ }
+
+ ret = wm831x_reg_write(wm831x, WM831X_RTC_ALARM_2, time & 0xffff);
+ if (ret < 0) {
+ dev_err(dev, "Failed to write ALARM_2: %d\n", ret);
+ return ret;
+ }
+
+ if (alrm->enabled) {
+ ret = wm831x_rtc_start_alarm(wm831x_rtc);
+ if (ret < 0) {
+ dev_err(dev, "Failed to start alarm: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int wm831x_rtc_alarm_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+
+ if (enabled)
+ return wm831x_rtc_start_alarm(wm831x_rtc);
+ else
+ return wm831x_rtc_stop_alarm(wm831x_rtc);
+}
+
+static int wm831x_rtc_update_irq_enable(struct device *dev,
+ unsigned int enabled)
+{
+ struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(dev);
+ int val;
+
+ if (enabled)
+ val = 1 << WM831X_RTC_PINT_FREQ_SHIFT;
+ else
+ val = 0;
+
+ return wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
+ WM831X_RTC_PINT_FREQ_MASK, val);
+}
+
+static irqreturn_t wm831x_alm_irq(int irq, void *data)
+{
+ struct wm831x_rtc *wm831x_rtc = data;
+
+ rtc_update_irq(wm831x_rtc->rtc, 1, RTC_IRQF | RTC_AF);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t wm831x_per_irq(int irq, void *data)
+{
+ struct wm831x_rtc *wm831x_rtc = data;
+
+ rtc_update_irq(wm831x_rtc->rtc, 1, RTC_IRQF | RTC_UF);
+
+ return IRQ_HANDLED;
+}
+
+static const struct rtc_class_ops wm831x_rtc_ops = {
+ .read_time = wm831x_rtc_readtime,
+ .set_mmss = wm831x_rtc_set_mmss,
+ .read_alarm = wm831x_rtc_readalarm,
+ .set_alarm = wm831x_rtc_setalarm,
+ .alarm_irq_enable = wm831x_rtc_alarm_irq_enable,
+ .update_irq_enable = wm831x_rtc_update_irq_enable,
+};
+
+#ifdef CONFIG_PM
+/* Turn off the alarm if it should not be a wake source. */
+static int wm831x_rtc_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev);
+ int ret, enable;
+
+ if (wm831x_rtc->alarm_enabled && device_may_wakeup(&pdev->dev))
+ enable = WM831X_RTC_ALM_ENA;
+ else
+ enable = 0;
+
+ ret = wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
+ WM831X_RTC_ALM_ENA, enable);
+ if (ret != 0)
+ dev_err(&pdev->dev, "Failed to update RTC alarm: %d\n", ret);
+
+ return 0;
+}
+
+/* Enable the alarm if it should be enabled (in case it was disabled to
+ * prevent use as a wake source).
+ */
+static int wm831x_rtc_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev);
+ int ret;
+
+ if (wm831x_rtc->alarm_enabled) {
+ ret = wm831x_rtc_start_alarm(wm831x_rtc);
+ if (ret != 0)
+ dev_err(&pdev->dev,
+ "Failed to restart RTC alarm: %d\n", ret);
+ }
+
+ return 0;
+}
+
+/* Unconditionally disable the alarm */
+static int wm831x_rtc_freeze(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct wm831x_rtc *wm831x_rtc = dev_get_drvdata(&pdev->dev);
+ int ret;
+
+ ret = wm831x_set_bits(wm831x_rtc->wm831x, WM831X_RTC_CONTROL,
+ WM831X_RTC_ALM_ENA, 0);
+ if (ret != 0)
+ dev_err(&pdev->dev, "Failed to stop RTC alarm: %d\n", ret);
+
+ return 0;
+}
+#else
+#define wm831x_rtc_suspend NULL
+#define wm831x_rtc_resume NULL
+#define wm831x_rtc_freeze NULL
+#endif
+
+static int wm831x_rtc_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_rtc *wm831x_rtc;
+ int per_irq = platform_get_irq_byname(pdev, "PER");
+ int alm_irq = platform_get_irq_byname(pdev, "ALM");
+ int ret = 0;
+
+ wm831x_rtc = kzalloc(sizeof(*wm831x_rtc), GFP_KERNEL);
+ if (wm831x_rtc == NULL)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, wm831x_rtc);
+ wm831x_rtc->wm831x = wm831x;
+
+ ret = wm831x_reg_read(wm831x, WM831X_RTC_CONTROL);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to read RTC control: %d\n", ret);
+ goto err;
+ }
+ if (ret & WM831X_RTC_ALM_ENA)
+ wm831x_rtc->alarm_enabled = 1;
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ wm831x_rtc->rtc = rtc_device_register("wm831x", &pdev->dev,
+ &wm831x_rtc_ops, THIS_MODULE);
+ if (IS_ERR(wm831x_rtc->rtc)) {
+ ret = PTR_ERR(wm831x_rtc->rtc);
+ goto err;
+ }
+
+ ret = wm831x_request_irq(wm831x, per_irq, wm831x_per_irq,
+ IRQF_TRIGGER_RISING, "wm831x_rtc_per",
+ wm831x_rtc);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request periodic IRQ %d: %d\n",
+ per_irq, ret);
+ }
+
+ ret = wm831x_request_irq(wm831x, alm_irq, wm831x_alm_irq,
+ IRQF_TRIGGER_RISING, "wm831x_rtc_alm",
+ wm831x_rtc);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request alarm IRQ %d: %d\n",
+ alm_irq, ret);
+ }
+
+ return 0;
+
+err:
+ kfree(wm831x_rtc);
+ return ret;
+}
+
+static int __devexit wm831x_rtc_remove(struct platform_device *pdev)
+{
+ struct wm831x_rtc *wm831x_rtc = platform_get_drvdata(pdev);
+ int per_irq = platform_get_irq_byname(pdev, "PER");
+ int alm_irq = platform_get_irq_byname(pdev, "ALM");
+
+ wm831x_free_irq(wm831x_rtc->wm831x, alm_irq, wm831x_rtc);
+ wm831x_free_irq(wm831x_rtc->wm831x, per_irq, wm831x_rtc);
+ rtc_device_unregister(wm831x_rtc->rtc);
+ kfree(wm831x_rtc);
+
+ return 0;
+}
+
+static struct dev_pm_ops wm831x_rtc_pm_ops = {
+ .suspend = wm831x_rtc_suspend,
+ .resume = wm831x_rtc_resume,
+
+ .freeze = wm831x_rtc_freeze,
+ .thaw = wm831x_rtc_resume,
+ .restore = wm831x_rtc_resume,
+
+ .poweroff = wm831x_rtc_suspend,
+};
+
+static struct platform_driver wm831x_rtc_driver = {
+ .probe = wm831x_rtc_probe,
+ .remove = __devexit_p(wm831x_rtc_remove),
+ .driver = {
+ .name = "wm831x-rtc",
+ .pm = &wm831x_rtc_pm_ops,
+ },
+};
+
+static int __init wm831x_rtc_init(void)
+{
+ return platform_driver_register(&wm831x_rtc_driver);
+}
+module_init(wm831x_rtc_init);
+
+static void __exit wm831x_rtc_exit(void)
+{
+ platform_driver_unregister(&wm831x_rtc_driver);
+}
+module_exit(wm831x_rtc_exit);
+
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_DESCRIPTION("RTC driver for the WM831x series PMICs");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-rtc");
diff --git a/drivers/serial/ioc4_serial.c b/drivers/serial/ioc4_serial.c
index 6bab63cd5b2..e5c58fe7e74 100644
--- a/drivers/serial/ioc4_serial.c
+++ b/drivers/serial/ioc4_serial.c
@@ -930,7 +930,7 @@ static void handle_dma_error_intr(void *arg, uint32_t other_ir)
if (readl(&port->ip_mem->pci_err_addr_l.raw) & IOC4_PCI_ERR_ADDR_VLD) {
printk(KERN_ERR
- "PCI error address is 0x%lx, "
+ "PCI error address is 0x%llx, "
"master is serial port %c %s\n",
(((uint64_t)readl(&port->ip_mem->pci_err_addr_h)
<< 32)
diff --git a/drivers/serial/sh-sci.c b/drivers/serial/sh-sci.c
index 8e2feb56334..32dc2fc50e6 100644
--- a/drivers/serial/sh-sci.c
+++ b/drivers/serial/sh-sci.c
@@ -272,7 +272,8 @@ static inline void sci_init_pins(struct uart_port *port, unsigned int cflag)
__raw_writew(data, PSCR);
}
}
-#elif defined(CONFIG_CPU_SUBTYPE_SH7763) || \
+#elif defined(CONFIG_CPU_SUBTYPE_SH7757) || \
+ defined(CONFIG_CPU_SUBTYPE_SH7763) || \
defined(CONFIG_CPU_SUBTYPE_SH7780) || \
defined(CONFIG_CPU_SUBTYPE_SH7785) || \
defined(CONFIG_CPU_SUBTYPE_SH7786) || \
@@ -662,10 +663,11 @@ static irqreturn_t sci_rx_interrupt(int irq, void *port)
static irqreturn_t sci_tx_interrupt(int irq, void *ptr)
{
struct uart_port *port = ptr;
+ unsigned long flags;
- spin_lock_irq(&port->lock);
+ spin_lock_irqsave(&port->lock, flags);
sci_transmit_chars(port);
- spin_unlock_irq(&port->lock);
+ spin_unlock_irqrestore(&port->lock, flags);
return IRQ_HANDLED;
}
diff --git a/drivers/serial/sh-sci.h b/drivers/serial/sh-sci.h
index 38072c15b84..3e2fcf93b42 100644
--- a/drivers/serial/sh-sci.h
+++ b/drivers/serial/sh-sci.h
@@ -112,6 +112,13 @@
#elif defined(CONFIG_H8S2678)
# define SCSCR_INIT(port) 0x30 /* TIE=0,RIE=0,TE=1,RE=1 */
# define H8300_SCI_DR(ch) *(volatile char *)(P1DR + h8300_sci_pins[ch].port)
+#elif defined(CONFIG_CPU_SUBTYPE_SH7757)
+# define SCSPTR0 0xfe4b0020
+# define SCSPTR1 0xfe4b0020
+# define SCSPTR2 0xfe4b0020
+# define SCIF_ORER 0x0001
+# define SCSCR_INIT(port) 0x38
+# define SCIF_ONLY
#elif defined(CONFIG_CPU_SUBTYPE_SH7763)
# define SCSPTR0 0xffe00024 /* 16 bit SCIF */
# define SCSPTR1 0xffe08024 /* 16 bit SCIF */
@@ -562,6 +569,16 @@ static inline int sci_rxd_in(struct uart_port *port)
return ctrl_inw(SCSPTR2)&0x0001 ? 1 : 0; /* SCIF */
return 1;
}
+#elif defined(CONFIG_CPU_SUBTYPE_SH7757)
+static inline int sci_rxd_in(struct uart_port *port)
+{
+ if (port->mapbase == 0xfe4b0000)
+ return ctrl_inw(SCSPTR0) & 0x0001 ? 1 : 0;
+ if (port->mapbase == 0xfe4c0000)
+ return ctrl_inw(SCSPTR1) & 0x0001 ? 1 : 0;
+ if (port->mapbase == 0xfe4d0000)
+ return ctrl_inw(SCSPTR2) & 0x0001 ? 1 : 0;
+}
#elif defined(CONFIG_CPU_SUBTYPE_SH7760)
static inline int sci_rxd_in(struct uart_port *port)
{
diff --git a/drivers/sh/intc.c b/drivers/sh/intc.c
index 3dd231a643b..559b5fe9dc0 100644
--- a/drivers/sh/intc.c
+++ b/drivers/sh/intc.c
@@ -77,7 +77,7 @@ static unsigned long ack_handle[NR_IRQS];
static inline struct intc_desc_int *get_intc_desc(unsigned int irq)
{
struct irq_chip *chip = get_irq_chip(irq);
- return (void *)((char *)chip - offsetof(struct intc_desc_int, chip));
+ return container_of(chip, struct intc_desc_int, chip);
}
static inline unsigned int set_field(unsigned int value,
@@ -95,16 +95,19 @@ static inline unsigned int set_field(unsigned int value,
static void write_8(unsigned long addr, unsigned long h, unsigned long data)
{
__raw_writeb(set_field(0, data, h), addr);
+ (void)__raw_readb(addr); /* Defeat write posting */
}
static void write_16(unsigned long addr, unsigned long h, unsigned long data)
{
__raw_writew(set_field(0, data, h), addr);
+ (void)__raw_readw(addr); /* Defeat write posting */
}
static void write_32(unsigned long addr, unsigned long h, unsigned long data)
{
__raw_writel(set_field(0, data, h), addr);
+ (void)__raw_readl(addr); /* Defeat write posting */
}
static void modify_8(unsigned long addr, unsigned long h, unsigned long data)
@@ -112,6 +115,7 @@ static void modify_8(unsigned long addr, unsigned long h, unsigned long data)
unsigned long flags;
local_irq_save(flags);
__raw_writeb(set_field(__raw_readb(addr), data, h), addr);
+ (void)__raw_readb(addr); /* Defeat write posting */
local_irq_restore(flags);
}
@@ -120,6 +124,7 @@ static void modify_16(unsigned long addr, unsigned long h, unsigned long data)
unsigned long flags;
local_irq_save(flags);
__raw_writew(set_field(__raw_readw(addr), data, h), addr);
+ (void)__raw_readw(addr); /* Defeat write posting */
local_irq_restore(flags);
}
@@ -128,6 +133,7 @@ static void modify_32(unsigned long addr, unsigned long h, unsigned long data)
unsigned long flags;
local_irq_save(flags);
__raw_writel(set_field(__raw_readl(addr), data, h), addr);
+ (void)__raw_readl(addr); /* Defeat write posting */
local_irq_restore(flags);
}
@@ -657,16 +663,9 @@ static unsigned int __init save_reg(struct intc_desc_int *d,
return 0;
}
-static unsigned char *intc_evt2irq_table;
-
-unsigned int intc_evt2irq(unsigned int vector)
+static void intc_redirect_irq(unsigned int irq, struct irq_desc *desc)
{
- unsigned int irq = evt2irq(vector);
-
- if (intc_evt2irq_table && intc_evt2irq_table[irq])
- irq = intc_evt2irq_table[irq];
-
- return irq;
+ generic_handle_irq((unsigned int)get_irq_data(irq));
}
void __init register_intc_controller(struct intc_desc *desc)
@@ -739,50 +738,48 @@ void __init register_intc_controller(struct intc_desc *desc)
BUG_ON(k > 256); /* _INTC_ADDR_E() and _INTC_ADDR_D() are 8 bits */
- /* keep the first vector only if same enum is used multiple times */
+ /* register the vectors one by one */
for (i = 0; i < desc->nr_vectors; i++) {
struct intc_vect *vect = desc->vectors + i;
- int first_irq = evt2irq(vect->vect);
+ unsigned int irq = evt2irq(vect->vect);
+ struct irq_desc *irq_desc;
if (!vect->enum_id)
continue;
+ irq_desc = irq_to_desc_alloc_node(irq, numa_node_id());
+ if (unlikely(!irq_desc)) {
+ pr_info("can't get irq_desc for %d\n", irq);
+ continue;
+ }
+
+ intc_register_irq(desc, d, vect->enum_id, irq);
+
for (k = i + 1; k < desc->nr_vectors; k++) {
struct intc_vect *vect2 = desc->vectors + k;
+ unsigned int irq2 = evt2irq(vect2->vect);
if (vect->enum_id != vect2->enum_id)
continue;
- vect2->enum_id = 0;
-
- if (!intc_evt2irq_table)
- intc_evt2irq_table = kzalloc(NR_IRQS, GFP_NOWAIT);
-
- if (!intc_evt2irq_table) {
- pr_warning("intc: cannot allocate evt2irq!\n");
+ /*
+ * In the case of multi-evt handling and sparse
+ * IRQ support, each vector still needs to have
+ * its own backing irq_desc.
+ */
+ irq_desc = irq_to_desc_alloc_node(irq2, numa_node_id());
+ if (unlikely(!irq_desc)) {
+ pr_info("can't get irq_desc for %d\n", irq2);
continue;
}
- intc_evt2irq_table[evt2irq(vect2->vect)] = first_irq;
- }
- }
-
- /* register the vectors one by one */
- for (i = 0; i < desc->nr_vectors; i++) {
- struct intc_vect *vect = desc->vectors + i;
- unsigned int irq = evt2irq(vect->vect);
- struct irq_desc *irq_desc;
-
- if (!vect->enum_id)
- continue;
+ vect2->enum_id = 0;
- irq_desc = irq_to_desc_alloc_node(irq, numa_node_id());
- if (unlikely(!irq_desc)) {
- printk(KERN_INFO "can not get irq_desc for %d\n", irq);
- continue;
+ /* redirect this interrupts to the first one */
+ set_irq_chip_and_handler_name(irq2, &d->chip,
+ intc_redirect_irq, "redirect");
+ set_irq_data(irq2, (void *)irq);
}
-
- intc_register_irq(desc, d, vect->enum_id, irq);
}
}
diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c
index 3f06818cf9f..02347c57357 100644
--- a/drivers/uio/uio_pdrv_genirq.c
+++ b/drivers/uio/uio_pdrv_genirq.c
@@ -20,6 +20,7 @@
#include <linux/bitops.h>
#include <linux/interrupt.h>
#include <linux/stringify.h>
+#include <linux/pm_runtime.h>
#define DRIVER_NAME "uio_pdrv_genirq"
@@ -27,8 +28,27 @@ struct uio_pdrv_genirq_platdata {
struct uio_info *uioinfo;
spinlock_t lock;
unsigned long flags;
+ struct platform_device *pdev;
};
+static int uio_pdrv_genirq_open(struct uio_info *info, struct inode *inode)
+{
+ struct uio_pdrv_genirq_platdata *priv = info->priv;
+
+ /* Wait until the Runtime PM code has woken up the device */
+ pm_runtime_get_sync(&priv->pdev->dev);
+ return 0;
+}
+
+static int uio_pdrv_genirq_release(struct uio_info *info, struct inode *inode)
+{
+ struct uio_pdrv_genirq_platdata *priv = info->priv;
+
+ /* Tell the Runtime PM code that the device has become idle */
+ pm_runtime_put_sync(&priv->pdev->dev);
+ return 0;
+}
+
static irqreturn_t uio_pdrv_genirq_handler(int irq, struct uio_info *dev_info)
{
struct uio_pdrv_genirq_platdata *priv = dev_info->priv;
@@ -97,6 +117,7 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev)
priv->uioinfo = uioinfo;
spin_lock_init(&priv->lock);
priv->flags = 0; /* interrupt is enabled to begin with */
+ priv->pdev = pdev;
uiomem = &uioinfo->mem[0];
@@ -136,8 +157,17 @@ static int uio_pdrv_genirq_probe(struct platform_device *pdev)
uioinfo->irq_flags |= IRQF_DISABLED;
uioinfo->handler = uio_pdrv_genirq_handler;
uioinfo->irqcontrol = uio_pdrv_genirq_irqcontrol;
+ uioinfo->open = uio_pdrv_genirq_open;
+ uioinfo->release = uio_pdrv_genirq_release;
uioinfo->priv = priv;
+ /* Enable Runtime PM for this device:
+ * The device starts in suspended state to allow the hardware to be
+ * turned off by default. The Runtime PM bus code should power on the
+ * hardware and enable clocks at open().
+ */
+ pm_runtime_enable(&pdev->dev);
+
ret = uio_register_device(&pdev->dev, priv->uioinfo);
if (ret) {
dev_err(&pdev->dev, "unable to register uio device\n");
@@ -157,16 +187,40 @@ static int uio_pdrv_genirq_remove(struct platform_device *pdev)
struct uio_pdrv_genirq_platdata *priv = platform_get_drvdata(pdev);
uio_unregister_device(priv->uioinfo);
+ pm_runtime_disable(&pdev->dev);
kfree(priv);
return 0;
}
+static int uio_pdrv_genirq_runtime_nop(struct device *dev)
+{
+ /* Runtime PM callback shared between ->runtime_suspend()
+ * and ->runtime_resume(). Simply returns success.
+ *
+ * In this driver pm_runtime_get_sync() and pm_runtime_put_sync()
+ * are used at open() and release() time. This allows the
+ * Runtime PM code to turn off power to the device while the
+ * device is unused, ie before open() and after release().
+ *
+ * This Runtime PM callback does not need to save or restore
+ * any registers since user space is responsbile for hardware
+ * register reinitialization after open().
+ */
+ return 0;
+}
+
+static struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = {
+ .runtime_suspend = uio_pdrv_genirq_runtime_nop,
+ .runtime_resume = uio_pdrv_genirq_runtime_nop,
+};
+
static struct platform_driver uio_pdrv_genirq = {
.probe = uio_pdrv_genirq_probe,
.remove = uio_pdrv_genirq_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
+ .pm = &uio_pdrv_genirq_dev_pm_ops,
},
};
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 7f8e83a954a..9f986b417c5 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -251,6 +251,24 @@ config USB_PXA25X_SMALL
default y if USB_ETH
default y if USB_G_SERIAL
+config USB_GADGET_R8A66597
+ boolean "Renesas R8A66597 USB Peripheral Controller"
+ select USB_GADGET_DUALSPEED
+ help
+ R8A66597 is a discrete USB host and peripheral controller chip that
+ supports both full and high speed USB 2.0 data transfers.
+ It has nine configurable endpoints, and endpoint zero.
+
+ Say "y" to link the driver statically, or "m" to build a
+ dynamically linked module called "r8a66597_udc" and force all
+ gadget drivers to also be dynamically linked.
+
+config USB_R8A66597
+ tristate
+ depends on USB_GADGET_R8A66597
+ default USB_GADGET
+ select USB_GADGET_SELECTED
+
config USB_GADGET_PXA27X
boolean "PXA 27x"
depends on ARCH_PXA && (PXA27x || PXA3xx)
@@ -360,16 +378,6 @@ config USB_M66592
default USB_GADGET
select USB_GADGET_SELECTED
-config SUPERH_BUILT_IN_M66592
- boolean "Enable SuperH built-in USB like the M66592"
- depends on USB_GADGET_M66592 && CPU_SUBTYPE_SH7722
- help
- SH7722 has USB like the M66592.
-
- The transfer rate is very slow when use "Ethernet Gadget".
- However, this problem is improved if change a value of
- NET_IP_ALIGN to 4.
-
#
# Controllers available only in discrete form (and all PCI controllers)
#
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index e6017e6bf6d..9d7b87c52e9 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -23,6 +23,7 @@ ifeq ($(CONFIG_ARCH_MXC),y)
fsl_usb2_udc-objs += fsl_mx3_udc.o
endif
obj-$(CONFIG_USB_M66592) += m66592-udc.o
+obj-$(CONFIG_USB_R8A66597) += r8a66597-udc.o
obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o
obj-$(CONFIG_USB_CI13XXX) += ci13xxx_udc.o
obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.o
diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h
index 8e0e9a0b736..f2d270b202f 100644
--- a/drivers/usb/gadget/gadget_chips.h
+++ b/drivers/usb/gadget/gadget_chips.h
@@ -173,6 +173,12 @@
// CONFIG_USB_GADGET_AU1X00
// ...
+#ifdef CONFIG_USB_GADGET_R8A66597
+#define gadget_is_r8a66597(g) !strcmp("r8a66597_udc", (g)->name)
+#else
+#define gadget_is_r8a66597(g) 0
+#endif
+
/**
* usb_gadget_controller_number - support bcdDevice id convention
@@ -239,6 +245,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget)
return 0x23;
else if (gadget_is_langwell(gadget))
return 0x24;
+ else if (gadget_is_r8a66597(gadget))
+ return 0x25;
return -ENOENT;
}
diff --git a/drivers/usb/gadget/m66592-udc.c b/drivers/usb/gadget/m66592-udc.c
index 43dcf9e1af6..a8c8543d1b0 100644
--- a/drivers/usb/gadget/m66592-udc.c
+++ b/drivers/usb/gadget/m66592-udc.c
@@ -25,44 +25,18 @@
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/platform_device.h>
-
+#include <linux/err.h>
#include <linux/usb/ch9.h>
#include <linux/usb/gadget.h>
#include "m66592-udc.h"
-
MODULE_DESCRIPTION("M66592 USB gadget driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yoshihiro Shimoda");
MODULE_ALIAS("platform:m66592_udc");
-#define DRIVER_VERSION "18 Oct 2007"
-
-/* module parameters */
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592)
-static unsigned short endian = M66592_LITTLE;
-module_param(endian, ushort, 0644);
-MODULE_PARM_DESC(endian, "data endian: big=0, little=0 (default=0)");
-#else
-static unsigned short clock = M66592_XTAL24;
-module_param(clock, ushort, 0644);
-MODULE_PARM_DESC(clock, "input clock: 48MHz=32768, 24MHz=16384, 12MHz=0 "
- "(default=16384)");
-
-static unsigned short vif = M66592_LDRV;
-module_param(vif, ushort, 0644);
-MODULE_PARM_DESC(vif, "input VIF: 3.3V=32768, 1.5V=0 (default=32768)");
-
-static unsigned short endian;
-module_param(endian, ushort, 0644);
-MODULE_PARM_DESC(endian, "data endian: big=256, little=0 (default=0)");
-
-static unsigned short irq_sense = M66592_INTL;
-module_param(irq_sense, ushort, 0644);
-MODULE_PARM_DESC(irq_sense, "IRQ sense: low level=2, falling edge=0 "
- "(default=2)");
-#endif
+#define DRIVER_VERSION "21 July 2009"
static const char udc_name[] = "m66592_udc";
static const char *m66592_ep_name[] = {
@@ -244,6 +218,7 @@ static inline int get_buffer_size(struct m66592 *m66592, u16 pipenum)
static inline void pipe_change(struct m66592 *m66592, u16 pipenum)
{
struct m66592_ep *ep = m66592->pipenum2ep[pipenum];
+ unsigned short mbw;
if (ep->use_dma)
return;
@@ -252,7 +227,12 @@ static inline void pipe_change(struct m66592 *m66592, u16 pipenum)
ndelay(450);
- m66592_bset(m66592, M66592_MBW, ep->fifosel);
+ if (m66592->pdata->on_chip)
+ mbw = M66592_MBW_32;
+ else
+ mbw = M66592_MBW_16;
+
+ m66592_bset(m66592, mbw, ep->fifosel);
}
static int pipe_buffer_setting(struct m66592 *m66592,
@@ -276,24 +256,27 @@ static int pipe_buffer_setting(struct m66592 *m66592,
buf_bsize = 0;
break;
case M66592_BULK:
- bufnum = m66592->bi_bufnum +
- (info->pipe - M66592_BASE_PIPENUM_BULK) * 16;
- m66592->bi_bufnum += 16;
+ /* isochronous pipes may be used as bulk pipes */
+ if (info->pipe > M66592_BASE_PIPENUM_BULK)
+ bufnum = info->pipe - M66592_BASE_PIPENUM_BULK;
+ else
+ bufnum = info->pipe - M66592_BASE_PIPENUM_ISOC;
+
+ bufnum = M66592_BASE_BUFNUM + (bufnum * 16);
buf_bsize = 7;
pipecfg |= M66592_DBLB;
if (!info->dir_in)
pipecfg |= M66592_SHTNAK;
break;
case M66592_ISO:
- bufnum = m66592->bi_bufnum +
+ bufnum = M66592_BASE_BUFNUM +
(info->pipe - M66592_BASE_PIPENUM_ISOC) * 16;
- m66592->bi_bufnum += 16;
buf_bsize = 7;
break;
}
- if (m66592->bi_bufnum > M66592_MAX_BUFNUM) {
- pr_err("m66592 pipe memory is insufficient(%d)\n",
- m66592->bi_bufnum);
+
+ if (buf_bsize && ((bufnum + 16) >= M66592_MAX_BUFNUM)) {
+ pr_err("m66592 pipe memory is insufficient\n");
return -ENOMEM;
}
@@ -313,17 +296,6 @@ static void pipe_buffer_release(struct m66592 *m66592,
if (info->pipe == 0)
return;
- switch (info->type) {
- case M66592_BULK:
- if (is_bulk_pipe(info->pipe))
- m66592->bi_bufnum -= 16;
- break;
- case M66592_ISO:
- if (is_isoc_pipe(info->pipe))
- m66592->bi_bufnum -= 16;
- break;
- }
-
if (is_bulk_pipe(info->pipe)) {
m66592->bulk--;
} else if (is_interrupt_pipe(info->pipe))
@@ -340,6 +312,7 @@ static void pipe_buffer_release(struct m66592 *m66592,
static void pipe_initialize(struct m66592_ep *ep)
{
struct m66592 *m66592 = ep->m66592;
+ unsigned short mbw;
m66592_mdfy(m66592, 0, M66592_CURPIPE, ep->fifosel);
@@ -351,7 +324,12 @@ static void pipe_initialize(struct m66592_ep *ep)
ndelay(450);
- m66592_bset(m66592, M66592_MBW, ep->fifosel);
+ if (m66592->pdata->on_chip)
+ mbw = M66592_MBW_32;
+ else
+ mbw = M66592_MBW_16;
+
+ m66592_bset(m66592, mbw, ep->fifosel);
}
}
@@ -367,15 +345,13 @@ static void m66592_ep_setting(struct m66592 *m66592, struct m66592_ep *ep,
ep->fifosel = M66592_D0FIFOSEL;
ep->fifoctr = M66592_D0FIFOCTR;
ep->fifotrn = M66592_D0FIFOTRN;
-#if !defined(CONFIG_SUPERH_BUILT_IN_M66592)
- } else if (m66592->num_dma == 1) {
+ } else if (!m66592->pdata->on_chip && m66592->num_dma == 1) {
m66592->num_dma++;
ep->use_dma = 1;
ep->fifoaddr = M66592_D1FIFO;
ep->fifosel = M66592_D1FIFOSEL;
ep->fifoctr = M66592_D1FIFOCTR;
ep->fifotrn = M66592_D1FIFOTRN;
-#endif
} else {
ep->use_dma = 0;
ep->fifoaddr = M66592_CFIFO;
@@ -620,76 +596,120 @@ static void start_ep0(struct m66592_ep *ep, struct m66592_request *req)
}
}
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592)
static void init_controller(struct m66592 *m66592)
{
- m66592_bset(m66592, M66592_HSE, M66592_SYSCFG); /* High spd */
- m66592_bclr(m66592, M66592_USBE, M66592_SYSCFG);
- m66592_bclr(m66592, M66592_DPRPU, M66592_SYSCFG);
- m66592_bset(m66592, M66592_USBE, M66592_SYSCFG);
+ unsigned int endian;
- /* This is a workaound for SH7722 2nd cut */
- m66592_bset(m66592, 0x8000, M66592_DVSTCTR);
- m66592_bset(m66592, 0x1000, M66592_TESTMODE);
- m66592_bclr(m66592, 0x8000, M66592_DVSTCTR);
+ if (m66592->pdata->on_chip) {
+ if (m66592->pdata->endian)
+ endian = 0; /* big endian */
+ else
+ endian = M66592_LITTLE; /* little endian */
- m66592_bset(m66592, M66592_INTL, M66592_INTENB1);
+ m66592_bset(m66592, M66592_HSE, M66592_SYSCFG); /* High spd */
+ m66592_bclr(m66592, M66592_USBE, M66592_SYSCFG);
+ m66592_bclr(m66592, M66592_DPRPU, M66592_SYSCFG);
+ m66592_bset(m66592, M66592_USBE, M66592_SYSCFG);
- m66592_write(m66592, 0, M66592_CFBCFG);
- m66592_write(m66592, 0, M66592_D0FBCFG);
- m66592_bset(m66592, endian, M66592_CFBCFG);
- m66592_bset(m66592, endian, M66592_D0FBCFG);
-}
-#else /* #if defined(CONFIG_SUPERH_BUILT_IN_M66592) */
-static void init_controller(struct m66592 *m66592)
-{
- m66592_bset(m66592, (vif & M66592_LDRV) | (endian & M66592_BIGEND),
- M66592_PINCFG);
- m66592_bset(m66592, M66592_HSE, M66592_SYSCFG); /* High spd */
- m66592_mdfy(m66592, clock & M66592_XTAL, M66592_XTAL, M66592_SYSCFG);
+ /* This is a workaound for SH7722 2nd cut */
+ m66592_bset(m66592, 0x8000, M66592_DVSTCTR);
+ m66592_bset(m66592, 0x1000, M66592_TESTMODE);
+ m66592_bclr(m66592, 0x8000, M66592_DVSTCTR);
- m66592_bclr(m66592, M66592_USBE, M66592_SYSCFG);
- m66592_bclr(m66592, M66592_DPRPU, M66592_SYSCFG);
- m66592_bset(m66592, M66592_USBE, M66592_SYSCFG);
+ m66592_bset(m66592, M66592_INTL, M66592_INTENB1);
+
+ m66592_write(m66592, 0, M66592_CFBCFG);
+ m66592_write(m66592, 0, M66592_D0FBCFG);
+ m66592_bset(m66592, endian, M66592_CFBCFG);
+ m66592_bset(m66592, endian, M66592_D0FBCFG);
+ } else {
+ unsigned int clock, vif, irq_sense;
+
+ if (m66592->pdata->endian)
+ endian = M66592_BIGEND; /* big endian */
+ else
+ endian = 0; /* little endian */
+
+ if (m66592->pdata->vif)
+ vif = M66592_LDRV; /* 3.3v */
+ else
+ vif = 0; /* 1.5v */
+
+ switch (m66592->pdata->xtal) {
+ case M66592_PLATDATA_XTAL_12MHZ:
+ clock = M66592_XTAL12;
+ break;
+ case M66592_PLATDATA_XTAL_24MHZ:
+ clock = M66592_XTAL24;
+ break;
+ case M66592_PLATDATA_XTAL_48MHZ:
+ clock = M66592_XTAL48;
+ break;
+ default:
+ pr_warning("m66592-udc: xtal configuration error\n");
+ clock = 0;
+ }
+
+ switch (m66592->irq_trigger) {
+ case IRQF_TRIGGER_LOW:
+ irq_sense = M66592_INTL;
+ break;
+ case IRQF_TRIGGER_FALLING:
+ irq_sense = 0;
+ break;
+ default:
+ pr_warning("m66592-udc: irq trigger config error\n");
+ irq_sense = 0;
+ }
- m66592_bset(m66592, M66592_XCKE, M66592_SYSCFG);
+ m66592_bset(m66592,
+ (vif & M66592_LDRV) | (endian & M66592_BIGEND),
+ M66592_PINCFG);
+ m66592_bset(m66592, M66592_HSE, M66592_SYSCFG); /* High spd */
+ m66592_mdfy(m66592, clock & M66592_XTAL, M66592_XTAL,
+ M66592_SYSCFG);
+ m66592_bclr(m66592, M66592_USBE, M66592_SYSCFG);
+ m66592_bclr(m66592, M66592_DPRPU, M66592_SYSCFG);
+ m66592_bset(m66592, M66592_USBE, M66592_SYSCFG);
+
+ m66592_bset(m66592, M66592_XCKE, M66592_SYSCFG);
- msleep(3);
+ msleep(3);
- m66592_bset(m66592, M66592_RCKE | M66592_PLLC, M66592_SYSCFG);
+ m66592_bset(m66592, M66592_RCKE | M66592_PLLC, M66592_SYSCFG);
- msleep(1);
+ msleep(1);
- m66592_bset(m66592, M66592_SCKE, M66592_SYSCFG);
+ m66592_bset(m66592, M66592_SCKE, M66592_SYSCFG);
- m66592_bset(m66592, irq_sense & M66592_INTL, M66592_INTENB1);
- m66592_write(m66592, M66592_BURST | M66592_CPU_ADR_RD_WR,
- M66592_DMA0CFG);
+ m66592_bset(m66592, irq_sense & M66592_INTL, M66592_INTENB1);
+ m66592_write(m66592, M66592_BURST | M66592_CPU_ADR_RD_WR,
+ M66592_DMA0CFG);
+ }
}
-#endif /* #if defined(CONFIG_SUPERH_BUILT_IN_M66592) */
static void disable_controller(struct m66592 *m66592)
{
-#if !defined(CONFIG_SUPERH_BUILT_IN_M66592)
- m66592_bclr(m66592, M66592_SCKE, M66592_SYSCFG);
- udelay(1);
- m66592_bclr(m66592, M66592_PLLC, M66592_SYSCFG);
- udelay(1);
- m66592_bclr(m66592, M66592_RCKE, M66592_SYSCFG);
- udelay(1);
- m66592_bclr(m66592, M66592_XCKE, M66592_SYSCFG);
-#endif
+ if (!m66592->pdata->on_chip) {
+ m66592_bclr(m66592, M66592_SCKE, M66592_SYSCFG);
+ udelay(1);
+ m66592_bclr(m66592, M66592_PLLC, M66592_SYSCFG);
+ udelay(1);
+ m66592_bclr(m66592, M66592_RCKE, M66592_SYSCFG);
+ udelay(1);
+ m66592_bclr(m66592, M66592_XCKE, M66592_SYSCFG);
+ }
}
static void m66592_start_xclock(struct m66592 *m66592)
{
-#if !defined(CONFIG_SUPERH_BUILT_IN_M66592)
u16 tmp;
- tmp = m66592_read(m66592, M66592_SYSCFG);
- if (!(tmp & M66592_XCKE))
- m66592_bset(m66592, M66592_XCKE, M66592_SYSCFG);
-#endif
+ if (!m66592->pdata->on_chip) {
+ tmp = m66592_read(m66592, M66592_SYSCFG);
+ if (!(tmp & M66592_XCKE))
+ m66592_bset(m66592, M66592_XCKE, M66592_SYSCFG);
+ }
}
/*-------------------------------------------------------------------------*/
@@ -1177,8 +1197,7 @@ static irqreturn_t m66592_irq(int irq, void *_m66592)
intsts0 = m66592_read(m66592, M66592_INTSTS0);
intenb0 = m66592_read(m66592, M66592_INTENB0);
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592)
- if (!intsts0 && !intenb0) {
+ if (m66592->pdata->on_chip && !intsts0 && !intenb0) {
/*
* When USB clock stops, it cannot read register. Even if a
* clock stops, the interrupt occurs. So this driver turn on
@@ -1188,7 +1207,6 @@ static irqreturn_t m66592_irq(int irq, void *_m66592)
intsts0 = m66592_read(m66592, M66592_INTSTS0);
intenb0 = m66592_read(m66592, M66592_INTENB0);
}
-#endif
savepipe = m66592_read(m66592, M66592_CFIFOSEL);
@@ -1534,9 +1552,11 @@ static int __exit m66592_remove(struct platform_device *pdev)
iounmap(m66592->reg);
free_irq(platform_get_irq(pdev, 0), m66592);
m66592_free_request(&m66592->ep[0].ep, m66592->ep0_req);
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592) && defined(CONFIG_HAVE_CLK)
- clk_disable(m66592->clk);
- clk_put(m66592->clk);
+#ifdef CONFIG_HAVE_CLK
+ if (m66592->pdata->on_chip) {
+ clk_disable(m66592->clk);
+ clk_put(m66592->clk);
+ }
#endif
kfree(m66592);
return 0;
@@ -1548,11 +1568,10 @@ static void nop_completion(struct usb_ep *ep, struct usb_request *r)
static int __init m66592_probe(struct platform_device *pdev)
{
- struct resource *res;
- int irq;
+ struct resource *res, *ires;
void __iomem *reg = NULL;
struct m66592 *m66592 = NULL;
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592) && defined(CONFIG_HAVE_CLK)
+#ifdef CONFIG_HAVE_CLK
char clk_name[8];
#endif
int ret = 0;
@@ -1565,10 +1584,11 @@ static int __init m66592_probe(struct platform_device *pdev)
goto clean_up;
}
- irq = platform_get_irq(pdev, 0);
- if (irq < 0) {
+ ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!ires) {
ret = -ENODEV;
- pr_err("platform_get_irq error.\n");
+ dev_err(&pdev->dev,
+ "platform_get_resource IORESOURCE_IRQ error.\n");
goto clean_up;
}
@@ -1579,6 +1599,12 @@ static int __init m66592_probe(struct platform_device *pdev)
goto clean_up;
}
+ if (pdev->dev.platform_data == NULL) {
+ dev_err(&pdev->dev, "no platform data\n");
+ ret = -ENODEV;
+ goto clean_up;
+ }
+
/* initialize ucd */
m66592 = kzalloc(sizeof(struct m66592), GFP_KERNEL);
if (m66592 == NULL) {
@@ -1586,6 +1612,9 @@ static int __init m66592_probe(struct platform_device *pdev)
goto clean_up;
}
+ m66592->pdata = pdev->dev.platform_data;
+ m66592->irq_trigger = ires->flags & IRQF_TRIGGER_MASK;
+
spin_lock_init(&m66592->lock);
dev_set_drvdata(&pdev->dev, m66592);
@@ -1603,24 +1632,25 @@ static int __init m66592_probe(struct platform_device *pdev)
m66592->timer.data = (unsigned long)m66592;
m66592->reg = reg;
- m66592->bi_bufnum = M66592_BASE_BUFNUM;
-
- ret = request_irq(irq, m66592_irq, IRQF_DISABLED | IRQF_SHARED,
+ ret = request_irq(ires->start, m66592_irq, IRQF_DISABLED | IRQF_SHARED,
udc_name, m66592);
if (ret < 0) {
pr_err("request_irq error (%d)\n", ret);
goto clean_up;
}
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592) && defined(CONFIG_HAVE_CLK)
- snprintf(clk_name, sizeof(clk_name), "usbf%d", pdev->id);
- m66592->clk = clk_get(&pdev->dev, clk_name);
- if (IS_ERR(m66592->clk)) {
- dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name);
- ret = PTR_ERR(m66592->clk);
- goto clean_up2;
+#ifdef CONFIG_HAVE_CLK
+ if (m66592->pdata->on_chip) {
+ snprintf(clk_name, sizeof(clk_name), "usbf%d", pdev->id);
+ m66592->clk = clk_get(&pdev->dev, clk_name);
+ if (IS_ERR(m66592->clk)) {
+ dev_err(&pdev->dev, "cannot get clock \"%s\"\n",
+ clk_name);
+ ret = PTR_ERR(m66592->clk);
+ goto clean_up2;
+ }
+ clk_enable(m66592->clk);
}
- clk_enable(m66592->clk);
#endif
INIT_LIST_HEAD(&m66592->gadget.ep_list);
m66592->gadget.ep0 = &m66592->ep[0].ep;
@@ -1662,12 +1692,14 @@ static int __init m66592_probe(struct platform_device *pdev)
return 0;
clean_up3:
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592) && defined(CONFIG_HAVE_CLK)
- clk_disable(m66592->clk);
- clk_put(m66592->clk);
+#ifdef CONFIG_HAVE_CLK
+ if (m66592->pdata->on_chip) {
+ clk_disable(m66592->clk);
+ clk_put(m66592->clk);
+ }
clean_up2:
#endif
- free_irq(irq, m66592);
+ free_irq(ires->start, m66592);
clean_up:
if (m66592) {
if (m66592->ep0_req)
diff --git a/drivers/usb/gadget/m66592-udc.h b/drivers/usb/gadget/m66592-udc.h
index 286ce07e796..8b960deed68 100644
--- a/drivers/usb/gadget/m66592-udc.h
+++ b/drivers/usb/gadget/m66592-udc.h
@@ -23,10 +23,12 @@
#ifndef __M66592_UDC_H__
#define __M66592_UDC_H__
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592) && defined(CONFIG_HAVE_CLK)
+#ifdef CONFIG_HAVE_CLK
#include <linux/clk.h>
#endif
+#include <linux/usb/m66592.h>
+
#define M66592_SYSCFG 0x00
#define M66592_XTAL 0xC000 /* b15-14: Crystal selection */
#define M66592_XTAL48 0x8000 /* 48MHz */
@@ -76,11 +78,11 @@
#define M66592_P_TST_J 0x0001 /* PERI TEST J */
#define M66592_P_TST_NORMAL 0x0000 /* PERI Normal Mode */
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592)
+/* built-in registers */
#define M66592_CFBCFG 0x0A
#define M66592_D0FBCFG 0x0C
#define M66592_LITTLE 0x0100 /* b8: Little endian mode */
-#else
+/* external chip case */
#define M66592_PINCFG 0x0A
#define M66592_LDRV 0x8000 /* b15: Drive Current Adjust */
#define M66592_BIGEND 0x0100 /* b8: Big endian mode */
@@ -100,8 +102,8 @@
#define M66592_PKTM 0x0020 /* b5: Packet mode */
#define M66592_DENDE 0x0010 /* b4: Dend enable */
#define M66592_OBUS 0x0004 /* b2: OUTbus mode */
-#endif /* #if defined(CONFIG_SUPERH_BUILT_IN_M66592) */
+/* common case */
#define M66592_CFIFO 0x10
#define M66592_D0FIFO 0x14
#define M66592_D1FIFO 0x18
@@ -113,13 +115,9 @@
#define M66592_REW 0x4000 /* b14: Buffer rewind */
#define M66592_DCLRM 0x2000 /* b13: DMA buffer clear mode */
#define M66592_DREQE 0x1000 /* b12: DREQ output enable */
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592)
-#define M66592_MBW 0x0800 /* b11: Maximum bit width for FIFO */
-#else
-#define M66592_MBW 0x0400 /* b10: Maximum bit width for FIFO */
-#define M66592_MBW_8 0x0000 /* 8bit */
-#define M66592_MBW_16 0x0400 /* 16bit */
-#endif /* #if defined(CONFIG_SUPERH_BUILT_IN_M66592) */
+#define M66592_MBW_8 0x0000 /* 8bit */
+#define M66592_MBW_16 0x0400 /* 16bit */
+#define M66592_MBW_32 0x0800 /* 32bit */
#define M66592_TRENB 0x0200 /* b9: Transaction counter enable */
#define M66592_TRCLR 0x0100 /* b8: Transaction counter clear */
#define M66592_DEZPM 0x0080 /* b7: Zero-length packet mode */
@@ -480,9 +478,11 @@ struct m66592_ep {
struct m66592 {
spinlock_t lock;
void __iomem *reg;
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592) && defined(CONFIG_HAVE_CLK)
+#ifdef CONFIG_HAVE_CLK
struct clk *clk;
#endif
+ struct m66592_platdata *pdata;
+ unsigned long irq_trigger;
struct usb_gadget gadget;
struct usb_gadget_driver *driver;
@@ -506,7 +506,6 @@ struct m66592 {
int interrupt;
int isochronous;
int num_dma;
- int bi_bufnum; /* bulk and isochronous's bufnum */
};
#define gadget_to_m66592(_gadget) container_of(_gadget, struct m66592, gadget)
@@ -547,13 +546,13 @@ static inline void m66592_read_fifo(struct m66592 *m66592,
{
unsigned long fifoaddr = (unsigned long)m66592->reg + offset;
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592)
- len = (len + 3) / 4;
- insl(fifoaddr, buf, len);
-#else
- len = (len + 1) / 2;
- insw(fifoaddr, buf, len);
-#endif
+ if (m66592->pdata->on_chip) {
+ len = (len + 3) / 4;
+ insl(fifoaddr, buf, len);
+ } else {
+ len = (len + 1) / 2;
+ insw(fifoaddr, buf, len);
+ }
}
static inline void m66592_write(struct m66592 *m66592, u16 val,
@@ -567,33 +566,34 @@ static inline void m66592_write_fifo(struct m66592 *m66592,
void *buf, unsigned long len)
{
unsigned long fifoaddr = (unsigned long)m66592->reg + offset;
-#if defined(CONFIG_SUPERH_BUILT_IN_M66592)
- unsigned long count;
- unsigned char *pb;
- int i;
-
- count = len / 4;
- outsl(fifoaddr, buf, count);
-
- if (len & 0x00000003) {
- pb = buf + count * 4;
- for (i = 0; i < (len & 0x00000003); i++) {
- if (m66592_read(m66592, M66592_CFBCFG)) /* little */
- outb(pb[i], fifoaddr + (3 - i));
- else
- outb(pb[i], fifoaddr + i);
+
+ if (m66592->pdata->on_chip) {
+ unsigned long count;
+ unsigned char *pb;
+ int i;
+
+ count = len / 4;
+ outsl(fifoaddr, buf, count);
+
+ if (len & 0x00000003) {
+ pb = buf + count * 4;
+ for (i = 0; i < (len & 0x00000003); i++) {
+ if (m66592_read(m66592, M66592_CFBCFG)) /* le */
+ outb(pb[i], fifoaddr + (3 - i));
+ else
+ outb(pb[i], fifoaddr + i);
+ }
+ }
+ } else {
+ unsigned long odd = len & 0x0001;
+
+ len = len / 2;
+ outsw(fifoaddr, buf, len);
+ if (odd) {
+ unsigned char *p = buf + len*2;
+ outb(*p, fifoaddr);
}
}
-#else
- unsigned long odd = len & 0x0001;
-
- len = len / 2;
- outsw(fifoaddr, buf, len);
- if (odd) {
- unsigned char *p = buf + len*2;
- outb(*p, fifoaddr);
- }
-#endif /* #if defined(CONFIG_SUPERH_BUILT_IN_M66592) */
}
static inline void m66592_mdfy(struct m66592 *m66592, u16 val, u16 pat,
diff --git a/drivers/usb/gadget/r8a66597-udc.c b/drivers/usb/gadget/r8a66597-udc.c
new file mode 100644
index 00000000000..e220fb8091a
--- /dev/null
+++ b/drivers/usb/gadget/r8a66597-udc.c
@@ -0,0 +1,1689 @@
+/*
+ * R8A66597 UDC (USB gadget)
+ *
+ * Copyright (C) 2006-2009 Renesas Solutions Corp.
+ *
+ * Author : Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com>
+ *
+ * This program 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; version 2 of the License.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include "r8a66597-udc.h"
+
+#define DRIVER_VERSION "2009-08-18"
+
+static const char udc_name[] = "r8a66597_udc";
+static const char *r8a66597_ep_name[] = {
+ "ep0", "ep1", "ep2", "ep3", "ep4", "ep5", "ep6", "ep7",
+ "ep8", "ep9",
+};
+
+static void disable_controller(struct r8a66597 *r8a66597);
+static void irq_ep0_write(struct r8a66597_ep *ep, struct r8a66597_request *req);
+static void irq_packet_write(struct r8a66597_ep *ep,
+ struct r8a66597_request *req);
+static int r8a66597_queue(struct usb_ep *_ep, struct usb_request *_req,
+ gfp_t gfp_flags);
+
+static void transfer_complete(struct r8a66597_ep *ep,
+ struct r8a66597_request *req, int status);
+
+/*-------------------------------------------------------------------------*/
+static inline u16 get_usb_speed(struct r8a66597 *r8a66597)
+{
+ return r8a66597_read(r8a66597, DVSTCTR0) & RHST;
+}
+
+static void enable_pipe_irq(struct r8a66597 *r8a66597, u16 pipenum,
+ unsigned long reg)
+{
+ u16 tmp;
+
+ tmp = r8a66597_read(r8a66597, INTENB0);
+ r8a66597_bclr(r8a66597, BEMPE | NRDYE | BRDYE,
+ INTENB0);
+ r8a66597_bset(r8a66597, (1 << pipenum), reg);
+ r8a66597_write(r8a66597, tmp, INTENB0);
+}
+
+static void disable_pipe_irq(struct r8a66597 *r8a66597, u16 pipenum,
+ unsigned long reg)
+{
+ u16 tmp;
+
+ tmp = r8a66597_read(r8a66597, INTENB0);
+ r8a66597_bclr(r8a66597, BEMPE | NRDYE | BRDYE,
+ INTENB0);
+ r8a66597_bclr(r8a66597, (1 << pipenum), reg);
+ r8a66597_write(r8a66597, tmp, INTENB0);
+}
+
+static void r8a66597_usb_connect(struct r8a66597 *r8a66597)
+{
+ r8a66597_bset(r8a66597, CTRE, INTENB0);
+ r8a66597_bset(r8a66597, BEMPE | BRDYE, INTENB0);
+
+ r8a66597_bset(r8a66597, DPRPU, SYSCFG0);
+}
+
+static void r8a66597_usb_disconnect(struct r8a66597 *r8a66597)
+__releases(r8a66597->lock)
+__acquires(r8a66597->lock)
+{
+ r8a66597_bclr(r8a66597, CTRE, INTENB0);
+ r8a66597_bclr(r8a66597, BEMPE | BRDYE, INTENB0);
+ r8a66597_bclr(r8a66597, DPRPU, SYSCFG0);
+
+ r8a66597->gadget.speed = USB_SPEED_UNKNOWN;
+ spin_unlock(&r8a66597->lock);
+ r8a66597->driver->disconnect(&r8a66597->gadget);
+ spin_lock(&r8a66597->lock);
+
+ disable_controller(r8a66597);
+ INIT_LIST_HEAD(&r8a66597->ep[0].queue);
+}
+
+static inline u16 control_reg_get_pid(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ u16 pid = 0;
+ unsigned long offset;
+
+ if (pipenum == 0)
+ pid = r8a66597_read(r8a66597, DCPCTR) & PID;
+ else if (pipenum < R8A66597_MAX_NUM_PIPE) {
+ offset = get_pipectr_addr(pipenum);
+ pid = r8a66597_read(r8a66597, offset) & PID;
+ } else
+ printk(KERN_ERR "unexpect pipe num (%d)\n", pipenum);
+
+ return pid;
+}
+
+static inline void control_reg_set_pid(struct r8a66597 *r8a66597, u16 pipenum,
+ u16 pid)
+{
+ unsigned long offset;
+
+ if (pipenum == 0)
+ r8a66597_mdfy(r8a66597, pid, PID, DCPCTR);
+ else if (pipenum < R8A66597_MAX_NUM_PIPE) {
+ offset = get_pipectr_addr(pipenum);
+ r8a66597_mdfy(r8a66597, pid, PID, offset);
+ } else
+ printk(KERN_ERR "unexpect pipe num (%d)\n", pipenum);
+}
+
+static inline void pipe_start(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ control_reg_set_pid(r8a66597, pipenum, PID_BUF);
+}
+
+static inline void pipe_stop(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ control_reg_set_pid(r8a66597, pipenum, PID_NAK);
+}
+
+static inline void pipe_stall(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ control_reg_set_pid(r8a66597, pipenum, PID_STALL);
+}
+
+static inline u16 control_reg_get(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ u16 ret = 0;
+ unsigned long offset;
+
+ if (pipenum == 0)
+ ret = r8a66597_read(r8a66597, DCPCTR);
+ else if (pipenum < R8A66597_MAX_NUM_PIPE) {
+ offset = get_pipectr_addr(pipenum);
+ ret = r8a66597_read(r8a66597, offset);
+ } else
+ printk(KERN_ERR "unexpect pipe num (%d)\n", pipenum);
+
+ return ret;
+}
+
+static inline void control_reg_sqclr(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ unsigned long offset;
+
+ pipe_stop(r8a66597, pipenum);
+
+ if (pipenum == 0)
+ r8a66597_bset(r8a66597, SQCLR, DCPCTR);
+ else if (pipenum < R8A66597_MAX_NUM_PIPE) {
+ offset = get_pipectr_addr(pipenum);
+ r8a66597_bset(r8a66597, SQCLR, offset);
+ } else
+ printk(KERN_ERR "unexpect pipe num(%d)\n", pipenum);
+}
+
+static inline int get_buffer_size(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ u16 tmp;
+ int size;
+
+ if (pipenum == 0) {
+ tmp = r8a66597_read(r8a66597, DCPCFG);
+ if ((tmp & R8A66597_CNTMD) != 0)
+ size = 256;
+ else {
+ tmp = r8a66597_read(r8a66597, DCPMAXP);
+ size = tmp & MAXP;
+ }
+ } else {
+ r8a66597_write(r8a66597, pipenum, PIPESEL);
+ tmp = r8a66597_read(r8a66597, PIPECFG);
+ if ((tmp & R8A66597_CNTMD) != 0) {
+ tmp = r8a66597_read(r8a66597, PIPEBUF);
+ size = ((tmp >> 10) + 1) * 64;
+ } else {
+ tmp = r8a66597_read(r8a66597, PIPEMAXP);
+ size = tmp & MXPS;
+ }
+ }
+
+ return size;
+}
+
+static inline unsigned short mbw_value(struct r8a66597 *r8a66597)
+{
+ if (r8a66597->pdata->on_chip)
+ return MBW_32;
+ else
+ return MBW_16;
+}
+
+static inline void pipe_change(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ struct r8a66597_ep *ep = r8a66597->pipenum2ep[pipenum];
+
+ if (ep->use_dma)
+ return;
+
+ r8a66597_mdfy(r8a66597, pipenum, CURPIPE, ep->fifosel);
+
+ ndelay(450);
+
+ r8a66597_bset(r8a66597, mbw_value(r8a66597), ep->fifosel);
+}
+
+static int pipe_buffer_setting(struct r8a66597 *r8a66597,
+ struct r8a66597_pipe_info *info)
+{
+ u16 bufnum = 0, buf_bsize = 0;
+ u16 pipecfg = 0;
+
+ if (info->pipe == 0)
+ return -EINVAL;
+
+ r8a66597_write(r8a66597, info->pipe, PIPESEL);
+
+ if (info->dir_in)
+ pipecfg |= R8A66597_DIR;
+ pipecfg |= info->type;
+ pipecfg |= info->epnum;
+ switch (info->type) {
+ case R8A66597_INT:
+ bufnum = 4 + (info->pipe - R8A66597_BASE_PIPENUM_INT);
+ buf_bsize = 0;
+ break;
+ case R8A66597_BULK:
+ /* isochronous pipes may be used as bulk pipes */
+ if (info->pipe > R8A66597_BASE_PIPENUM_BULK)
+ bufnum = info->pipe - R8A66597_BASE_PIPENUM_BULK;
+ else
+ bufnum = info->pipe - R8A66597_BASE_PIPENUM_ISOC;
+
+ bufnum = R8A66597_BASE_BUFNUM + (bufnum * 16);
+ buf_bsize = 7;
+ pipecfg |= R8A66597_DBLB;
+ if (!info->dir_in)
+ pipecfg |= R8A66597_SHTNAK;
+ break;
+ case R8A66597_ISO:
+ bufnum = R8A66597_BASE_BUFNUM +
+ (info->pipe - R8A66597_BASE_PIPENUM_ISOC) * 16;
+ buf_bsize = 7;
+ break;
+ }
+
+ if (buf_bsize && ((bufnum + 16) >= R8A66597_MAX_BUFNUM)) {
+ pr_err(KERN_ERR "r8a66597 pipe memory is insufficient\n");
+ return -ENOMEM;
+ }
+
+ r8a66597_write(r8a66597, pipecfg, PIPECFG);
+ r8a66597_write(r8a66597, (buf_bsize << 10) | (bufnum), PIPEBUF);
+ r8a66597_write(r8a66597, info->maxpacket, PIPEMAXP);
+ if (info->interval)
+ info->interval--;
+ r8a66597_write(r8a66597, info->interval, PIPEPERI);
+
+ return 0;
+}
+
+static void pipe_buffer_release(struct r8a66597 *r8a66597,
+ struct r8a66597_pipe_info *info)
+{
+ if (info->pipe == 0)
+ return;
+
+ if (is_bulk_pipe(info->pipe))
+ r8a66597->bulk--;
+ else if (is_interrupt_pipe(info->pipe))
+ r8a66597->interrupt--;
+ else if (is_isoc_pipe(info->pipe)) {
+ r8a66597->isochronous--;
+ if (info->type == R8A66597_BULK)
+ r8a66597->bulk--;
+ } else
+ printk(KERN_ERR "ep_release: unexpect pipenum (%d)\n",
+ info->pipe);
+}
+
+static void pipe_initialize(struct r8a66597_ep *ep)
+{
+ struct r8a66597 *r8a66597 = ep->r8a66597;
+
+ r8a66597_mdfy(r8a66597, 0, CURPIPE, ep->fifosel);
+
+ r8a66597_write(r8a66597, ACLRM, ep->pipectr);
+ r8a66597_write(r8a66597, 0, ep->pipectr);
+ r8a66597_write(r8a66597, SQCLR, ep->pipectr);
+ if (ep->use_dma) {
+ r8a66597_mdfy(r8a66597, ep->pipenum, CURPIPE, ep->fifosel);
+
+ ndelay(450);
+
+ r8a66597_bset(r8a66597, mbw_value(r8a66597), ep->fifosel);
+ }
+}
+
+static void r8a66597_ep_setting(struct r8a66597 *r8a66597,
+ struct r8a66597_ep *ep,
+ const struct usb_endpoint_descriptor *desc,
+ u16 pipenum, int dma)
+{
+ ep->use_dma = 0;
+ ep->fifoaddr = CFIFO;
+ ep->fifosel = CFIFOSEL;
+ ep->fifoctr = CFIFOCTR;
+ ep->fifotrn = 0;
+
+ ep->pipectr = get_pipectr_addr(pipenum);
+ ep->pipenum = pipenum;
+ ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize);
+ r8a66597->pipenum2ep[pipenum] = ep;
+ r8a66597->epaddr2ep[desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK]
+ = ep;
+ INIT_LIST_HEAD(&ep->queue);
+}
+
+static void r8a66597_ep_release(struct r8a66597_ep *ep)
+{
+ struct r8a66597 *r8a66597 = ep->r8a66597;
+ u16 pipenum = ep->pipenum;
+
+ if (pipenum == 0)
+ return;
+
+ if (ep->use_dma)
+ r8a66597->num_dma--;
+ ep->pipenum = 0;
+ ep->busy = 0;
+ ep->use_dma = 0;
+}
+
+static int alloc_pipe_config(struct r8a66597_ep *ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct r8a66597 *r8a66597 = ep->r8a66597;
+ struct r8a66597_pipe_info info;
+ int dma = 0;
+ unsigned char *counter;
+ int ret;
+
+ ep->desc = desc;
+
+ if (ep->pipenum) /* already allocated pipe */
+ return 0;
+
+ switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) {
+ case USB_ENDPOINT_XFER_BULK:
+ if (r8a66597->bulk >= R8A66597_MAX_NUM_BULK) {
+ if (r8a66597->isochronous >= R8A66597_MAX_NUM_ISOC) {
+ printk(KERN_ERR "bulk pipe is insufficient\n");
+ return -ENODEV;
+ } else {
+ info.pipe = R8A66597_BASE_PIPENUM_ISOC
+ + r8a66597->isochronous;
+ counter = &r8a66597->isochronous;
+ }
+ } else {
+ info.pipe = R8A66597_BASE_PIPENUM_BULK + r8a66597->bulk;
+ counter = &r8a66597->bulk;
+ }
+ info.type = R8A66597_BULK;
+ dma = 1;
+ break;
+ case USB_ENDPOINT_XFER_INT:
+ if (r8a66597->interrupt >= R8A66597_MAX_NUM_INT) {
+ printk(KERN_ERR "interrupt pipe is insufficient\n");
+ return -ENODEV;
+ }
+ info.pipe = R8A66597_BASE_PIPENUM_INT + r8a66597->interrupt;
+ info.type = R8A66597_INT;
+ counter = &r8a66597->interrupt;
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+ if (r8a66597->isochronous >= R8A66597_MAX_NUM_ISOC) {
+ printk(KERN_ERR "isochronous pipe is insufficient\n");
+ return -ENODEV;
+ }
+ info.pipe = R8A66597_BASE_PIPENUM_ISOC + r8a66597->isochronous;
+ info.type = R8A66597_ISO;
+ counter = &r8a66597->isochronous;
+ break;
+ default:
+ printk(KERN_ERR "unexpect xfer type\n");
+ return -EINVAL;
+ }
+ ep->type = info.type;
+
+ info.epnum = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
+ info.maxpacket = le16_to_cpu(desc->wMaxPacketSize);
+ info.interval = desc->bInterval;
+ if (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
+ info.dir_in = 1;
+ else
+ info.dir_in = 0;
+
+ ret = pipe_buffer_setting(r8a66597, &info);
+ if (ret < 0) {
+ printk(KERN_ERR "pipe_buffer_setting fail\n");
+ return ret;
+ }
+
+ (*counter)++;
+ if ((counter == &r8a66597->isochronous) && info.type == R8A66597_BULK)
+ r8a66597->bulk++;
+
+ r8a66597_ep_setting(r8a66597, ep, desc, info.pipe, dma);
+ pipe_initialize(ep);
+
+ return 0;
+}
+
+static int free_pipe_config(struct r8a66597_ep *ep)
+{
+ struct r8a66597 *r8a66597 = ep->r8a66597;
+ struct r8a66597_pipe_info info;
+
+ info.pipe = ep->pipenum;
+ info.type = ep->type;
+ pipe_buffer_release(r8a66597, &info);
+ r8a66597_ep_release(ep);
+
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+static void pipe_irq_enable(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ enable_irq_ready(r8a66597, pipenum);
+ enable_irq_nrdy(r8a66597, pipenum);
+}
+
+static void pipe_irq_disable(struct r8a66597 *r8a66597, u16 pipenum)
+{
+ disable_irq_ready(r8a66597, pipenum);
+ disable_irq_nrdy(r8a66597, pipenum);
+}
+
+/* if complete is true, gadget driver complete function is not call */
+static void control_end(struct r8a66597 *r8a66597, unsigned ccpl)
+{
+ r8a66597->ep[0].internal_ccpl = ccpl;
+ pipe_start(r8a66597, 0);
+ r8a66597_bset(r8a66597, CCPL, DCPCTR);
+}
+
+static void start_ep0_write(struct r8a66597_ep *ep,
+ struct r8a66597_request *req)
+{
+ struct r8a66597 *r8a66597 = ep->r8a66597;
+
+ pipe_change(r8a66597, ep->pipenum);
+ r8a66597_mdfy(r8a66597, ISEL, (ISEL | CURPIPE), CFIFOSEL);
+ r8a66597_write(r8a66597, BCLR, ep->fifoctr);
+ if (req->req.length == 0) {
+ r8a66597_bset(r8a66597, BVAL, ep->fifoctr);
+ pipe_start(r8a66597, 0);
+ transfer_complete(ep, req, 0);
+ } else {
+ r8a66597_write(r8a66597, ~BEMP0, BEMPSTS);
+ irq_ep0_write(ep, req);
+ }
+}
+
+static void start_packet_write(struct r8a66597_ep *ep,
+ struct r8a66597_request *req)
+{
+ struct r8a66597 *r8a66597 = ep->r8a66597;
+ u16 tmp;
+
+ pipe_change(r8a66597, ep->pipenum);
+ disable_irq_empty(r8a66597, ep->pipenum);
+ pipe_start(r8a66597, ep->pipenum);
+
+ tmp = r8a66597_read(r8a66597, ep->fifoctr);
+ if (unlikely((tmp & FRDY) == 0))
+ pipe_irq_enable(r8a66597, ep->pipenum);
+ else
+ irq_packet_write(ep, req);
+}
+
+static void start_packet_read(struct r8a66597_ep *ep,
+ struct r8a66597_request *req)
+{
+ struct r8a66597 *r8a66597 = ep->r8a66597;
+ u16 pipenum = ep->pipenum;
+
+ if (ep->pipenum == 0) {
+ r8a66597_mdfy(r8a66597, 0, (ISEL | CURPIPE), CFIFOSEL);
+ r8a66597_write(r8a66597, BCLR, ep->fifoctr);
+ pipe_start(r8a66597, pipenum);
+ pipe_irq_enable(r8a66597, pipenum);
+ } else {
+ if (ep->use_dma) {
+ r8a66597_bset(r8a66597, TRCLR, ep->fifosel);
+ pipe_change(r8a66597, pipenum);
+ r8a66597_bset(r8a66597, TRENB, ep->fifosel);
+ r8a66597_write(r8a66597,
+ (req->req.length + ep->ep.maxpacket - 1)
+ / ep->ep.maxpacket,
+ ep->fifotrn);
+ }
+ pipe_start(r8a66597, pipenum); /* trigger once */
+ pipe_irq_enable(r8a66597, pipenum);
+ }
+}
+
+static void start_packet(struct r8a66597_ep *ep, struct r8a66597_request *req)
+{
+ if (ep->desc->bEndpointAddress & USB_DIR_IN)
+ start_packet_write(ep, req);
+ else
+ start_packet_read(ep, req);
+}
+
+static void start_ep0(struct r8a66597_ep *ep, struct r8a66597_request *req)
+{
+ u16 ctsq;
+
+ ctsq = r8a66597_read(ep->r8a66597, INTSTS0) & CTSQ;
+
+ switch (ctsq) {
+ case CS_RDDS:
+ start_ep0_write(ep, req);
+ break;
+ case CS_WRDS:
+ start_packet_read(ep, req);
+ break;
+
+ case CS_WRND:
+ control_end(ep->r8a66597, 0);
+ break;
+ default:
+ printk(KERN_ERR "start_ep0: unexpect ctsq(%x)\n", ctsq);
+ break;
+ }
+}
+
+static void init_controller(struct r8a66597 *r8a66597)
+{
+ u16 vif = r8a66597->pdata->vif ? LDRV : 0;
+ u16 irq_sense = r8a66597->irq_sense_low ? INTL : 0;
+ u16 endian = r8a66597->pdata->endian ? BIGEND : 0;
+
+ if (r8a66597->pdata->on_chip) {
+ r8a66597_bset(r8a66597, 0x04, SYSCFG1);
+ r8a66597_bset(r8a66597, HSE, SYSCFG0);
+
+ r8a66597_bclr(r8a66597, USBE, SYSCFG0);
+ r8a66597_bclr(r8a66597, DPRPU, SYSCFG0);
+ r8a66597_bset(r8a66597, USBE, SYSCFG0);
+
+ r8a66597_bset(r8a66597, SCKE, SYSCFG0);
+
+ r8a66597_bset(r8a66597, irq_sense, INTENB1);
+ r8a66597_write(r8a66597, BURST | CPU_ADR_RD_WR,
+ DMA0CFG);
+ } else {
+ r8a66597_bset(r8a66597, vif | endian, PINCFG);
+ r8a66597_bset(r8a66597, HSE, SYSCFG0); /* High spd */
+ r8a66597_mdfy(r8a66597, get_xtal_from_pdata(r8a66597->pdata),
+ XTAL, SYSCFG0);
+
+ r8a66597_bclr(r8a66597, USBE, SYSCFG0);
+ r8a66597_bclr(r8a66597, DPRPU, SYSCFG0);
+ r8a66597_bset(r8a66597, USBE, SYSCFG0);
+
+ r8a66597_bset(r8a66597, XCKE, SYSCFG0);
+
+ msleep(3);
+
+ r8a66597_bset(r8a66597, PLLC, SYSCFG0);
+
+ msleep(1);
+
+ r8a66597_bset(r8a66597, SCKE, SYSCFG0);
+
+ r8a66597_bset(r8a66597, irq_sense, INTENB1);
+ r8a66597_write(r8a66597, BURST | CPU_ADR_RD_WR,
+ DMA0CFG);
+ }
+}
+
+static void disable_controller(struct r8a66597 *r8a66597)
+{
+ if (r8a66597->pdata->on_chip) {
+ r8a66597_bset(r8a66597, SCKE, SYSCFG0);
+
+ /* disable interrupts */
+ r8a66597_write(r8a66597, 0, INTENB0);
+ r8a66597_write(r8a66597, 0, INTENB1);
+ r8a66597_write(r8a66597, 0, BRDYENB);
+ r8a66597_write(r8a66597, 0, BEMPENB);
+ r8a66597_write(r8a66597, 0, NRDYENB);
+
+ /* clear status */
+ r8a66597_write(r8a66597, 0, BRDYSTS);
+ r8a66597_write(r8a66597, 0, NRDYSTS);
+ r8a66597_write(r8a66597, 0, BEMPSTS);
+
+ r8a66597_bclr(r8a66597, USBE, SYSCFG0);
+ r8a66597_bclr(r8a66597, SCKE, SYSCFG0);
+
+ } else {
+ r8a66597_bclr(r8a66597, SCKE, SYSCFG0);
+ udelay(1);
+ r8a66597_bclr(r8a66597, PLLC, SYSCFG0);
+ udelay(1);
+ udelay(1);
+ r8a66597_bclr(r8a66597, XCKE, SYSCFG0);
+ }
+}
+
+static void r8a66597_start_xclock(struct r8a66597 *r8a66597)
+{
+ u16 tmp;
+
+ if (!r8a66597->pdata->on_chip) {
+ tmp = r8a66597_read(r8a66597, SYSCFG0);
+ if (!(tmp & XCKE))
+ r8a66597_bset(r8a66597, XCKE, SYSCFG0);
+ }
+}
+
+static struct r8a66597_request *get_request_from_ep(struct r8a66597_ep *ep)
+{
+ return list_entry(ep->queue.next, struct r8a66597_request, queue);
+}
+
+/*-------------------------------------------------------------------------*/
+static void transfer_complete(struct r8a66597_ep *ep,
+ struct r8a66597_request *req, int status)
+__releases(r8a66597->lock)
+__acquires(r8a66597->lock)
+{
+ int restart = 0;
+
+ if (unlikely(ep->pipenum == 0)) {
+ if (ep->internal_ccpl) {
+ ep->internal_ccpl = 0;
+ return;
+ }
+ }
+
+ list_del_init(&req->queue);
+ if (ep->r8a66597->gadget.speed == USB_SPEED_UNKNOWN)
+ req->req.status = -ESHUTDOWN;
+ else
+ req->req.status = status;
+
+ if (!list_empty(&ep->queue))
+ restart = 1;
+
+ spin_unlock(&ep->r8a66597->lock);
+ req->req.complete(&ep->ep, &req->req);
+ spin_lock(&ep->r8a66597->lock);
+
+ if (restart) {
+ req = get_request_from_ep(ep);
+ if (ep->desc)
+ start_packet(ep, req);
+ }
+}
+
+static void irq_ep0_write(struct r8a66597_ep *ep, struct r8a66597_request *req)
+{
+ int i;
+ u16 tmp;
+ unsigned bufsize;
+ size_t size;
+ void *buf;
+ u16 pipenum = ep->pipenum;
+ struct r8a66597 *r8a66597 = ep->r8a66597;
+
+ pipe_change(r8a66597, pipenum);
+ r8a66597_bset(r8a66597, ISEL, ep->fifosel);
+
+ i = 0;
+ do {
+ tmp = r8a66597_read(r8a66597, ep->fifoctr);
+ if (i++ > 100000) {
+ printk(KERN_ERR "pipe0 is busy. maybe cpu i/o bus"
+ "conflict. please power off this controller.");
+ return;
+ }
+ ndelay(1);
+ } while ((tmp & FRDY) == 0);
+
+ /* prepare parameters */
+ bufsize = get_buffer_size(r8a66597, pipenum);
+ buf = req->req.buf + req->req.actual;
+ size = min(bufsize, req->req.length - req->req.actual);
+
+ /* write fifo */
+ if (req->req.buf) {
+ if (size > 0)
+ r8a66597_write_fifo(r8a66597, ep->fifoaddr, buf, size);
+ if ((size == 0) || ((size % ep->ep.maxpacket) != 0))
+ r8a66597_bset(r8a66597, BVAL, ep->fifoctr);
+ }
+
+ /* update parameters */
+ req->req.actual += size;
+
+ /* check transfer finish */
+ if ((!req->req.zero && (req->req.actual == req->req.length))
+ || (size % ep->ep.maxpacket)
+ || (size == 0)) {
+ disable_irq_ready(r8a66597, pipenum);
+ disable_irq_empty(r8a66597, pipenum);
+ } else {
+ disable_irq_ready(r8a66597, pipenum);
+ enable_irq_empty(r8a66597, pipenum);
+ }
+ pipe_start(r8a66597, pipenum);
+}
+
+static void irq_packet_write(struct r8a66597_ep *ep,
+ struct r8a66597_request *req)
+{
+ u16 tmp;
+ unsigned bufsize;
+ size_t size;
+ void *buf;
+ u16 pipenum = ep->pipenum;
+ struct r8a66597 *r8a66597 = ep->r8a66597;
+
+ pipe_change(r8a66597, pipenum);
+ tmp = r8a66597_read(r8a66597, ep->fifoctr);
+ if (unlikely((tmp & FRDY) == 0)) {
+ pipe_stop(r8a66597, pipenum);
+ pipe_irq_disable(r8a66597, pipenum);
+ printk(KERN_ERR "write fifo not ready. pipnum=%d\n", pipenum);
+ return;
+ }
+
+ /* prepare parameters */
+ bufsize = get_buffer_size(r8a66597, pipenum);
+ buf = req->req.buf + req->req.actual;
+ size = min(bufsize, req->req.length - req->req.actual);
+
+ /* write fifo */
+ if (req->req.buf) {
+ r8a66597_write_fifo(r8a66597, ep->fifoaddr, buf, size);
+ if ((size == 0)
+ || ((size % ep->ep.maxpacket) != 0)
+ || ((bufsize != ep->ep.maxpacket)
+ && (bufsize > size)))
+ r8a66597_bset(r8a66597, BVAL, ep->fifoctr);
+ }
+
+ /* update parameters */
+ req->req.actual += size;
+ /* check transfer finish */
+ if ((!req->req.zero && (req->req.actual == req->req.length))
+ || (size % ep->ep.maxpacket)
+ || (size == 0)) {
+ disable_irq_ready(r8a66597, pipenum);
+ enable_irq_empty(r8a66597, pipenum);
+ } else {
+ disable_irq_empty(r8a66597, pipenum);
+ pipe_irq_enable(r8a66597, pipenum);
+ }
+}
+
+static void irq_packet_read(struct r8a66597_ep *ep,
+ struct r8a66597_request *req)
+{
+ u16 tmp;
+ int rcv_len, bufsize, req_len;
+ int size;
+ void *buf;
+ u16 pipenum = ep->pipenum;
+ struct r8a66597 *r8a66597 = ep->r8a66597;
+ int finish = 0;
+
+ pipe_change(r8a66597, pipenum);
+ tmp = r8a66597_read(r8a66597, ep->fifoctr);
+ if (unlikely((tmp & FRDY) == 0)) {
+ req->req.status = -EPIPE;
+ pipe_stop(r8a66597, pipenum);
+ pipe_irq_disable(r8a66597, pipenum);
+ printk(KERN_ERR "read fifo not ready");
+ return;
+ }
+
+ /* prepare parameters */
+ rcv_len = tmp & DTLN;
+ bufsize = get_buffer_size(r8a66597, pipenum);
+
+ buf = req->req.buf + req->req.actual;
+ req_len = req->req.length - req->req.actual;
+ if (rcv_len < bufsize)
+ size = min(rcv_len, req_len);
+ else
+ size = min(bufsize, req_len);
+
+ /* update parameters */
+ req->req.actual += size;
+
+ /* check transfer finish */
+ if ((!req->req.zero && (req->req.actual == req->req.length))
+ || (size % ep->ep.maxpacket)
+ || (size == 0)) {
+ pipe_stop(r8a66597, pipenum);
+ pipe_irq_disable(r8a66597, pipenum);
+ finish = 1;
+ }
+
+ /* read fifo */
+ if (req->req.buf) {
+ if (size == 0)
+ r8a66597_write(r8a66597, BCLR, ep->fifoctr);
+ else
+ r8a66597_read_fifo(r8a66597, ep->fifoaddr, buf, size);
+
+ }
+
+ if ((ep->pipenum != 0) && finish)
+ transfer_complete(ep, req, 0);
+}
+
+static void irq_pipe_ready(struct r8a66597 *r8a66597, u16 status, u16 enb)
+{
+ u16 check;
+ u16 pipenum;
+ struct r8a66597_ep *ep;
+ struct r8a66597_request *req;
+
+ if ((status & BRDY0) && (enb & BRDY0)) {
+ r8a66597_write(r8a66597, ~BRDY0, BRDYSTS);
+ r8a66597_mdfy(r8a66597, 0, CURPIPE, CFIFOSEL);
+
+ ep = &r8a66597->ep[0];
+ req = get_request_from_ep(ep);
+ irq_packet_read(ep, req);
+ } else {
+ for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) {
+ check = 1 << pipenum;
+ if ((status & check) && (enb & check)) {
+ r8a66597_write(r8a66597, ~check, BRDYSTS);
+ ep = r8a66597->pipenum2ep[pipenum];
+ req = get_request_from_ep(ep);
+ if (ep->desc->bEndpointAddress & USB_DIR_IN)
+ irq_packet_write(ep, req);
+ else
+ irq_packet_read(ep, req);
+ }
+ }
+ }
+}
+
+static void irq_pipe_empty(struct r8a66597 *r8a66597, u16 status, u16 enb)
+{
+ u16 tmp;
+ u16 check;
+ u16 pipenum;
+ struct r8a66597_ep *ep;
+ struct r8a66597_request *req;
+
+ if ((status & BEMP0) && (enb & BEMP0)) {
+ r8a66597_write(r8a66597, ~BEMP0, BEMPSTS);
+
+ ep = &r8a66597->ep[0];
+ req = get_request_from_ep(ep);
+ irq_ep0_write(ep, req);
+ } else {
+ for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) {
+ check = 1 << pipenum;
+ if ((status & check) && (enb & check)) {
+ r8a66597_write(r8a66597, ~check, BEMPSTS);
+ tmp = control_reg_get(r8a66597, pipenum);
+ if ((tmp & INBUFM) == 0) {
+ disable_irq_empty(r8a66597, pipenum);
+ pipe_irq_disable(r8a66597, pipenum);
+ pipe_stop(r8a66597, pipenum);
+ ep = r8a66597->pipenum2ep[pipenum];
+ req = get_request_from_ep(ep);
+ if (!list_empty(&ep->queue))
+ transfer_complete(ep, req, 0);
+ }
+ }
+ }
+ }
+}
+
+static void get_status(struct r8a66597 *r8a66597, struct usb_ctrlrequest *ctrl)
+__releases(r8a66597->lock)
+__acquires(r8a66597->lock)
+{
+ struct r8a66597_ep *ep;
+ u16 pid;
+ u16 status = 0;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+
+ switch (ctrl->bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ status = 1 << USB_DEVICE_SELF_POWERED;
+ break;
+ case USB_RECIP_INTERFACE:
+ status = 0;
+ break;
+ case USB_RECIP_ENDPOINT:
+ ep = r8a66597->epaddr2ep[w_index & USB_ENDPOINT_NUMBER_MASK];
+ pid = control_reg_get_pid(r8a66597, ep->pipenum);
+ if (pid == PID_STALL)
+ status = 1 << USB_ENDPOINT_HALT;
+ else
+ status = 0;
+ break;
+ default:
+ pipe_stall(r8a66597, 0);
+ return; /* exit */
+ }
+
+ r8a66597->ep0_data = cpu_to_le16(status);
+ r8a66597->ep0_req->buf = &r8a66597->ep0_data;
+ r8a66597->ep0_req->length = 2;
+ /* AV: what happens if we get called again before that gets through? */
+ spin_unlock(&r8a66597->lock);
+ r8a66597_queue(r8a66597->gadget.ep0, r8a66597->ep0_req, GFP_KERNEL);
+ spin_lock(&r8a66597->lock);
+}
+
+static void clear_feature(struct r8a66597 *r8a66597,
+ struct usb_ctrlrequest *ctrl)
+{
+ switch (ctrl->bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ control_end(r8a66597, 1);
+ break;
+ case USB_RECIP_INTERFACE:
+ control_end(r8a66597, 1);
+ break;
+ case USB_RECIP_ENDPOINT: {
+ struct r8a66597_ep *ep;
+ struct r8a66597_request *req;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+
+ ep = r8a66597->epaddr2ep[w_index & USB_ENDPOINT_NUMBER_MASK];
+ if (!ep->wedge) {
+ pipe_stop(r8a66597, ep->pipenum);
+ control_reg_sqclr(r8a66597, ep->pipenum);
+ spin_unlock(&r8a66597->lock);
+ usb_ep_clear_halt(&ep->ep);
+ spin_lock(&r8a66597->lock);
+ }
+
+ control_end(r8a66597, 1);
+
+ req = get_request_from_ep(ep);
+ if (ep->busy) {
+ ep->busy = 0;
+ if (list_empty(&ep->queue))
+ break;
+ start_packet(ep, req);
+ } else if (!list_empty(&ep->queue))
+ pipe_start(r8a66597, ep->pipenum);
+ }
+ break;
+ default:
+ pipe_stall(r8a66597, 0);
+ break;
+ }
+}
+
+static void set_feature(struct r8a66597 *r8a66597, struct usb_ctrlrequest *ctrl)
+{
+
+ switch (ctrl->bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ control_end(r8a66597, 1);
+ break;
+ case USB_RECIP_INTERFACE:
+ control_end(r8a66597, 1);
+ break;
+ case USB_RECIP_ENDPOINT: {
+ struct r8a66597_ep *ep;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+
+ ep = r8a66597->epaddr2ep[w_index & USB_ENDPOINT_NUMBER_MASK];
+ pipe_stall(r8a66597, ep->pipenum);
+
+ control_end(r8a66597, 1);
+ }
+ break;
+ default:
+ pipe_stall(r8a66597, 0);
+ break;
+ }
+}
+
+/* if return value is true, call class driver's setup() */
+static int setup_packet(struct r8a66597 *r8a66597, struct usb_ctrlrequest *ctrl)
+{
+ u16 *p = (u16 *)ctrl;
+ unsigned long offset = USBREQ;
+ int i, ret = 0;
+
+ /* read fifo */
+ r8a66597_write(r8a66597, ~VALID, INTSTS0);
+
+ for (i = 0; i < 4; i++)
+ p[i] = r8a66597_read(r8a66597, offset + i*2);
+
+ /* check request */
+ if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
+ switch (ctrl->bRequest) {
+ case USB_REQ_GET_STATUS:
+ get_status(r8a66597, ctrl);
+ break;
+ case USB_REQ_CLEAR_FEATURE:
+ clear_feature(r8a66597, ctrl);
+ break;
+ case USB_REQ_SET_FEATURE:
+ set_feature(r8a66597, ctrl);
+ break;
+ default:
+ ret = 1;
+ break;
+ }
+ } else
+ ret = 1;
+ return ret;
+}
+
+static void r8a66597_update_usb_speed(struct r8a66597 *r8a66597)
+{
+ u16 speed = get_usb_speed(r8a66597);
+
+ switch (speed) {
+ case HSMODE:
+ r8a66597->gadget.speed = USB_SPEED_HIGH;
+ break;
+ case FSMODE:
+ r8a66597->gadget.speed = USB_SPEED_FULL;
+ break;
+ default:
+ r8a66597->gadget.speed = USB_SPEED_UNKNOWN;
+ printk(KERN_ERR "USB speed unknown\n");
+ }
+}
+
+static void irq_device_state(struct r8a66597 *r8a66597)
+{
+ u16 dvsq;
+
+ dvsq = r8a66597_read(r8a66597, INTSTS0) & DVSQ;
+ r8a66597_write(r8a66597, ~DVST, INTSTS0);
+
+ if (dvsq == DS_DFLT) {
+ /* bus reset */
+ r8a66597->driver->disconnect(&r8a66597->gadget);
+ r8a66597_update_usb_speed(r8a66597);
+ }
+ if (r8a66597->old_dvsq == DS_CNFG && dvsq != DS_CNFG)
+ r8a66597_update_usb_speed(r8a66597);
+ if ((dvsq == DS_CNFG || dvsq == DS_ADDS)
+ && r8a66597->gadget.speed == USB_SPEED_UNKNOWN)
+ r8a66597_update_usb_speed(r8a66597);
+
+ r8a66597->old_dvsq = dvsq;
+}
+
+static void irq_control_stage(struct r8a66597 *r8a66597)
+__releases(r8a66597->lock)
+__acquires(r8a66597->lock)
+{
+ struct usb_ctrlrequest ctrl;
+ u16 ctsq;
+
+ ctsq = r8a66597_read(r8a66597, INTSTS0) & CTSQ;
+ r8a66597_write(r8a66597, ~CTRT, INTSTS0);
+
+ switch (ctsq) {
+ case CS_IDST: {
+ struct r8a66597_ep *ep;
+ struct r8a66597_request *req;
+ ep = &r8a66597->ep[0];
+ req = get_request_from_ep(ep);
+ transfer_complete(ep, req, 0);
+ }
+ break;
+
+ case CS_RDDS:
+ case CS_WRDS:
+ case CS_WRND:
+ if (setup_packet(r8a66597, &ctrl)) {
+ spin_unlock(&r8a66597->lock);
+ if (r8a66597->driver->setup(&r8a66597->gadget, &ctrl)
+ < 0)
+ pipe_stall(r8a66597, 0);
+ spin_lock(&r8a66597->lock);
+ }
+ break;
+ case CS_RDSS:
+ case CS_WRSS:
+ control_end(r8a66597, 0);
+ break;
+ default:
+ printk(KERN_ERR "ctrl_stage: unexpect ctsq(%x)\n", ctsq);
+ break;
+ }
+}
+
+static irqreturn_t r8a66597_irq(int irq, void *_r8a66597)
+{
+ struct r8a66597 *r8a66597 = _r8a66597;
+ u16 intsts0;
+ u16 intenb0;
+ u16 brdysts, nrdysts, bempsts;
+ u16 brdyenb, nrdyenb, bempenb;
+ u16 savepipe;
+ u16 mask0;
+
+ spin_lock(&r8a66597->lock);
+
+ intsts0 = r8a66597_read(r8a66597, INTSTS0);
+ intenb0 = r8a66597_read(r8a66597, INTENB0);
+
+ savepipe = r8a66597_read(r8a66597, CFIFOSEL);
+
+ mask0 = intsts0 & intenb0;
+ if (mask0) {
+ brdysts = r8a66597_read(r8a66597, BRDYSTS);
+ nrdysts = r8a66597_read(r8a66597, NRDYSTS);
+ bempsts = r8a66597_read(r8a66597, BEMPSTS);
+ brdyenb = r8a66597_read(r8a66597, BRDYENB);
+ nrdyenb = r8a66597_read(r8a66597, NRDYENB);
+ bempenb = r8a66597_read(r8a66597, BEMPENB);
+
+ if (mask0 & VBINT) {
+ r8a66597_write(r8a66597, 0xffff & ~VBINT,
+ INTSTS0);
+ r8a66597_start_xclock(r8a66597);
+
+ /* start vbus sampling */
+ r8a66597->old_vbus = r8a66597_read(r8a66597, INTSTS0)
+ & VBSTS;
+ r8a66597->scount = R8A66597_MAX_SAMPLING;
+
+ mod_timer(&r8a66597->timer,
+ jiffies + msecs_to_jiffies(50));
+ }
+ if (intsts0 & DVSQ)
+ irq_device_state(r8a66597);
+
+ if ((intsts0 & BRDY) && (intenb0 & BRDYE)
+ && (brdysts & brdyenb))
+ irq_pipe_ready(r8a66597, brdysts, brdyenb);
+ if ((intsts0 & BEMP) && (intenb0 & BEMPE)
+ && (bempsts & bempenb))
+ irq_pipe_empty(r8a66597, bempsts, bempenb);
+
+ if (intsts0 & CTRT)
+ irq_control_stage(r8a66597);
+ }
+
+ r8a66597_write(r8a66597, savepipe, CFIFOSEL);
+
+ spin_unlock(&r8a66597->lock);
+ return IRQ_HANDLED;
+}
+
+static void r8a66597_timer(unsigned long _r8a66597)
+{
+ struct r8a66597 *r8a66597 = (struct r8a66597 *)_r8a66597;
+ unsigned long flags;
+ u16 tmp;
+
+ spin_lock_irqsave(&r8a66597->lock, flags);
+ tmp = r8a66597_read(r8a66597, SYSCFG0);
+ if (r8a66597->scount > 0) {
+ tmp = r8a66597_read(r8a66597, INTSTS0) & VBSTS;
+ if (tmp == r8a66597->old_vbus) {
+ r8a66597->scount--;
+ if (r8a66597->scount == 0) {
+ if (tmp == VBSTS)
+ r8a66597_usb_connect(r8a66597);
+ else
+ r8a66597_usb_disconnect(r8a66597);
+ } else {
+ mod_timer(&r8a66597->timer,
+ jiffies + msecs_to_jiffies(50));
+ }
+ } else {
+ r8a66597->scount = R8A66597_MAX_SAMPLING;
+ r8a66597->old_vbus = tmp;
+ mod_timer(&r8a66597->timer,
+ jiffies + msecs_to_jiffies(50));
+ }
+ }
+ spin_unlock_irqrestore(&r8a66597->lock, flags);
+}
+
+/*-------------------------------------------------------------------------*/
+static int r8a66597_enable(struct usb_ep *_ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct r8a66597_ep *ep;
+
+ ep = container_of(_ep, struct r8a66597_ep, ep);
+ return alloc_pipe_config(ep, desc);
+}
+
+static int r8a66597_disable(struct usb_ep *_ep)
+{
+ struct r8a66597_ep *ep;
+ struct r8a66597_request *req;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct r8a66597_ep, ep);
+ BUG_ON(!ep);
+
+ while (!list_empty(&ep->queue)) {
+ req = get_request_from_ep(ep);
+ spin_lock_irqsave(&ep->r8a66597->lock, flags);
+ transfer_complete(ep, req, -ECONNRESET);
+ spin_unlock_irqrestore(&ep->r8a66597->lock, flags);
+ }
+
+ pipe_irq_disable(ep->r8a66597, ep->pipenum);
+ return free_pipe_config(ep);
+}
+
+static struct usb_request *r8a66597_alloc_request(struct usb_ep *_ep,
+ gfp_t gfp_flags)
+{
+ struct r8a66597_request *req;
+
+ req = kzalloc(sizeof(struct r8a66597_request), gfp_flags);
+ if (!req)
+ return NULL;
+
+ INIT_LIST_HEAD(&req->queue);
+
+ return &req->req;
+}
+
+static void r8a66597_free_request(struct usb_ep *_ep, struct usb_request *_req)
+{
+ struct r8a66597_request *req;
+
+ req = container_of(_req, struct r8a66597_request, req);
+ kfree(req);
+}
+
+static int r8a66597_queue(struct usb_ep *_ep, struct usb_request *_req,
+ gfp_t gfp_flags)
+{
+ struct r8a66597_ep *ep;
+ struct r8a66597_request *req;
+ unsigned long flags;
+ int request = 0;
+
+ ep = container_of(_ep, struct r8a66597_ep, ep);
+ req = container_of(_req, struct r8a66597_request, req);
+
+ if (ep->r8a66597->gadget.speed == USB_SPEED_UNKNOWN)
+ return -ESHUTDOWN;
+
+ spin_lock_irqsave(&ep->r8a66597->lock, flags);
+
+ if (list_empty(&ep->queue))
+ request = 1;
+
+ list_add_tail(&req->queue, &ep->queue);
+ req->req.actual = 0;
+ req->req.status = -EINPROGRESS;
+
+ if (ep->desc == NULL) /* control */
+ start_ep0(ep, req);
+ else {
+ if (request && !ep->busy)
+ start_packet(ep, req);
+ }
+
+ spin_unlock_irqrestore(&ep->r8a66597->lock, flags);
+
+ return 0;
+}
+
+static int r8a66597_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+ struct r8a66597_ep *ep;
+ struct r8a66597_request *req;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct r8a66597_ep, ep);
+ req = container_of(_req, struct r8a66597_request, req);
+
+ spin_lock_irqsave(&ep->r8a66597->lock, flags);
+ if (!list_empty(&ep->queue))
+ transfer_complete(ep, req, -ECONNRESET);
+ spin_unlock_irqrestore(&ep->r8a66597->lock, flags);
+
+ return 0;
+}
+
+static int r8a66597_set_halt(struct usb_ep *_ep, int value)
+{
+ struct r8a66597_ep *ep;
+ struct r8a66597_request *req;
+ unsigned long flags;
+ int ret = 0;
+
+ ep = container_of(_ep, struct r8a66597_ep, ep);
+ req = get_request_from_ep(ep);
+
+ spin_lock_irqsave(&ep->r8a66597->lock, flags);
+ if (!list_empty(&ep->queue)) {
+ ret = -EAGAIN;
+ goto out;
+ }
+ if (value) {
+ ep->busy = 1;
+ pipe_stall(ep->r8a66597, ep->pipenum);
+ } else {
+ ep->busy = 0;
+ ep->wedge = 0;
+ pipe_stop(ep->r8a66597, ep->pipenum);
+ }
+
+out:
+ spin_unlock_irqrestore(&ep->r8a66597->lock, flags);
+ return ret;
+}
+
+static int r8a66597_set_wedge(struct usb_ep *_ep)
+{
+ struct r8a66597_ep *ep;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct r8a66597_ep, ep);
+
+ if (!ep || !ep->desc)
+ return -EINVAL;
+
+ spin_lock_irqsave(&ep->r8a66597->lock, flags);
+ ep->wedge = 1;
+ spin_unlock_irqrestore(&ep->r8a66597->lock, flags);
+
+ return usb_ep_set_halt(_ep);
+}
+
+static void r8a66597_fifo_flush(struct usb_ep *_ep)
+{
+ struct r8a66597_ep *ep;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct r8a66597_ep, ep);
+ spin_lock_irqsave(&ep->r8a66597->lock, flags);
+ if (list_empty(&ep->queue) && !ep->busy) {
+ pipe_stop(ep->r8a66597, ep->pipenum);
+ r8a66597_bclr(ep->r8a66597, BCLR, ep->fifoctr);
+ }
+ spin_unlock_irqrestore(&ep->r8a66597->lock, flags);
+}
+
+static struct usb_ep_ops r8a66597_ep_ops = {
+ .enable = r8a66597_enable,
+ .disable = r8a66597_disable,
+
+ .alloc_request = r8a66597_alloc_request,
+ .free_request = r8a66597_free_request,
+
+ .queue = r8a66597_queue,
+ .dequeue = r8a66597_dequeue,
+
+ .set_halt = r8a66597_set_halt,
+ .set_wedge = r8a66597_set_wedge,
+ .fifo_flush = r8a66597_fifo_flush,
+};
+
+/*-------------------------------------------------------------------------*/
+static struct r8a66597 *the_controller;
+
+int usb_gadget_register_driver(struct usb_gadget_driver *driver)
+{
+ struct r8a66597 *r8a66597 = the_controller;
+ int retval;
+
+ if (!driver
+ || driver->speed != USB_SPEED_HIGH
+ || !driver->bind
+ || !driver->setup)
+ return -EINVAL;
+ if (!r8a66597)
+ return -ENODEV;
+ if (r8a66597->driver)
+ return -EBUSY;
+
+ /* hook up the driver */
+ driver->driver.bus = NULL;
+ r8a66597->driver = driver;
+ r8a66597->gadget.dev.driver = &driver->driver;
+
+ retval = device_add(&r8a66597->gadget.dev);
+ if (retval) {
+ printk(KERN_ERR "device_add error (%d)\n", retval);
+ goto error;
+ }
+
+ retval = driver->bind(&r8a66597->gadget);
+ if (retval) {
+ printk(KERN_ERR "bind to driver error (%d)\n", retval);
+ device_del(&r8a66597->gadget.dev);
+ goto error;
+ }
+
+ r8a66597_bset(r8a66597, VBSE, INTENB0);
+ if (r8a66597_read(r8a66597, INTSTS0) & VBSTS) {
+ r8a66597_start_xclock(r8a66597);
+ /* start vbus sampling */
+ r8a66597->old_vbus = r8a66597_read(r8a66597,
+ INTSTS0) & VBSTS;
+ r8a66597->scount = R8A66597_MAX_SAMPLING;
+ mod_timer(&r8a66597->timer, jiffies + msecs_to_jiffies(50));
+ }
+
+ return 0;
+
+error:
+ r8a66597->driver = NULL;
+ r8a66597->gadget.dev.driver = NULL;
+
+ return retval;
+}
+EXPORT_SYMBOL(usb_gadget_register_driver);
+
+int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
+{
+ struct r8a66597 *r8a66597 = the_controller;
+ unsigned long flags;
+
+ if (driver != r8a66597->driver || !driver->unbind)
+ return -EINVAL;
+
+ spin_lock_irqsave(&r8a66597->lock, flags);
+ if (r8a66597->gadget.speed != USB_SPEED_UNKNOWN)
+ r8a66597_usb_disconnect(r8a66597);
+ spin_unlock_irqrestore(&r8a66597->lock, flags);
+
+ r8a66597_bclr(r8a66597, VBSE, INTENB0);
+
+ driver->unbind(&r8a66597->gadget);
+
+ init_controller(r8a66597);
+ disable_controller(r8a66597);
+
+ device_del(&r8a66597->gadget.dev);
+ r8a66597->driver = NULL;
+ return 0;
+}
+EXPORT_SYMBOL(usb_gadget_unregister_driver);
+
+/*-------------------------------------------------------------------------*/
+static int r8a66597_get_frame(struct usb_gadget *_gadget)
+{
+ struct r8a66597 *r8a66597 = gadget_to_r8a66597(_gadget);
+ return r8a66597_read(r8a66597, FRMNUM) & 0x03FF;
+}
+
+static struct usb_gadget_ops r8a66597_gadget_ops = {
+ .get_frame = r8a66597_get_frame,
+};
+
+static int __exit r8a66597_remove(struct platform_device *pdev)
+{
+ struct r8a66597 *r8a66597 = dev_get_drvdata(&pdev->dev);
+
+ del_timer_sync(&r8a66597->timer);
+ iounmap((void *)r8a66597->reg);
+ free_irq(platform_get_irq(pdev, 0), r8a66597);
+ r8a66597_free_request(&r8a66597->ep[0].ep, r8a66597->ep0_req);
+#ifdef CONFIG_HAVE_CLK
+ if (r8a66597->pdata->on_chip) {
+ clk_disable(r8a66597->clk);
+ clk_put(r8a66597->clk);
+ }
+#endif
+ kfree(r8a66597);
+ return 0;
+}
+
+static void nop_completion(struct usb_ep *ep, struct usb_request *r)
+{
+}
+
+static int __init r8a66597_probe(struct platform_device *pdev)
+{
+#ifdef CONFIG_HAVE_CLK
+ char clk_name[8];
+#endif
+ struct resource *res, *ires;
+ int irq;
+ void __iomem *reg = NULL;
+ struct r8a66597 *r8a66597 = NULL;
+ int ret = 0;
+ int i;
+ unsigned long irq_trigger;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ ret = -ENODEV;
+ printk(KERN_ERR "platform_get_resource error.\n");
+ goto clean_up;
+ }
+
+ ires = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ irq = ires->start;
+ irq_trigger = ires->flags & IRQF_TRIGGER_MASK;
+
+ if (irq < 0) {
+ ret = -ENODEV;
+ printk(KERN_ERR "platform_get_irq error.\n");
+ goto clean_up;
+ }
+
+ reg = ioremap(res->start, resource_size(res));
+ if (reg == NULL) {
+ ret = -ENOMEM;
+ printk(KERN_ERR "ioremap error.\n");
+ goto clean_up;
+ }
+
+ /* initialize ucd */
+ r8a66597 = kzalloc(sizeof(struct r8a66597), GFP_KERNEL);
+ if (r8a66597 == NULL) {
+ printk(KERN_ERR "kzalloc error\n");
+ goto clean_up;
+ }
+
+ spin_lock_init(&r8a66597->lock);
+ dev_set_drvdata(&pdev->dev, r8a66597);
+ r8a66597->pdata = pdev->dev.platform_data;
+ r8a66597->irq_sense_low = irq_trigger == IRQF_TRIGGER_LOW;
+
+ r8a66597->gadget.ops = &r8a66597_gadget_ops;
+ device_initialize(&r8a66597->gadget.dev);
+ dev_set_name(&r8a66597->gadget.dev, "gadget");
+ r8a66597->gadget.is_dualspeed = 1;
+ r8a66597->gadget.dev.parent = &pdev->dev;
+ r8a66597->gadget.dev.dma_mask = pdev->dev.dma_mask;
+ r8a66597->gadget.dev.release = pdev->dev.release;
+ r8a66597->gadget.name = udc_name;
+
+ init_timer(&r8a66597->timer);
+ r8a66597->timer.function = r8a66597_timer;
+ r8a66597->timer.data = (unsigned long)r8a66597;
+ r8a66597->reg = (unsigned long)reg;
+
+#ifdef CONFIG_HAVE_CLK
+ if (r8a66597->pdata->on_chip) {
+ snprintf(clk_name, sizeof(clk_name), "usb%d", pdev->id);
+ r8a66597->clk = clk_get(&pdev->dev, clk_name);
+ if (IS_ERR(r8a66597->clk)) {
+ dev_err(&pdev->dev, "cannot get clock \"%s\"\n",
+ clk_name);
+ ret = PTR_ERR(r8a66597->clk);
+ goto clean_up;
+ }
+ clk_enable(r8a66597->clk);
+ }
+#endif
+
+ disable_controller(r8a66597); /* make sure controller is disabled */
+
+ ret = request_irq(irq, r8a66597_irq, IRQF_DISABLED | IRQF_SHARED,
+ udc_name, r8a66597);
+ if (ret < 0) {
+ printk(KERN_ERR "request_irq error (%d)\n", ret);
+ goto clean_up2;
+ }
+
+ INIT_LIST_HEAD(&r8a66597->gadget.ep_list);
+ r8a66597->gadget.ep0 = &r8a66597->ep[0].ep;
+ INIT_LIST_HEAD(&r8a66597->gadget.ep0->ep_list);
+ for (i = 0; i < R8A66597_MAX_NUM_PIPE; i++) {
+ struct r8a66597_ep *ep = &r8a66597->ep[i];
+
+ if (i != 0) {
+ INIT_LIST_HEAD(&r8a66597->ep[i].ep.ep_list);
+ list_add_tail(&r8a66597->ep[i].ep.ep_list,
+ &r8a66597->gadget.ep_list);
+ }
+ ep->r8a66597 = r8a66597;
+ INIT_LIST_HEAD(&ep->queue);
+ ep->ep.name = r8a66597_ep_name[i];
+ ep->ep.ops = &r8a66597_ep_ops;
+ ep->ep.maxpacket = 512;
+ }
+ r8a66597->ep[0].ep.maxpacket = 64;
+ r8a66597->ep[0].pipenum = 0;
+ r8a66597->ep[0].fifoaddr = CFIFO;
+ r8a66597->ep[0].fifosel = CFIFOSEL;
+ r8a66597->ep[0].fifoctr = CFIFOCTR;
+ r8a66597->ep[0].fifotrn = 0;
+ r8a66597->ep[0].pipectr = get_pipectr_addr(0);
+ r8a66597->pipenum2ep[0] = &r8a66597->ep[0];
+ r8a66597->epaddr2ep[0] = &r8a66597->ep[0];
+
+ the_controller = r8a66597;
+
+ r8a66597->ep0_req = r8a66597_alloc_request(&r8a66597->ep[0].ep,
+ GFP_KERNEL);
+ if (r8a66597->ep0_req == NULL)
+ goto clean_up3;
+ r8a66597->ep0_req->complete = nop_completion;
+
+ init_controller(r8a66597);
+
+ dev_info(&pdev->dev, "version %s\n", DRIVER_VERSION);
+ return 0;
+
+clean_up3:
+ free_irq(irq, r8a66597);
+clean_up2:
+#ifdef CONFIG_HAVE_CLK
+ if (r8a66597->pdata->on_chip) {
+ clk_disable(r8a66597->clk);
+ clk_put(r8a66597->clk);
+ }
+#endif
+clean_up:
+ if (r8a66597) {
+ if (r8a66597->ep0_req)
+ r8a66597_free_request(&r8a66597->ep[0].ep,
+ r8a66597->ep0_req);
+ kfree(r8a66597);
+ }
+ if (reg)
+ iounmap(reg);
+
+ return ret;
+}
+
+/*-------------------------------------------------------------------------*/
+static struct platform_driver r8a66597_driver = {
+ .remove = __exit_p(r8a66597_remove),
+ .driver = {
+ .name = (char *) udc_name,
+ },
+};
+
+static int __init r8a66597_udc_init(void)
+{
+ return platform_driver_probe(&r8a66597_driver, r8a66597_probe);
+}
+module_init(r8a66597_udc_init);
+
+static void __exit r8a66597_udc_cleanup(void)
+{
+ platform_driver_unregister(&r8a66597_driver);
+}
+module_exit(r8a66597_udc_cleanup);
+
+MODULE_DESCRIPTION("R8A66597 USB gadget driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yoshihiro Shimoda");
+
diff --git a/drivers/usb/gadget/r8a66597-udc.h b/drivers/usb/gadget/r8a66597-udc.h
new file mode 100644
index 00000000000..03087e7b919
--- /dev/null
+++ b/drivers/usb/gadget/r8a66597-udc.h
@@ -0,0 +1,256 @@
+/*
+ * R8A66597 UDC
+ *
+ * Copyright (C) 2007-2009 Renesas Solutions Corp.
+ *
+ * Author : Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com>
+ *
+ * This program 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; version 2 of the License.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __R8A66597_H__
+#define __R8A66597_H__
+
+#ifdef CONFIG_HAVE_CLK
+#include <linux/clk.h>
+#endif
+
+#include <linux/usb/r8a66597.h>
+
+#define R8A66597_MAX_SAMPLING 10
+
+#define R8A66597_MAX_NUM_PIPE 8
+#define R8A66597_MAX_NUM_BULK 3
+#define R8A66597_MAX_NUM_ISOC 2
+#define R8A66597_MAX_NUM_INT 2
+
+#define R8A66597_BASE_PIPENUM_BULK 3
+#define R8A66597_BASE_PIPENUM_ISOC 1
+#define R8A66597_BASE_PIPENUM_INT 6
+
+#define R8A66597_BASE_BUFNUM 6
+#define R8A66597_MAX_BUFNUM 0x4F
+
+#define is_bulk_pipe(pipenum) \
+ ((pipenum >= R8A66597_BASE_PIPENUM_BULK) && \
+ (pipenum < (R8A66597_BASE_PIPENUM_BULK + R8A66597_MAX_NUM_BULK)))
+#define is_interrupt_pipe(pipenum) \
+ ((pipenum >= R8A66597_BASE_PIPENUM_INT) && \
+ (pipenum < (R8A66597_BASE_PIPENUM_INT + R8A66597_MAX_NUM_INT)))
+#define is_isoc_pipe(pipenum) \
+ ((pipenum >= R8A66597_BASE_PIPENUM_ISOC) && \
+ (pipenum < (R8A66597_BASE_PIPENUM_ISOC + R8A66597_MAX_NUM_ISOC)))
+
+struct r8a66597_pipe_info {
+ u16 pipe;
+ u16 epnum;
+ u16 maxpacket;
+ u16 type;
+ u16 interval;
+ u16 dir_in;
+};
+
+struct r8a66597_request {
+ struct usb_request req;
+ struct list_head queue;
+};
+
+struct r8a66597_ep {
+ struct usb_ep ep;
+ struct r8a66597 *r8a66597;
+
+ struct list_head queue;
+ unsigned busy:1;
+ unsigned wedge:1;
+ unsigned internal_ccpl:1; /* use only control */
+
+ /* this member can able to after r8a66597_enable */
+ unsigned use_dma:1;
+ u16 pipenum;
+ u16 type;
+ const struct usb_endpoint_descriptor *desc;
+ /* register address */
+ unsigned char fifoaddr;
+ unsigned char fifosel;
+ unsigned char fifoctr;
+ unsigned char fifotrn;
+ unsigned char pipectr;
+};
+
+struct r8a66597 {
+ spinlock_t lock;
+ unsigned long reg;
+
+#ifdef CONFIG_HAVE_CLK
+ struct clk *clk;
+#endif
+ struct r8a66597_platdata *pdata;
+
+ struct usb_gadget gadget;
+ struct usb_gadget_driver *driver;
+
+ struct r8a66597_ep ep[R8A66597_MAX_NUM_PIPE];
+ struct r8a66597_ep *pipenum2ep[R8A66597_MAX_NUM_PIPE];
+ struct r8a66597_ep *epaddr2ep[16];
+
+ struct timer_list timer;
+ struct usb_request *ep0_req; /* for internal request */
+ u16 ep0_data; /* for internal request */
+ u16 old_vbus;
+ u16 scount;
+ u16 old_dvsq;
+
+ /* pipe config */
+ unsigned char bulk;
+ unsigned char interrupt;
+ unsigned char isochronous;
+ unsigned char num_dma;
+
+ unsigned irq_sense_low:1;
+};
+
+#define gadget_to_r8a66597(_gadget) \
+ container_of(_gadget, struct r8a66597, gadget)
+#define r8a66597_to_gadget(r8a66597) (&r8a66597->gadget)
+
+static inline u16 r8a66597_read(struct r8a66597 *r8a66597, unsigned long offset)
+{
+ return inw(r8a66597->reg + offset);
+}
+
+static inline void r8a66597_read_fifo(struct r8a66597 *r8a66597,
+ unsigned long offset, u16 *buf,
+ int len)
+{
+ if (r8a66597->pdata->on_chip) {
+ unsigned long fifoaddr = r8a66597->reg + offset;
+ unsigned long count;
+ union {
+ unsigned long dword;
+ unsigned char byte[4];
+ } data;
+ unsigned char *pb;
+ int i;
+
+ count = len / 4;
+ insl(fifoaddr, buf, count);
+
+ if (len & 0x00000003) {
+ data.dword = inl(fifoaddr);
+ pb = (unsigned char *)buf + count * 4;
+ for (i = 0; i < (len & 0x00000003); i++)
+ pb[i] = data.byte[i];
+ }
+ } else {
+ len = (len + 1) / 2;
+ insw(r8a66597->reg + offset, buf, len);
+ }
+}
+
+static inline void r8a66597_write(struct r8a66597 *r8a66597, u16 val,
+ unsigned long offset)
+{
+ outw(val, r8a66597->reg + offset);
+}
+
+static inline void r8a66597_write_fifo(struct r8a66597 *r8a66597,
+ unsigned long offset, u16 *buf,
+ int len)
+{
+ unsigned long fifoaddr = r8a66597->reg + offset;
+
+ if (r8a66597->pdata->on_chip) {
+ unsigned long count;
+ unsigned char *pb;
+ int i;
+
+ count = len / 4;
+ outsl(fifoaddr, buf, count);
+
+ if (len & 0x00000003) {
+ pb = (unsigned char *)buf + count * 4;
+ for (i = 0; i < (len & 0x00000003); i++) {
+ if (r8a66597_read(r8a66597, CFIFOSEL) & BIGEND)
+ outb(pb[i], fifoaddr + i);
+ else
+ outb(pb[i], fifoaddr + 3 - i);
+ }
+ }
+ } else {
+ int odd = len & 0x0001;
+
+ len = len / 2;
+ outsw(fifoaddr, buf, len);
+ if (unlikely(odd)) {
+ buf = &buf[len];
+ outb((unsigned char)*buf, fifoaddr);
+ }
+ }
+}
+
+static inline void r8a66597_mdfy(struct r8a66597 *r8a66597,
+ u16 val, u16 pat, unsigned long offset)
+{
+ u16 tmp;
+ tmp = r8a66597_read(r8a66597, offset);
+ tmp = tmp & (~pat);
+ tmp = tmp | val;
+ r8a66597_write(r8a66597, tmp, offset);
+}
+
+static inline u16 get_xtal_from_pdata(struct r8a66597_platdata *pdata)
+{
+ u16 clock = 0;
+
+ switch (pdata->xtal) {
+ case R8A66597_PLATDATA_XTAL_12MHZ:
+ clock = XTAL12;
+ break;
+ case R8A66597_PLATDATA_XTAL_24MHZ:
+ clock = XTAL24;
+ break;
+ case R8A66597_PLATDATA_XTAL_48MHZ:
+ clock = XTAL48;
+ break;
+ default:
+ printk(KERN_ERR "r8a66597: platdata clock is wrong.\n");
+ break;
+ }
+
+ return clock;
+}
+
+#define r8a66597_bclr(r8a66597, val, offset) \
+ r8a66597_mdfy(r8a66597, 0, val, offset)
+#define r8a66597_bset(r8a66597, val, offset) \
+ r8a66597_mdfy(r8a66597, val, 0, offset)
+
+#define get_pipectr_addr(pipenum) (PIPE1CTR + (pipenum - 1) * 2)
+
+#define enable_irq_ready(r8a66597, pipenum) \
+ enable_pipe_irq(r8a66597, pipenum, BRDYENB)
+#define disable_irq_ready(r8a66597, pipenum) \
+ disable_pipe_irq(r8a66597, pipenum, BRDYENB)
+#define enable_irq_empty(r8a66597, pipenum) \
+ enable_pipe_irq(r8a66597, pipenum, BEMPENB)
+#define disable_irq_empty(r8a66597, pipenum) \
+ disable_pipe_irq(r8a66597, pipenum, BEMPENB)
+#define enable_irq_nrdy(r8a66597, pipenum) \
+ enable_pipe_irq(r8a66597, pipenum, NRDYENB)
+#define disable_irq_nrdy(r8a66597, pipenum) \
+ disable_pipe_irq(r8a66597, pipenum, NRDYENB)
+
+#endif /* __R8A66597_H__ */
+
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 1a920c70b5a..f21ca7d27a4 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -336,13 +336,6 @@ config USB_R8A66597_HCD
To compile this driver as a module, choose M here: the
module will be called r8a66597-hcd.
-config SUPERH_ON_CHIP_R8A66597
- boolean "Enable SuperH on-chip R8A66597 USB"
- depends on USB_R8A66597_HCD && (CPU_SUBTYPE_SH7366 || CPU_SUBTYPE_SH7723 || CPU_SUBTYPE_SH7724)
- help
- This driver enables support for the on-chip R8A66597 in the
- SH7366, SH7723 and SH7724 processors.
-
config USB_WHCI_HCD
tristate "Wireless USB Host Controller Interface (WHCI) driver (EXPERIMENTAL)"
depends on EXPERIMENTAL
diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c
index e18f74946e6..749b5374282 100644
--- a/drivers/usb/host/r8a66597-hcd.c
+++ b/drivers/usb/host/r8a66597-hcd.c
@@ -91,43 +91,43 @@ static int r8a66597_clock_enable(struct r8a66597 *r8a66597)
u16 tmp;
int i = 0;
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597)
-#if defined(CONFIG_HAVE_CLK)
- clk_enable(r8a66597->clk);
+ if (r8a66597->pdata->on_chip) {
+#ifdef CONFIG_HAVE_CLK
+ clk_enable(r8a66597->clk);
#endif
- do {
- r8a66597_write(r8a66597, SCKE, SYSCFG0);
- tmp = r8a66597_read(r8a66597, SYSCFG0);
- if (i++ > 1000) {
- printk(KERN_ERR "r8a66597: register access fail.\n");
- return -ENXIO;
- }
- } while ((tmp & SCKE) != SCKE);
- r8a66597_write(r8a66597, 0x04, 0x02);
-#else
- do {
- r8a66597_write(r8a66597, USBE, SYSCFG0);
- tmp = r8a66597_read(r8a66597, SYSCFG0);
- if (i++ > 1000) {
- printk(KERN_ERR "r8a66597: register access fail.\n");
- return -ENXIO;
- }
- } while ((tmp & USBE) != USBE);
- r8a66597_bclr(r8a66597, USBE, SYSCFG0);
- r8a66597_mdfy(r8a66597, get_xtal_from_pdata(r8a66597->pdata), XTAL,
- SYSCFG0);
+ do {
+ r8a66597_write(r8a66597, SCKE, SYSCFG0);
+ tmp = r8a66597_read(r8a66597, SYSCFG0);
+ if (i++ > 1000) {
+ printk(KERN_ERR "r8a66597: reg access fail.\n");
+ return -ENXIO;
+ }
+ } while ((tmp & SCKE) != SCKE);
+ r8a66597_write(r8a66597, 0x04, 0x02);
+ } else {
+ do {
+ r8a66597_write(r8a66597, USBE, SYSCFG0);
+ tmp = r8a66597_read(r8a66597, SYSCFG0);
+ if (i++ > 1000) {
+ printk(KERN_ERR "r8a66597: reg access fail.\n");
+ return -ENXIO;
+ }
+ } while ((tmp & USBE) != USBE);
+ r8a66597_bclr(r8a66597, USBE, SYSCFG0);
+ r8a66597_mdfy(r8a66597, get_xtal_from_pdata(r8a66597->pdata),
+ XTAL, SYSCFG0);
- i = 0;
- r8a66597_bset(r8a66597, XCKE, SYSCFG0);
- do {
- msleep(1);
- tmp = r8a66597_read(r8a66597, SYSCFG0);
- if (i++ > 500) {
- printk(KERN_ERR "r8a66597: register access fail.\n");
- return -ENXIO;
- }
- } while ((tmp & SCKE) != SCKE);
-#endif /* #if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) */
+ i = 0;
+ r8a66597_bset(r8a66597, XCKE, SYSCFG0);
+ do {
+ msleep(1);
+ tmp = r8a66597_read(r8a66597, SYSCFG0);
+ if (i++ > 500) {
+ printk(KERN_ERR "r8a66597: reg access fail.\n");
+ return -ENXIO;
+ }
+ } while ((tmp & SCKE) != SCKE);
+ }
return 0;
}
@@ -136,15 +136,16 @@ static void r8a66597_clock_disable(struct r8a66597 *r8a66597)
{
r8a66597_bclr(r8a66597, SCKE, SYSCFG0);
udelay(1);
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597)
-#if defined(CONFIG_HAVE_CLK)
- clk_disable(r8a66597->clk);
-#endif
-#else
- r8a66597_bclr(r8a66597, PLLC, SYSCFG0);
- r8a66597_bclr(r8a66597, XCKE, SYSCFG0);
- r8a66597_bclr(r8a66597, USBE, SYSCFG0);
+
+ if (r8a66597->pdata->on_chip) {
+#ifdef CONFIG_HAVE_CLK
+ clk_disable(r8a66597->clk);
#endif
+ } else {
+ r8a66597_bclr(r8a66597, PLLC, SYSCFG0);
+ r8a66597_bclr(r8a66597, XCKE, SYSCFG0);
+ r8a66597_bclr(r8a66597, USBE, SYSCFG0);
+ }
}
static void r8a66597_enable_port(struct r8a66597 *r8a66597, int port)
@@ -205,7 +206,7 @@ static int enable_controller(struct r8a66597 *r8a66597)
r8a66597_bset(r8a66597, SIGNE | SACKE, INTENB1);
- for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++)
+ for (port = 0; port < r8a66597->max_root_hub; port++)
r8a66597_enable_port(r8a66597, port);
return 0;
@@ -218,7 +219,7 @@ static void disable_controller(struct r8a66597 *r8a66597)
r8a66597_write(r8a66597, 0, INTENB0);
r8a66597_write(r8a66597, 0, INTSTS0);
- for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++)
+ for (port = 0; port < r8a66597->max_root_hub; port++)
r8a66597_disable_port(r8a66597, port);
r8a66597_clock_disable(r8a66597);
@@ -249,11 +250,12 @@ static int is_hub_limit(char *devpath)
return ((strlen(devpath) >= 4) ? 1 : 0);
}
-static void get_port_number(char *devpath, u16 *root_port, u16 *hub_port)
+static void get_port_number(struct r8a66597 *r8a66597,
+ char *devpath, u16 *root_port, u16 *hub_port)
{
if (root_port) {
*root_port = (devpath[0] & 0x0F) - 1;
- if (*root_port >= R8A66597_MAX_ROOT_HUB)
+ if (*root_port >= r8a66597->max_root_hub)
printk(KERN_ERR "r8a66597: Illegal root port number.\n");
}
if (hub_port)
@@ -355,7 +357,8 @@ static int make_r8a66597_device(struct r8a66597 *r8a66597,
INIT_LIST_HEAD(&dev->device_list);
list_add_tail(&dev->device_list, &r8a66597->child_device);
- get_port_number(urb->dev->devpath, &dev->root_port, &dev->hub_port);
+ get_port_number(r8a66597, urb->dev->devpath,
+ &dev->root_port, &dev->hub_port);
if (!is_child_device(urb->dev->devpath))
r8a66597->root_hub[dev->root_port].dev = dev;
@@ -420,7 +423,7 @@ static void free_usb_address(struct r8a66597 *r8a66597,
list_del(&dev->device_list);
kfree(dev);
- for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++) {
+ for (port = 0; port < r8a66597->max_root_hub; port++) {
if (r8a66597->root_hub[port].dev == dev) {
r8a66597->root_hub[port].dev = NULL;
break;
@@ -495,10 +498,20 @@ static void r8a66597_pipe_toggle(struct r8a66597 *r8a66597,
r8a66597_bset(r8a66597, SQCLR, pipe->pipectr);
}
+static inline unsigned short mbw_value(struct r8a66597 *r8a66597)
+{
+ if (r8a66597->pdata->on_chip)
+ return MBW_32;
+ else
+ return MBW_16;
+}
+
/* this function must be called with interrupt disabled */
static inline void cfifo_change(struct r8a66597 *r8a66597, u16 pipenum)
{
- r8a66597_mdfy(r8a66597, MBW | pipenum, MBW | CURPIPE, CFIFOSEL);
+ unsigned short mbw = mbw_value(r8a66597);
+
+ r8a66597_mdfy(r8a66597, mbw | pipenum, mbw | CURPIPE, CFIFOSEL);
r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, pipenum);
}
@@ -506,11 +519,13 @@ static inline void cfifo_change(struct r8a66597 *r8a66597, u16 pipenum)
static inline void fifo_change_from_pipe(struct r8a66597 *r8a66597,
struct r8a66597_pipe *pipe)
{
+ unsigned short mbw = mbw_value(r8a66597);
+
cfifo_change(r8a66597, 0);
- r8a66597_mdfy(r8a66597, MBW | 0, MBW | CURPIPE, D0FIFOSEL);
- r8a66597_mdfy(r8a66597, MBW | 0, MBW | CURPIPE, D1FIFOSEL);
+ r8a66597_mdfy(r8a66597, mbw | 0, mbw | CURPIPE, D0FIFOSEL);
+ r8a66597_mdfy(r8a66597, mbw | 0, mbw | CURPIPE, D1FIFOSEL);
- r8a66597_mdfy(r8a66597, MBW | pipe->info.pipenum, MBW | CURPIPE,
+ r8a66597_mdfy(r8a66597, mbw | pipe->info.pipenum, mbw | CURPIPE,
pipe->fifosel);
r8a66597_reg_wait(r8a66597, pipe->fifosel, CURPIPE, pipe->info.pipenum);
}
@@ -742,9 +757,13 @@ static void enable_r8a66597_pipe_dma(struct r8a66597 *r8a66597,
struct r8a66597_pipe *pipe,
struct urb *urb)
{
-#if !defined(CONFIG_SUPERH_ON_CHIP_R8A66597)
int i;
struct r8a66597_pipe_info *info = &pipe->info;
+ unsigned short mbw = mbw_value(r8a66597);
+
+ /* pipe dma is only for external controlles */
+ if (r8a66597->pdata->on_chip)
+ return;
if ((pipe->info.pipenum != 0) && (info->type != R8A66597_INT)) {
for (i = 0; i < R8A66597_MAX_DMA_CHANNEL; i++) {
@@ -763,8 +782,8 @@ static void enable_r8a66597_pipe_dma(struct r8a66597 *r8a66597,
set_pipe_reg_addr(pipe, i);
cfifo_change(r8a66597, 0);
- r8a66597_mdfy(r8a66597, MBW | pipe->info.pipenum,
- MBW | CURPIPE, pipe->fifosel);
+ r8a66597_mdfy(r8a66597, mbw | pipe->info.pipenum,
+ mbw | CURPIPE, pipe->fifosel);
r8a66597_reg_wait(r8a66597, pipe->fifosel, CURPIPE,
pipe->info.pipenum);
@@ -772,7 +791,6 @@ static void enable_r8a66597_pipe_dma(struct r8a66597 *r8a66597,
break;
}
}
-#endif /* #if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) */
}
/* this function must be called with interrupt disabled */
@@ -1769,7 +1787,7 @@ static void r8a66597_timer(unsigned long _r8a66597)
spin_lock_irqsave(&r8a66597->lock, flags);
- for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++)
+ for (port = 0; port < r8a66597->max_root_hub; port++)
r8a66597_root_hub_control(r8a66597, port);
spin_unlock_irqrestore(&r8a66597->lock, flags);
@@ -1807,7 +1825,7 @@ static void set_address_zero(struct r8a66597 *r8a66597, struct urb *urb)
u16 root_port, hub_port;
if (usb_address == 0) {
- get_port_number(urb->dev->devpath,
+ get_port_number(r8a66597, urb->dev->devpath,
&root_port, &hub_port);
set_devadd_reg(r8a66597, 0,
get_r8a66597_usb_speed(urb->dev->speed),
@@ -2082,7 +2100,7 @@ static int r8a66597_hub_status_data(struct usb_hcd *hcd, char *buf)
*buf = 0; /* initialize (no change) */
- for (i = 0; i < R8A66597_MAX_ROOT_HUB; i++) {
+ for (i = 0; i < r8a66597->max_root_hub; i++) {
if (r8a66597->root_hub[i].port & 0xffff0000)
*buf |= 1 << (i + 1);
}
@@ -2097,11 +2115,11 @@ static void r8a66597_hub_descriptor(struct r8a66597 *r8a66597,
{
desc->bDescriptorType = 0x29;
desc->bHubContrCurrent = 0;
- desc->bNbrPorts = R8A66597_MAX_ROOT_HUB;
+ desc->bNbrPorts = r8a66597->max_root_hub;
desc->bDescLength = 9;
desc->bPwrOn2PwrGood = 0;
desc->wHubCharacteristics = cpu_to_le16(0x0011);
- desc->bitmap[0] = ((1 << R8A66597_MAX_ROOT_HUB) - 1) << 1;
+ desc->bitmap[0] = ((1 << r8a66597->max_root_hub) - 1) << 1;
desc->bitmap[1] = ~0;
}
@@ -2129,7 +2147,7 @@ static int r8a66597_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
}
break;
case ClearPortFeature:
- if (wIndex > R8A66597_MAX_ROOT_HUB)
+ if (wIndex > r8a66597->max_root_hub)
goto error;
if (wLength != 0)
goto error;
@@ -2162,12 +2180,12 @@ static int r8a66597_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
*buf = 0x00;
break;
case GetPortStatus:
- if (wIndex > R8A66597_MAX_ROOT_HUB)
+ if (wIndex > r8a66597->max_root_hub)
goto error;
*(__le32 *)buf = cpu_to_le32(rh->port);
break;
case SetPortFeature:
- if (wIndex > R8A66597_MAX_ROOT_HUB)
+ if (wIndex > r8a66597->max_root_hub)
goto error;
if (wLength != 0)
goto error;
@@ -2216,7 +2234,7 @@ static int r8a66597_bus_suspend(struct usb_hcd *hcd)
dbg("%s", __func__);
- for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++) {
+ for (port = 0; port < r8a66597->max_root_hub; port++) {
struct r8a66597_root_hub *rh = &r8a66597->root_hub[port];
unsigned long dvstctr_reg = get_dvstctr_reg(port);
@@ -2247,7 +2265,7 @@ static int r8a66597_bus_resume(struct usb_hcd *hcd)
dbg("%s", __func__);
- for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++) {
+ for (port = 0; port < r8a66597->max_root_hub; port++) {
struct r8a66597_root_hub *rh = &r8a66597->root_hub[port];
unsigned long dvstctr_reg = get_dvstctr_reg(port);
@@ -2305,16 +2323,16 @@ static struct hc_driver r8a66597_hc_driver = {
};
#if defined(CONFIG_PM)
-static int r8a66597_suspend(struct platform_device *pdev, pm_message_t state)
+static int r8a66597_suspend(struct device *dev)
{
- struct r8a66597 *r8a66597 = dev_get_drvdata(&pdev->dev);
+ struct r8a66597 *r8a66597 = dev_get_drvdata(dev);
int port;
dbg("%s", __func__);
disable_controller(r8a66597);
- for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++) {
+ for (port = 0; port < r8a66597->max_root_hub; port++) {
struct r8a66597_root_hub *rh = &r8a66597->root_hub[port];
rh->port = 0x00000000;
@@ -2323,9 +2341,9 @@ static int r8a66597_suspend(struct platform_device *pdev, pm_message_t state)
return 0;
}
-static int r8a66597_resume(struct platform_device *pdev)
+static int r8a66597_resume(struct device *dev)
{
- struct r8a66597 *r8a66597 = dev_get_drvdata(&pdev->dev);
+ struct r8a66597 *r8a66597 = dev_get_drvdata(dev);
struct usb_hcd *hcd = r8a66597_to_hcd(r8a66597);
dbg("%s", __func__);
@@ -2335,9 +2353,17 @@ static int r8a66597_resume(struct platform_device *pdev)
return 0;
}
+
+static struct dev_pm_ops r8a66597_dev_pm_ops = {
+ .suspend = r8a66597_suspend,
+ .resume = r8a66597_resume,
+ .poweroff = r8a66597_suspend,
+ .restore = r8a66597_resume,
+};
+
+#define R8A66597_DEV_PM_OPS (&r8a66597_dev_pm_ops)
#else /* if defined(CONFIG_PM) */
-#define r8a66597_suspend NULL
-#define r8a66597_resume NULL
+#define R8A66597_DEV_PM_OPS NULL
#endif
static int __init_or_module r8a66597_remove(struct platform_device *pdev)
@@ -2348,8 +2374,9 @@ static int __init_or_module r8a66597_remove(struct platform_device *pdev)
del_timer_sync(&r8a66597->rh_timer);
usb_remove_hcd(hcd);
iounmap((void *)r8a66597->reg);
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK)
- clk_put(r8a66597->clk);
+#ifdef CONFIG_HAVE_CLK
+ if (r8a66597->pdata->on_chip)
+ clk_put(r8a66597->clk);
#endif
usb_put_hcd(hcd);
return 0;
@@ -2357,7 +2384,7 @@ static int __init_or_module r8a66597_remove(struct platform_device *pdev)
static int __devinit r8a66597_probe(struct platform_device *pdev)
{
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK)
+#ifdef CONFIG_HAVE_CLK
char clk_name[8];
#endif
struct resource *res = NULL, *ires;
@@ -2419,15 +2446,20 @@ static int __devinit r8a66597_probe(struct platform_device *pdev)
r8a66597->pdata = pdev->dev.platform_data;
r8a66597->irq_sense_low = irq_trigger == IRQF_TRIGGER_LOW;
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK)
- snprintf(clk_name, sizeof(clk_name), "usb%d", pdev->id);
- r8a66597->clk = clk_get(&pdev->dev, clk_name);
- if (IS_ERR(r8a66597->clk)) {
- dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name);
- ret = PTR_ERR(r8a66597->clk);
- goto clean_up2;
- }
+ if (r8a66597->pdata->on_chip) {
+#ifdef CONFIG_HAVE_CLK
+ snprintf(clk_name, sizeof(clk_name), "usb%d", pdev->id);
+ r8a66597->clk = clk_get(&pdev->dev, clk_name);
+ if (IS_ERR(r8a66597->clk)) {
+ dev_err(&pdev->dev, "cannot get clock \"%s\"\n",
+ clk_name);
+ ret = PTR_ERR(r8a66597->clk);
+ goto clean_up2;
+ }
#endif
+ r8a66597->max_root_hub = 1;
+ } else
+ r8a66597->max_root_hub = 2;
spin_lock_init(&r8a66597->lock);
init_timer(&r8a66597->rh_timer);
@@ -2457,8 +2489,9 @@ static int __devinit r8a66597_probe(struct platform_device *pdev)
return 0;
clean_up3:
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK)
- clk_put(r8a66597->clk);
+#ifdef CONFIG_HAVE_CLK
+ if (r8a66597->pdata->on_chip)
+ clk_put(r8a66597->clk);
clean_up2:
#endif
usb_put_hcd(hcd);
@@ -2473,11 +2506,10 @@ clean_up:
static struct platform_driver r8a66597_driver = {
.probe = r8a66597_probe,
.remove = r8a66597_remove,
- .suspend = r8a66597_suspend,
- .resume = r8a66597_resume,
.driver = {
.name = (char *) hcd_name,
.owner = THIS_MODULE,
+ .pm = R8A66597_DEV_PM_OPS,
},
};
diff --git a/drivers/usb/host/r8a66597.h b/drivers/usb/host/r8a66597.h
index d72680b433f..228e3fb2385 100644
--- a/drivers/usb/host/r8a66597.h
+++ b/drivers/usb/host/r8a66597.h
@@ -26,390 +26,16 @@
#ifndef __R8A66597_H__
#define __R8A66597_H__
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK)
+#ifdef CONFIG_HAVE_CLK
#include <linux/clk.h>
#endif
#include <linux/usb/r8a66597.h>
-#define SYSCFG0 0x00
-#define SYSCFG1 0x02
-#define SYSSTS0 0x04
-#define SYSSTS1 0x06
-#define DVSTCTR0 0x08
-#define DVSTCTR1 0x0A
-#define TESTMODE 0x0C
-#define PINCFG 0x0E
-#define DMA0CFG 0x10
-#define DMA1CFG 0x12
-#define CFIFO 0x14
-#define D0FIFO 0x18
-#define D1FIFO 0x1C
-#define CFIFOSEL 0x20
-#define CFIFOCTR 0x22
-#define CFIFOSIE 0x24
-#define D0FIFOSEL 0x28
-#define D0FIFOCTR 0x2A
-#define D1FIFOSEL 0x2C
-#define D1FIFOCTR 0x2E
-#define INTENB0 0x30
-#define INTENB1 0x32
-#define INTENB2 0x34
-#define BRDYENB 0x36
-#define NRDYENB 0x38
-#define BEMPENB 0x3A
-#define SOFCFG 0x3C
-#define INTSTS0 0x40
-#define INTSTS1 0x42
-#define INTSTS2 0x44
-#define BRDYSTS 0x46
-#define NRDYSTS 0x48
-#define BEMPSTS 0x4A
-#define FRMNUM 0x4C
-#define UFRMNUM 0x4E
-#define USBADDR 0x50
-#define USBREQ 0x54
-#define USBVAL 0x56
-#define USBINDX 0x58
-#define USBLENG 0x5A
-#define DCPCFG 0x5C
-#define DCPMAXP 0x5E
-#define DCPCTR 0x60
-#define PIPESEL 0x64
-#define PIPECFG 0x68
-#define PIPEBUF 0x6A
-#define PIPEMAXP 0x6C
-#define PIPEPERI 0x6E
-#define PIPE1CTR 0x70
-#define PIPE2CTR 0x72
-#define PIPE3CTR 0x74
-#define PIPE4CTR 0x76
-#define PIPE5CTR 0x78
-#define PIPE6CTR 0x7A
-#define PIPE7CTR 0x7C
-#define PIPE8CTR 0x7E
-#define PIPE9CTR 0x80
-#define PIPE1TRE 0x90
-#define PIPE1TRN 0x92
-#define PIPE2TRE 0x94
-#define PIPE2TRN 0x96
-#define PIPE3TRE 0x98
-#define PIPE3TRN 0x9A
-#define PIPE4TRE 0x9C
-#define PIPE4TRN 0x9E
-#define PIPE5TRE 0xA0
-#define PIPE5TRN 0xA2
-#define DEVADD0 0xD0
-#define DEVADD1 0xD2
-#define DEVADD2 0xD4
-#define DEVADD3 0xD6
-#define DEVADD4 0xD8
-#define DEVADD5 0xDA
-#define DEVADD6 0xDC
-#define DEVADD7 0xDE
-#define DEVADD8 0xE0
-#define DEVADD9 0xE2
-#define DEVADDA 0xE4
-
-/* System Configuration Control Register */
-#define XTAL 0xC000 /* b15-14: Crystal selection */
-#define XTAL48 0x8000 /* 48MHz */
-#define XTAL24 0x4000 /* 24MHz */
-#define XTAL12 0x0000 /* 12MHz */
-#define XCKE 0x2000 /* b13: External clock enable */
-#define PLLC 0x0800 /* b11: PLL control */
-#define SCKE 0x0400 /* b10: USB clock enable */
-#define PCSDIS 0x0200 /* b9: not CS wakeup */
-#define LPSME 0x0100 /* b8: Low power sleep mode */
-#define HSE 0x0080 /* b7: Hi-speed enable */
-#define DCFM 0x0040 /* b6: Controller function select */
-#define DRPD 0x0020 /* b5: D+/- pull down control */
-#define DPRPU 0x0010 /* b4: D+ pull up control */
-#define USBE 0x0001 /* b0: USB module operation enable */
-
-/* System Configuration Status Register */
-#define OVCBIT 0x8000 /* b15-14: Over-current bit */
-#define OVCMON 0xC000 /* b15-14: Over-current monitor */
-#define SOFEA 0x0020 /* b5: SOF monitor */
-#define IDMON 0x0004 /* b3: ID-pin monitor */
-#define LNST 0x0003 /* b1-0: D+, D- line status */
-#define SE1 0x0003 /* SE1 */
-#define FS_KSTS 0x0002 /* Full-Speed K State */
-#define FS_JSTS 0x0001 /* Full-Speed J State */
-#define LS_JSTS 0x0002 /* Low-Speed J State */
-#define LS_KSTS 0x0001 /* Low-Speed K State */
-#define SE0 0x0000 /* SE0 */
-
-/* Device State Control Register */
-#define EXTLP0 0x0400 /* b10: External port */
-#define VBOUT 0x0200 /* b9: VBUS output */
-#define WKUP 0x0100 /* b8: Remote wakeup */
-#define RWUPE 0x0080 /* b7: Remote wakeup sense */
-#define USBRST 0x0040 /* b6: USB reset enable */
-#define RESUME 0x0020 /* b5: Resume enable */
-#define UACT 0x0010 /* b4: USB bus enable */
-#define RHST 0x0007 /* b1-0: Reset handshake status */
-#define HSPROC 0x0004 /* HS handshake is processing */
-#define HSMODE 0x0003 /* Hi-Speed mode */
-#define FSMODE 0x0002 /* Full-Speed mode */
-#define LSMODE 0x0001 /* Low-Speed mode */
-#define UNDECID 0x0000 /* Undecided */
-
-/* Test Mode Register */
-#define UTST 0x000F /* b3-0: Test select */
-#define H_TST_PACKET 0x000C /* HOST TEST Packet */
-#define H_TST_SE0_NAK 0x000B /* HOST TEST SE0 NAK */
-#define H_TST_K 0x000A /* HOST TEST K */
-#define H_TST_J 0x0009 /* HOST TEST J */
-#define H_TST_NORMAL 0x0000 /* HOST Normal Mode */
-#define P_TST_PACKET 0x0004 /* PERI TEST Packet */
-#define P_TST_SE0_NAK 0x0003 /* PERI TEST SE0 NAK */
-#define P_TST_K 0x0002 /* PERI TEST K */
-#define P_TST_J 0x0001 /* PERI TEST J */
-#define P_TST_NORMAL 0x0000 /* PERI Normal Mode */
-
-/* Data Pin Configuration Register */
-#define LDRV 0x8000 /* b15: Drive Current Adjust */
-#define VIF1 0x0000 /* VIF = 1.8V */
-#define VIF3 0x8000 /* VIF = 3.3V */
-#define INTA 0x0001 /* b1: USB INT-pin active */
-
-/* DMAx Pin Configuration Register */
-#define DREQA 0x4000 /* b14: Dreq active select */
-#define BURST 0x2000 /* b13: Burst mode */
-#define DACKA 0x0400 /* b10: Dack active select */
-#define DFORM 0x0380 /* b9-7: DMA mode select */
-#define CPU_ADR_RD_WR 0x0000 /* Address + RD/WR mode (CPU bus) */
-#define CPU_DACK_RD_WR 0x0100 /* DACK + RD/WR mode (CPU bus) */
-#define CPU_DACK_ONLY 0x0180 /* DACK only mode (CPU bus) */
-#define SPLIT_DACK_ONLY 0x0200 /* DACK only mode (SPLIT bus) */
-#define DENDA 0x0040 /* b6: Dend active select */
-#define PKTM 0x0020 /* b5: Packet mode */
-#define DENDE 0x0010 /* b4: Dend enable */
-#define OBUS 0x0004 /* b2: OUTbus mode */
-
-/* CFIFO/DxFIFO Port Select Register */
-#define RCNT 0x8000 /* b15: Read count mode */
-#define REW 0x4000 /* b14: Buffer rewind */
-#define DCLRM 0x2000 /* b13: DMA buffer clear mode */
-#define DREQE 0x1000 /* b12: DREQ output enable */
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597)
-#define MBW 0x0800
-#else
-#define MBW 0x0400 /* b10: Maximum bit width for FIFO access */
-#endif
-#define MBW_8 0x0000 /* 8bit */
-#define MBW_16 0x0400 /* 16bit */
-#define BIGEND 0x0100 /* b8: Big endian mode */
-#define BYTE_LITTLE 0x0000 /* little dendian */
-#define BYTE_BIG 0x0100 /* big endifan */
-#define ISEL 0x0020 /* b5: DCP FIFO port direction select */
-#define CURPIPE 0x000F /* b2-0: PIPE select */
-
-/* CFIFO/DxFIFO Port Control Register */
-#define BVAL 0x8000 /* b15: Buffer valid flag */
-#define BCLR 0x4000 /* b14: Buffer clear */
-#define FRDY 0x2000 /* b13: FIFO ready */
-#define DTLN 0x0FFF /* b11-0: FIFO received data length */
-
-/* Interrupt Enable Register 0 */
-#define VBSE 0x8000 /* b15: VBUS interrupt */
-#define RSME 0x4000 /* b14: Resume interrupt */
-#define SOFE 0x2000 /* b13: Frame update interrupt */
-#define DVSE 0x1000 /* b12: Device state transition interrupt */
-#define CTRE 0x0800 /* b11: Control transfer stage transition interrupt */
-#define BEMPE 0x0400 /* b10: Buffer empty interrupt */
-#define NRDYE 0x0200 /* b9: Buffer not ready interrupt */
-#define BRDYE 0x0100 /* b8: Buffer ready interrupt */
-
-/* Interrupt Enable Register 1 */
-#define OVRCRE 0x8000 /* b15: Over-current interrupt */
-#define BCHGE 0x4000 /* b14: USB us chenge interrupt */
-#define DTCHE 0x1000 /* b12: Detach sense interrupt */
-#define ATTCHE 0x0800 /* b11: Attach sense interrupt */
-#define EOFERRE 0x0040 /* b6: EOF error interrupt */
-#define SIGNE 0x0020 /* b5: SETUP IGNORE interrupt */
-#define SACKE 0x0010 /* b4: SETUP ACK interrupt */
-
-/* BRDY Interrupt Enable/Status Register */
-#define BRDY9 0x0200 /* b9: PIPE9 */
-#define BRDY8 0x0100 /* b8: PIPE8 */
-#define BRDY7 0x0080 /* b7: PIPE7 */
-#define BRDY6 0x0040 /* b6: PIPE6 */
-#define BRDY5 0x0020 /* b5: PIPE5 */
-#define BRDY4 0x0010 /* b4: PIPE4 */
-#define BRDY3 0x0008 /* b3: PIPE3 */
-#define BRDY2 0x0004 /* b2: PIPE2 */
-#define BRDY1 0x0002 /* b1: PIPE1 */
-#define BRDY0 0x0001 /* b1: PIPE0 */
-
-/* NRDY Interrupt Enable/Status Register */
-#define NRDY9 0x0200 /* b9: PIPE9 */
-#define NRDY8 0x0100 /* b8: PIPE8 */
-#define NRDY7 0x0080 /* b7: PIPE7 */
-#define NRDY6 0x0040 /* b6: PIPE6 */
-#define NRDY5 0x0020 /* b5: PIPE5 */
-#define NRDY4 0x0010 /* b4: PIPE4 */
-#define NRDY3 0x0008 /* b3: PIPE3 */
-#define NRDY2 0x0004 /* b2: PIPE2 */
-#define NRDY1 0x0002 /* b1: PIPE1 */
-#define NRDY0 0x0001 /* b1: PIPE0 */
-
-/* BEMP Interrupt Enable/Status Register */
-#define BEMP9 0x0200 /* b9: PIPE9 */
-#define BEMP8 0x0100 /* b8: PIPE8 */
-#define BEMP7 0x0080 /* b7: PIPE7 */
-#define BEMP6 0x0040 /* b6: PIPE6 */
-#define BEMP5 0x0020 /* b5: PIPE5 */
-#define BEMP4 0x0010 /* b4: PIPE4 */
-#define BEMP3 0x0008 /* b3: PIPE3 */
-#define BEMP2 0x0004 /* b2: PIPE2 */
-#define BEMP1 0x0002 /* b1: PIPE1 */
-#define BEMP0 0x0001 /* b0: PIPE0 */
-
-/* SOF Pin Configuration Register */
-#define TRNENSEL 0x0100 /* b8: Select transaction enable period */
-#define BRDYM 0x0040 /* b6: BRDY clear timing */
-#define INTL 0x0020 /* b5: Interrupt sense select */
-#define EDGESTS 0x0010 /* b4: */
-#define SOFMODE 0x000C /* b3-2: SOF pin select */
-#define SOF_125US 0x0008 /* SOF OUT 125us Frame Signal */
-#define SOF_1MS 0x0004 /* SOF OUT 1ms Frame Signal */
-#define SOF_DISABLE 0x0000 /* SOF OUT Disable */
-
-/* Interrupt Status Register 0 */
-#define VBINT 0x8000 /* b15: VBUS interrupt */
-#define RESM 0x4000 /* b14: Resume interrupt */
-#define SOFR 0x2000 /* b13: SOF frame update interrupt */
-#define DVST 0x1000 /* b12: Device state transition interrupt */
-#define CTRT 0x0800 /* b11: Control transfer stage transition interrupt */
-#define BEMP 0x0400 /* b10: Buffer empty interrupt */
-#define NRDY 0x0200 /* b9: Buffer not ready interrupt */
-#define BRDY 0x0100 /* b8: Buffer ready interrupt */
-#define VBSTS 0x0080 /* b7: VBUS input port */
-#define DVSQ 0x0070 /* b6-4: Device state */
-#define DS_SPD_CNFG 0x0070 /* Suspend Configured */
-#define DS_SPD_ADDR 0x0060 /* Suspend Address */
-#define DS_SPD_DFLT 0x0050 /* Suspend Default */
-#define DS_SPD_POWR 0x0040 /* Suspend Powered */
-#define DS_SUSP 0x0040 /* Suspend */
-#define DS_CNFG 0x0030 /* Configured */
-#define DS_ADDS 0x0020 /* Address */
-#define DS_DFLT 0x0010 /* Default */
-#define DS_POWR 0x0000 /* Powered */
-#define DVSQS 0x0030 /* b5-4: Device state */
-#define VALID 0x0008 /* b3: Setup packet detected flag */
-#define CTSQ 0x0007 /* b2-0: Control transfer stage */
-#define CS_SQER 0x0006 /* Sequence error */
-#define CS_WRND 0x0005 /* Control write nodata status stage */
-#define CS_WRSS 0x0004 /* Control write status stage */
-#define CS_WRDS 0x0003 /* Control write data stage */
-#define CS_RDSS 0x0002 /* Control read status stage */
-#define CS_RDDS 0x0001 /* Control read data stage */
-#define CS_IDST 0x0000 /* Idle or setup stage */
-
-/* Interrupt Status Register 1 */
-#define OVRCR 0x8000 /* b15: Over-current interrupt */
-#define BCHG 0x4000 /* b14: USB bus chenge interrupt */
-#define DTCH 0x1000 /* b12: Detach sense interrupt */
-#define ATTCH 0x0800 /* b11: Attach sense interrupt */
-#define EOFERR 0x0040 /* b6: EOF-error interrupt */
-#define SIGN 0x0020 /* b5: Setup ignore interrupt */
-#define SACK 0x0010 /* b4: Setup acknowledge interrupt */
-
-/* Frame Number Register */
-#define OVRN 0x8000 /* b15: Overrun error */
-#define CRCE 0x4000 /* b14: Received data error */
-#define FRNM 0x07FF /* b10-0: Frame number */
-
-/* Micro Frame Number Register */
-#define UFRNM 0x0007 /* b2-0: Micro frame number */
-
-/* Default Control Pipe Maxpacket Size Register */
-/* Pipe Maxpacket Size Register */
-#define DEVSEL 0xF000 /* b15-14: Device address select */
-#define MAXP 0x007F /* b6-0: Maxpacket size of default control pipe */
-
-/* Default Control Pipe Control Register */
-#define BSTS 0x8000 /* b15: Buffer status */
-#define SUREQ 0x4000 /* b14: Send USB request */
-#define CSCLR 0x2000 /* b13: complete-split status clear */
-#define CSSTS 0x1000 /* b12: complete-split status */
-#define SUREQCLR 0x0800 /* b11: stop setup request */
-#define SQCLR 0x0100 /* b8: Sequence toggle bit clear */
-#define SQSET 0x0080 /* b7: Sequence toggle bit set */
-#define SQMON 0x0040 /* b6: Sequence toggle bit monitor */
-#define PBUSY 0x0020 /* b5: pipe busy */
-#define PINGE 0x0010 /* b4: ping enable */
-#define CCPL 0x0004 /* b2: Enable control transfer complete */
-#define PID 0x0003 /* b1-0: Response PID */
-#define PID_STALL11 0x0003 /* STALL */
-#define PID_STALL 0x0002 /* STALL */
-#define PID_BUF 0x0001 /* BUF */
-#define PID_NAK 0x0000 /* NAK */
-
-/* Pipe Window Select Register */
-#define PIPENM 0x0007 /* b2-0: Pipe select */
-
-/* Pipe Configuration Register */
-#define R8A66597_TYP 0xC000 /* b15-14: Transfer type */
-#define R8A66597_ISO 0xC000 /* Isochronous */
-#define R8A66597_INT 0x8000 /* Interrupt */
-#define R8A66597_BULK 0x4000 /* Bulk */
-#define R8A66597_BFRE 0x0400 /* b10: Buffer ready interrupt mode select */
-#define R8A66597_DBLB 0x0200 /* b9: Double buffer mode select */
-#define R8A66597_CNTMD 0x0100 /* b8: Continuous transfer mode select */
-#define R8A66597_SHTNAK 0x0080 /* b7: Transfer end NAK */
-#define R8A66597_DIR 0x0010 /* b4: Transfer direction select */
-#define R8A66597_EPNUM 0x000F /* b3-0: Eendpoint number select */
-
-/* Pipe Buffer Configuration Register */
-#define BUFSIZE 0x7C00 /* b14-10: Pipe buffer size */
-#define BUFNMB 0x007F /* b6-0: Pipe buffer number */
-#define PIPE0BUF 256
-#define PIPExBUF 64
-
-/* Pipe Maxpacket Size Register */
-#define MXPS 0x07FF /* b10-0: Maxpacket size */
-
-/* Pipe Cycle Configuration Register */
-#define IFIS 0x1000 /* b12: Isochronous in-buffer flush mode select */
-#define IITV 0x0007 /* b2-0: Isochronous interval */
-
-/* Pipex Control Register */
-#define BSTS 0x8000 /* b15: Buffer status */
-#define INBUFM 0x4000 /* b14: IN buffer monitor (Only for PIPE1 to 5) */
-#define CSCLR 0x2000 /* b13: complete-split status clear */
-#define CSSTS 0x1000 /* b12: complete-split status */
-#define ATREPM 0x0400 /* b10: Auto repeat mode */
-#define ACLRM 0x0200 /* b9: Out buffer auto clear mode */
-#define SQCLR 0x0100 /* b8: Sequence toggle bit clear */
-#define SQSET 0x0080 /* b7: Sequence toggle bit set */
-#define SQMON 0x0040 /* b6: Sequence toggle bit monitor */
-#define PBUSY 0x0020 /* b5: pipe busy */
-#define PID 0x0003 /* b1-0: Response PID */
-
-/* PIPExTRE */
-#define TRENB 0x0200 /* b9: Transaction counter enable */
-#define TRCLR 0x0100 /* b8: Transaction counter clear */
-
-/* PIPExTRN */
-#define TRNCNT 0xFFFF /* b15-0: Transaction counter */
-
-/* DEVADDx */
-#define UPPHUB 0x7800
-#define HUBPORT 0x0700
-#define USBSPD 0x00C0
-#define RTPORT 0x0001
-
#define R8A66597_MAX_NUM_PIPE 10
#define R8A66597_BUF_BSIZE 8
#define R8A66597_MAX_DEVICE 10
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597)
-#define R8A66597_MAX_ROOT_HUB 1
-#else
#define R8A66597_MAX_ROOT_HUB 2
-#endif
#define R8A66597_MAX_SAMPLING 5
#define R8A66597_RH_POLL_TIME 10
#define R8A66597_MAX_DMA_CHANNEL 2
@@ -487,7 +113,7 @@ struct r8a66597_root_hub {
struct r8a66597 {
spinlock_t lock;
unsigned long reg;
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK)
+#ifdef CONFIG_HAVE_CLK
struct clk *clk;
#endif
struct r8a66597_platdata *pdata;
@@ -504,6 +130,7 @@ struct r8a66597 {
unsigned short interval_map;
unsigned char pipe_cnt[R8A66597_MAX_NUM_PIPE];
unsigned char dma_map;
+ unsigned int max_root_hub;
struct list_head child_device;
unsigned long child_connect_map[4];
@@ -550,21 +177,22 @@ static inline void r8a66597_read_fifo(struct r8a66597 *r8a66597,
unsigned long offset, u16 *buf,
int len)
{
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597)
unsigned long fifoaddr = r8a66597->reg + offset;
unsigned long count;
- count = len / 4;
- insl(fifoaddr, buf, count);
+ if (r8a66597->pdata->on_chip) {
+ count = len / 4;
+ insl(fifoaddr, buf, count);
- if (len & 0x00000003) {
- unsigned long tmp = inl(fifoaddr);
- memcpy((unsigned char *)buf + count * 4, &tmp, len & 0x03);
+ if (len & 0x00000003) {
+ unsigned long tmp = inl(fifoaddr);
+ memcpy((unsigned char *)buf + count * 4, &tmp,
+ len & 0x03);
+ }
+ } else {
+ len = (len + 1) / 2;
+ insw(fifoaddr, buf, len);
}
-#else
- len = (len + 1) / 2;
- insw(r8a66597->reg + offset, buf, len);
-#endif
}
static inline void r8a66597_write(struct r8a66597 *r8a66597, u16 val,
@@ -578,33 +206,33 @@ static inline void r8a66597_write_fifo(struct r8a66597 *r8a66597,
int len)
{
unsigned long fifoaddr = r8a66597->reg + offset;
-#if defined(CONFIG_SUPERH_ON_CHIP_R8A66597)
unsigned long count;
unsigned char *pb;
int i;
- count = len / 4;
- outsl(fifoaddr, buf, count);
+ if (r8a66597->pdata->on_chip) {
+ count = len / 4;
+ outsl(fifoaddr, buf, count);
+
+ if (len & 0x00000003) {
+ pb = (unsigned char *)buf + count * 4;
+ for (i = 0; i < (len & 0x00000003); i++) {
+ if (r8a66597_read(r8a66597, CFIFOSEL) & BIGEND)
+ outb(pb[i], fifoaddr + i);
+ else
+ outb(pb[i], fifoaddr + 3 - i);
+ }
+ }
+ } else {
+ int odd = len & 0x0001;
- if (len & 0x00000003) {
- pb = (unsigned char *)buf + count * 4;
- for (i = 0; i < (len & 0x00000003); i++) {
- if (r8a66597_read(r8a66597, CFIFOSEL) & BIGEND)
- outb(pb[i], fifoaddr + i);
- else
- outb(pb[i], fifoaddr + 3 - i);
+ len = len / 2;
+ outsw(fifoaddr, buf, len);
+ if (unlikely(odd)) {
+ buf = &buf[len];
+ outb((unsigned char)*buf, fifoaddr);
}
}
-#else
- int odd = len & 0x0001;
-
- len = len / 2;
- outsw(fifoaddr, buf, len);
- if (unlikely(odd)) {
- buf = &buf[len];
- outb((unsigned char)*buf, fifoaddr);
- }
-#endif
}
static inline void r8a66597_mdfy(struct r8a66597 *r8a66597,
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index cef3e1d9b92..11af4cb8924 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -1869,7 +1869,7 @@ config FB_W100
config FB_SH_MOBILE_LCDC
tristate "SuperH Mobile LCDC framebuffer support"
- depends on FB && SUPERH
+ depends on FB && SUPERH && HAVE_CLK
select FB_SYS_FILLRECT
select FB_SYS_COPYAREA
select FB_SYS_IMAGEBLIT
diff --git a/drivers/video/omap/dispc.c b/drivers/video/omap/dispc.c
index 148cbcc3960..915439dc05a 100644
--- a/drivers/video/omap/dispc.c
+++ b/drivers/video/omap/dispc.c
@@ -212,9 +212,9 @@ static void enable_rfbi_mode(int enable)
dispc_write_reg(DISPC_CONTROL, l);
/* Set bypass mode in RFBI module */
- l = __raw_readl(IO_ADDRESS(RFBI_CONTROL));
+ l = __raw_readl(OMAP2_IO_ADDRESS(RFBI_CONTROL));
l |= enable ? 0 : (1 << 1);
- __raw_writel(l, IO_ADDRESS(RFBI_CONTROL));
+ __raw_writel(l, OMAP2_IO_ADDRESS(RFBI_CONTROL));
}
static void set_lcd_data_lines(int data_lines)
@@ -1421,7 +1421,7 @@ static int omap_dispc_init(struct omapfb_device *fbdev, int ext_mode,
}
/* L3 firewall setting: enable access to OCM RAM */
- __raw_writel(0x402000b0, IO_ADDRESS(0x680050a0));
+ __raw_writel(0x402000b0, OMAP2_IO_ADDRESS(0x680050a0));
if ((r = alloc_palette_ram()) < 0)
goto fail2;
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c
index 07f22b62563..3ad5157f989 100644
--- a/drivers/video/sh_mobile_lcdcfb.c
+++ b/drivers/video/sh_mobile_lcdcfb.c
@@ -14,6 +14,7 @@
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/clk.h>
+#include <linux/pm_runtime.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
@@ -22,35 +23,8 @@
#include <asm/atomic.h>
#define PALETTE_NR 16
-
-struct sh_mobile_lcdc_priv;
-struct sh_mobile_lcdc_chan {
- struct sh_mobile_lcdc_priv *lcdc;
- unsigned long *reg_offs;
- unsigned long ldmt1r_value;
- unsigned long enabled; /* ME and SE in LDCNT2R */
- struct sh_mobile_lcdc_chan_cfg cfg;
- u32 pseudo_palette[PALETTE_NR];
- struct fb_info *info;
- dma_addr_t dma_handle;
- struct fb_deferred_io defio;
- struct scatterlist *sglist;
- unsigned long frame_end;
- wait_queue_head_t frame_end_wait;
-};
-
-struct sh_mobile_lcdc_priv {
- void __iomem *base;
- int irq;
-#ifdef CONFIG_HAVE_CLK
- atomic_t clk_usecnt;
- struct clk *dot_clk;
- struct clk *clk;
-#endif
- unsigned long lddckr;
- struct sh_mobile_lcdc_chan ch[2];
- int started;
-};
+#define SIDE_B_OFFSET 0x1000
+#define MIRROR_OFFSET 0x2000
/* shared registers */
#define _LDDCKR 0x410
@@ -59,17 +33,30 @@ struct sh_mobile_lcdc_priv {
#define _LDSR 0x46c
#define _LDCNT1R 0x470
#define _LDCNT2R 0x474
+#define _LDRCNTR 0x478
#define _LDDDSR 0x47c
#define _LDDWD0R 0x800
#define _LDDRDR 0x840
#define _LDDWAR 0x900
#define _LDDRAR 0x904
+/* shared registers and their order for context save/restore */
+static int lcdc_shared_regs[] = {
+ _LDDCKR,
+ _LDDCKSTPR,
+ _LDINTR,
+ _LDDDSR,
+ _LDCNT1R,
+ _LDCNT2R,
+};
+#define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs)
+
/* per-channel registers */
enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R,
- LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR };
+ LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR,
+ NR_CH_REGS };
-static unsigned long lcdc_offs_mainlcd[] = {
+static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = {
[LDDCKPAT1R] = 0x400,
[LDDCKPAT2R] = 0x404,
[LDMT1R] = 0x418,
@@ -87,7 +74,7 @@ static unsigned long lcdc_offs_mainlcd[] = {
[LDPMR] = 0x460,
};
-static unsigned long lcdc_offs_sublcd[] = {
+static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = {
[LDDCKPAT1R] = 0x408,
[LDDCKPAT2R] = 0x40c,
[LDMT1R] = 0x600,
@@ -110,12 +97,80 @@ static unsigned long lcdc_offs_sublcd[] = {
#define DISPLAY_BEU 0x00000008
#define LCDC_ENABLE 0x00000001
#define LDINTR_FE 0x00000400
+#define LDINTR_VSE 0x00000200
+#define LDINTR_VEE 0x00000100
#define LDINTR_FS 0x00000004
+#define LDINTR_VSS 0x00000002
+#define LDINTR_VES 0x00000001
+#define LDRCNTR_SRS 0x00020000
+#define LDRCNTR_SRC 0x00010000
+#define LDRCNTR_MRS 0x00000002
+#define LDRCNTR_MRC 0x00000001
+
+struct sh_mobile_lcdc_priv;
+struct sh_mobile_lcdc_chan {
+ struct sh_mobile_lcdc_priv *lcdc;
+ unsigned long *reg_offs;
+ unsigned long ldmt1r_value;
+ unsigned long enabled; /* ME and SE in LDCNT2R */
+ struct sh_mobile_lcdc_chan_cfg cfg;
+ u32 pseudo_palette[PALETTE_NR];
+ unsigned long saved_ch_regs[NR_CH_REGS];
+ struct fb_info *info;
+ dma_addr_t dma_handle;
+ struct fb_deferred_io defio;
+ struct scatterlist *sglist;
+ unsigned long frame_end;
+ unsigned long pan_offset;
+ unsigned long new_pan_offset;
+ wait_queue_head_t frame_end_wait;
+};
+
+struct sh_mobile_lcdc_priv {
+ void __iomem *base;
+ int irq;
+ atomic_t hw_usecnt;
+ struct device *dev;
+ struct clk *dot_clk;
+ unsigned long lddckr;
+ struct sh_mobile_lcdc_chan ch[2];
+ unsigned long saved_shared_regs[NR_SHARED_REGS];
+ int started;
+};
+
+static bool banked(int reg_nr)
+{
+ switch (reg_nr) {
+ case LDMT1R:
+ case LDMT2R:
+ case LDMT3R:
+ case LDDFR:
+ case LDSM1R:
+ case LDSA1R:
+ case LDMLSR:
+ case LDHCNR:
+ case LDHSYNR:
+ case LDVLNR:
+ case LDVSYNR:
+ return true;
+ }
+ return false;
+}
static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan,
int reg_nr, unsigned long data)
{
iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr]);
+ if (banked(reg_nr))
+ iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] +
+ SIDE_B_OFFSET);
+}
+
+static void lcdc_write_chan_mirror(struct sh_mobile_lcdc_chan *chan,
+ int reg_nr, unsigned long data)
+{
+ iowrite32(data, chan->lcdc->base + chan->reg_offs[reg_nr] +
+ MIRROR_OFFSET);
}
static unsigned long lcdc_read_chan(struct sh_mobile_lcdc_chan *chan,
@@ -156,6 +211,7 @@ static void lcdc_sys_write_index(void *handle, unsigned long data)
lcdc_write(ch->lcdc, _LDDWD0R, data | 0x10000000);
lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0);
lcdc_write(ch->lcdc, _LDDWAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0));
+ lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0);
}
static void lcdc_sys_write_data(void *handle, unsigned long data)
@@ -165,6 +221,7 @@ static void lcdc_sys_write_data(void *handle, unsigned long data)
lcdc_write(ch->lcdc, _LDDWD0R, data | 0x11000000);
lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0);
lcdc_write(ch->lcdc, _LDDWAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0));
+ lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0);
}
static unsigned long lcdc_sys_read_data(void *handle)
@@ -175,8 +232,9 @@ static unsigned long lcdc_sys_read_data(void *handle)
lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0);
lcdc_write(ch->lcdc, _LDDRAR, 1 | (lcdc_chan_is_sublcd(ch) ? 2 : 0));
udelay(1);
+ lcdc_wait_bit(ch->lcdc, _LDSR, 2, 0);
- return lcdc_read(ch->lcdc, _LDDRDR) & 0xffff;
+ return lcdc_read(ch->lcdc, _LDDRDR) & 0x3ffff;
}
struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = {
@@ -185,11 +243,10 @@ struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = {
lcdc_sys_read_data,
};
-#ifdef CONFIG_HAVE_CLK
static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv)
{
- if (atomic_inc_and_test(&priv->clk_usecnt)) {
- clk_enable(priv->clk);
+ if (atomic_inc_and_test(&priv->hw_usecnt)) {
+ pm_runtime_get_sync(priv->dev);
if (priv->dot_clk)
clk_enable(priv->dot_clk);
}
@@ -197,16 +254,12 @@ static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv)
static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv)
{
- if (atomic_sub_return(1, &priv->clk_usecnt) == -1) {
+ if (atomic_sub_return(1, &priv->hw_usecnt) == -1) {
if (priv->dot_clk)
clk_disable(priv->dot_clk);
- clk_disable(priv->clk);
+ pm_runtime_put(priv->dev);
}
}
-#else
-static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) {}
-static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) {}
-#endif
static int sh_mobile_lcdc_sginit(struct fb_info *info,
struct list_head *pagelist)
@@ -255,30 +308,52 @@ static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data)
struct sh_mobile_lcdc_priv *priv = data;
struct sh_mobile_lcdc_chan *ch;
unsigned long tmp;
+ unsigned long ldintr;
int is_sub;
int k;
/* acknowledge interrupt */
- tmp = lcdc_read(priv, _LDINTR);
- tmp &= 0xffffff00; /* mask in high 24 bits */
- tmp |= 0x000000ff ^ LDINTR_FS; /* status in low 8 */
+ ldintr = tmp = lcdc_read(priv, _LDINTR);
+ /*
+ * disable further VSYNC End IRQs, preserve all other enabled IRQs,
+ * write 0 to bits 0-6 to ack all triggered IRQs.
+ */
+ tmp &= 0xffffff00 & ~LDINTR_VEE;
lcdc_write(priv, _LDINTR, tmp);
/* figure out if this interrupt is for main or sub lcd */
is_sub = (lcdc_read(priv, _LDSR) & (1 << 10)) ? 1 : 0;
- /* wake up channel and disable clocks*/
+ /* wake up channel and disable clocks */
for (k = 0; k < ARRAY_SIZE(priv->ch); k++) {
ch = &priv->ch[k];
if (!ch->enabled)
continue;
- if (is_sub == lcdc_chan_is_sublcd(ch)) {
- ch->frame_end = 1;
- wake_up(&ch->frame_end_wait);
+ /* Frame Start */
+ if (ldintr & LDINTR_FS) {
+ if (is_sub == lcdc_chan_is_sublcd(ch)) {
+ ch->frame_end = 1;
+ wake_up(&ch->frame_end_wait);
- sh_mobile_lcdc_clk_off(priv);
+ sh_mobile_lcdc_clk_off(priv);
+ }
+ }
+
+ /* VSYNC End */
+ if (ldintr & LDINTR_VES) {
+ unsigned long ldrcntr = lcdc_read(priv, _LDRCNTR);
+ /* Set the source address for the next refresh */
+ lcdc_write_chan_mirror(ch, LDSA1R, ch->dma_handle +
+ ch->new_pan_offset);
+ if (lcdc_chan_is_sublcd(ch))
+ lcdc_write(ch->lcdc, _LDRCNTR,
+ ldrcntr ^ LDRCNTR_SRS);
+ else
+ lcdc_write(ch->lcdc, _LDRCNTR,
+ ldrcntr ^ LDRCNTR_MRS);
+ ch->pan_offset = ch->new_pan_offset;
}
}
@@ -520,7 +595,6 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv)
board_cfg = &ch->cfg.board_cfg;
if (board_cfg->display_off)
board_cfg->display_off(board_cfg->board_data);
-
}
/* stop the lcdc */
@@ -579,9 +653,6 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev,
int clock_source,
struct sh_mobile_lcdc_priv *priv)
{
-#ifdef CONFIG_HAVE_CLK
- char clk_name[8];
-#endif
char *str;
int icksel;
@@ -595,25 +666,21 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev,
priv->lddckr = icksel << 16;
-#ifdef CONFIG_HAVE_CLK
- atomic_set(&priv->clk_usecnt, -1);
- snprintf(clk_name, sizeof(clk_name), "lcdc%d", pdev->id);
- priv->clk = clk_get(&pdev->dev, clk_name);
- if (IS_ERR(priv->clk)) {
- dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name);
- return PTR_ERR(priv->clk);
- }
-
if (str) {
priv->dot_clk = clk_get(&pdev->dev, str);
if (IS_ERR(priv->dot_clk)) {
dev_err(&pdev->dev, "cannot get dot clock %s\n", str);
- clk_put(priv->clk);
return PTR_ERR(priv->dot_clk);
}
}
-#endif
-
+ atomic_set(&priv->hw_usecnt, -1);
+
+ /* Runtime PM support involves two step for this driver:
+ * 1) Enable Runtime PM
+ * 2) Force Runtime PM Resume since hardware is accessed from probe()
+ */
+ pm_runtime_enable(priv->dev);
+ pm_runtime_resume(priv->dev);
return 0;
}
@@ -646,6 +713,9 @@ static struct fb_fix_screeninfo sh_mobile_lcdc_fix = {
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_TRUECOLOR,
.accel = FB_ACCEL_NONE,
+ .xpanstep = 0,
+ .ypanstep = 1,
+ .ywrapstep = 0,
};
static void sh_mobile_lcdc_fillrect(struct fb_info *info,
@@ -669,13 +739,38 @@ static void sh_mobile_lcdc_imageblit(struct fb_info *info,
sh_mobile_lcdc_deferred_io_touch(info);
}
+static int sh_mobile_fb_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct sh_mobile_lcdc_chan *ch = info->par;
+
+ if (info->var.xoffset == var->xoffset &&
+ info->var.yoffset == var->yoffset)
+ return 0; /* No change, do nothing */
+
+ ch->new_pan_offset = (var->yoffset * info->fix.line_length) +
+ (var->xoffset * (info->var.bits_per_pixel / 8));
+
+ if (ch->new_pan_offset != ch->pan_offset) {
+ unsigned long ldintr;
+ ldintr = lcdc_read(ch->lcdc, _LDINTR);
+ ldintr |= LDINTR_VEE;
+ lcdc_write(ch->lcdc, _LDINTR, ldintr);
+ sh_mobile_lcdc_deferred_io_touch(info);
+ }
+
+ return 0;
+}
+
static struct fb_ops sh_mobile_lcdc_ops = {
+ .owner = THIS_MODULE,
.fb_setcolreg = sh_mobile_lcdc_setcolreg,
.fb_read = fb_sys_read,
.fb_write = fb_sys_write,
.fb_fillrect = sh_mobile_lcdc_fillrect,
.fb_copyarea = sh_mobile_lcdc_copyarea,
.fb_imageblit = sh_mobile_lcdc_imageblit,
+ .fb_pan_display = sh_mobile_fb_pan_display,
};
static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp)
@@ -731,9 +826,59 @@ static int sh_mobile_lcdc_resume(struct device *dev)
return sh_mobile_lcdc_start(platform_get_drvdata(pdev));
}
+static int sh_mobile_lcdc_runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct sh_mobile_lcdc_priv *p = platform_get_drvdata(pdev);
+ struct sh_mobile_lcdc_chan *ch;
+ int k, n;
+
+ /* save per-channel registers */
+ for (k = 0; k < ARRAY_SIZE(p->ch); k++) {
+ ch = &p->ch[k];
+ if (!ch->enabled)
+ continue;
+ for (n = 0; n < NR_CH_REGS; n++)
+ ch->saved_ch_regs[n] = lcdc_read_chan(ch, n);
+ }
+
+ /* save shared registers */
+ for (n = 0; n < NR_SHARED_REGS; n++)
+ p->saved_shared_regs[n] = lcdc_read(p, lcdc_shared_regs[n]);
+
+ /* turn off LCDC hardware */
+ lcdc_write(p, _LDCNT1R, 0);
+ return 0;
+}
+
+static int sh_mobile_lcdc_runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct sh_mobile_lcdc_priv *p = platform_get_drvdata(pdev);
+ struct sh_mobile_lcdc_chan *ch;
+ int k, n;
+
+ /* restore per-channel registers */
+ for (k = 0; k < ARRAY_SIZE(p->ch); k++) {
+ ch = &p->ch[k];
+ if (!ch->enabled)
+ continue;
+ for (n = 0; n < NR_CH_REGS; n++)
+ lcdc_write_chan(ch, n, ch->saved_ch_regs[n]);
+ }
+
+ /* restore shared registers */
+ for (n = 0; n < NR_SHARED_REGS; n++)
+ lcdc_write(p, lcdc_shared_regs[n], p->saved_shared_regs[n]);
+
+ return 0;
+}
+
static struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = {
.suspend = sh_mobile_lcdc_suspend,
.resume = sh_mobile_lcdc_resume,
+ .runtime_suspend = sh_mobile_lcdc_runtime_suspend,
+ .runtime_resume = sh_mobile_lcdc_runtime_resume,
};
static int sh_mobile_lcdc_remove(struct platform_device *pdev);
@@ -778,6 +923,7 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev)
}
priv->irq = i;
+ priv->dev = &pdev->dev;
platform_set_drvdata(pdev, priv);
pdata = pdev->dev.platform_data;
@@ -792,6 +938,8 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev)
goto err1;
}
init_waitqueue_head(&priv->ch[i].frame_end_wait);
+ priv->ch[j].pan_offset = 0;
+ priv->ch[j].new_pan_offset = 0;
switch (pdata->ch[i].chan) {
case LCDC_CHAN_MAINLCD:
@@ -834,7 +982,9 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev)
info = priv->ch[i].info;
info->fbops = &sh_mobile_lcdc_ops;
info->var.xres = info->var.xres_virtual = cfg->lcd_cfg.xres;
- info->var.yres = info->var.yres_virtual = cfg->lcd_cfg.yres;
+ info->var.yres = cfg->lcd_cfg.yres;
+ /* Default Y virtual resolution is 2x panel size */
+ info->var.yres_virtual = info->var.yres * 2;
info->var.width = cfg->lcd_size_cfg.width;
info->var.height = cfg->lcd_size_cfg.height;
info->var.activate = FB_ACTIVATE_NOW;
@@ -844,7 +994,8 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev)
info->fix = sh_mobile_lcdc_fix;
info->fix.line_length = cfg->lcd_cfg.xres * (cfg->bpp / 8);
- info->fix.smem_len = info->fix.line_length * cfg->lcd_cfg.yres;
+ info->fix.smem_len = info->fix.line_length *
+ info->var.yres_virtual;
buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len,
&priv->ch[i].dma_handle, GFP_KERNEL);
@@ -947,11 +1098,10 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev)
framebuffer_release(info);
}
-#ifdef CONFIG_HAVE_CLK
if (priv->dot_clk)
clk_put(priv->dot_clk);
- clk_put(priv->clk);
-#endif
+
+ pm_runtime_disable(priv->dev);
if (priv->base)
iounmap(priv->base);
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index b1ccc04f3c9..ff3eb8ff6bd 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -55,6 +55,13 @@ config SOFT_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called softdog.
+config WM831X_WATCHDOG
+ tristate "WM831x watchdog"
+ depends on MFD_WM831X
+ help
+ Support for the watchdog in the WM831x AudioPlus PMICs. When
+ the watchdog triggers the system will be reset.
+
config WM8350_WATCHDOG
tristate "WM8350 watchdog"
depends on MFD_WM8350
@@ -266,6 +273,15 @@ config STMP3XXX_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called stmp3xxx_wdt.
+config NUC900_WATCHDOG
+ tristate "Nuvoton NUC900 watchdog"
+ depends on ARCH_W90X900
+ help
+ Say Y here if to include support for the watchdog timer
+ for the Nuvoton NUC900 series SoCs.
+ To compile this driver as a module, choose M here: the
+ module will be called nuc900_wdt.
+
# AVR32 Architecture
config AT32AP700X_WDT
@@ -369,6 +385,28 @@ config SC520_WDT
You can compile this driver directly into the kernel, or use
it as a module. The module will be called sc520_wdt.
+config SBC_FITPC2_WATCHDOG
+ tristate "Compulab SBC-FITPC2 watchdog"
+ depends on X86
+ ---help---
+ This is the driver for the built-in watchdog timer on the fit-PC2
+ Single-board computer made by Compulab.
+
+ It`s possible to enable watchdog timer either from BIOS (F2) or from booted Linux.
+ When "Watchdog Timer Value" enabled one can set 31-255 s operational range.
+
+ Entering BIOS setup temporary disables watchdog operation regardless to current state,
+ so system will not be restarted while user in BIOS setup.
+
+ Once watchdog was enabled the system will be restarted every
+ "Watchdog Timer Value" period, so to prevent it user can restart or
+ disable the watchdog.
+
+ To compile this driver as a module, choose M here: the
+ module will be called sbc_fitpc2_wdt.
+
+ Most people will say N.
+
config EUROTECH_WDT
tristate "Eurotech CPU-1220/1410 Watchdog Timer"
depends on X86
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 3d774294a2b..348b3b862c9 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -44,6 +44,7 @@ obj-$(CONFIG_DAVINCI_WATCHDOG) += davinci_wdt.o
obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o
obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o
obj-$(CONFIG_STMP3XXX_WATCHDOG) += stmp3xxx_wdt.o
+obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o
# AVR32 Architecture
obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o
@@ -64,6 +65,7 @@ obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o
obj-$(CONFIG_GEODE_WDT) += geodewdt.o
obj-$(CONFIG_SC520_WDT) += sc520_wdt.o
+obj-$(CONFIG_SBC_FITPC2_WATCHDOG) += sbc_fitpc2_wdt.o
obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o
obj-$(CONFIG_IB700_WDT) += ib700wdt.o
obj-$(CONFIG_IBMASR) += ibmasr.o
@@ -139,5 +141,6 @@ obj-$(CONFIG_WATCHDOG_CP1XXX) += cpwd.o
# XTENSA Architecture
# Architecture Independant
+obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
diff --git a/drivers/watchdog/ar7_wdt.c b/drivers/watchdog/ar7_wdt.c
index 2f8643efe92..2e94b71b20d 100644
--- a/drivers/watchdog/ar7_wdt.c
+++ b/drivers/watchdog/ar7_wdt.c
@@ -28,9 +28,8 @@
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
#include <linux/watchdog.h>
-#include <linux/notifier.h>
-#include <linux/reboot.h>
#include <linux/fs.h>
#include <linux/ioport.h>
#include <linux/io.h>
@@ -76,24 +75,10 @@ static unsigned expect_close;
/* XXX currently fixed, allows max margin ~68.72 secs */
#define prescale_value 0xffff
-/* Offset of the WDT registers */
-static unsigned long ar7_regs_wdt;
+/* Resource of the WDT registers */
+static struct resource *ar7_regs_wdt;
/* Pointer to the remapped WDT IO space */
static struct ar7_wdt *ar7_wdt;
-static void ar7_wdt_get_regs(void)
-{
- u16 chip_id = ar7_chip_id();
- switch (chip_id) {
- case AR7_CHIP_7100:
- case AR7_CHIP_7200:
- ar7_regs_wdt = AR7_REGS_WDT;
- break;
- default:
- ar7_regs_wdt = UR8_REGS_WDT;
- break;
- }
-}
-
static void ar7_wdt_kick(u32 value)
{
@@ -202,20 +187,6 @@ static int ar7_wdt_release(struct inode *inode, struct file *file)
return 0;
}
-static int ar7_wdt_notify_sys(struct notifier_block *this,
- unsigned long code, void *unused)
-{
- if (code == SYS_HALT || code == SYS_POWER_OFF)
- if (!nowayout)
- ar7_wdt_disable_wdt();
-
- return NOTIFY_DONE;
-}
-
-static struct notifier_block ar7_wdt_notifier = {
- .notifier_call = ar7_wdt_notify_sys,
-};
-
static ssize_t ar7_wdt_write(struct file *file, const char *data,
size_t len, loff_t *ppos)
{
@@ -299,56 +270,86 @@ static struct miscdevice ar7_wdt_miscdev = {
.fops = &ar7_wdt_fops,
};
-static int __init ar7_wdt_init(void)
+static int __devinit ar7_wdt_probe(struct platform_device *pdev)
{
int rc;
spin_lock_init(&wdt_lock);
- ar7_wdt_get_regs();
+ ar7_regs_wdt =
+ platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
+ if (!ar7_regs_wdt) {
+ printk(KERN_ERR DRVNAME ": could not get registers resource\n");
+ rc = -ENODEV;
+ goto out;
+ }
- if (!request_mem_region(ar7_regs_wdt, sizeof(struct ar7_wdt),
- LONGNAME)) {
+ if (!request_mem_region(ar7_regs_wdt->start,
+ resource_size(ar7_regs_wdt), LONGNAME)) {
printk(KERN_WARNING DRVNAME ": watchdog I/O region busy\n");
- return -EBUSY;
+ rc = -EBUSY;
+ goto out;
}
- ar7_wdt = (struct ar7_wdt *)
- ioremap(ar7_regs_wdt, sizeof(struct ar7_wdt));
+ ar7_wdt = ioremap(ar7_regs_wdt->start, resource_size(ar7_regs_wdt));
+ if (!ar7_wdt) {
+ printk(KERN_ERR DRVNAME ": could not ioremap registers\n");
+ rc = -ENXIO;
+ goto out_mem_region;
+ }
ar7_wdt_disable_wdt();
ar7_wdt_prescale(prescale_value);
ar7_wdt_update_margin(margin);
- rc = register_reboot_notifier(&ar7_wdt_notifier);
- if (rc) {
- printk(KERN_ERR DRVNAME
- ": unable to register reboot notifier\n");
- goto out_alloc;
- }
-
rc = misc_register(&ar7_wdt_miscdev);
if (rc) {
printk(KERN_ERR DRVNAME ": unable to register misc device\n");
- goto out_register;
+ goto out_alloc;
}
goto out;
-out_register:
- unregister_reboot_notifier(&ar7_wdt_notifier);
out_alloc:
iounmap(ar7_wdt);
- release_mem_region(ar7_regs_wdt, sizeof(struct ar7_wdt));
+out_mem_region:
+ release_mem_region(ar7_regs_wdt->start, resource_size(ar7_regs_wdt));
out:
return rc;
}
-static void __exit ar7_wdt_cleanup(void)
+static int __devexit ar7_wdt_remove(struct platform_device *pdev)
{
misc_deregister(&ar7_wdt_miscdev);
- unregister_reboot_notifier(&ar7_wdt_notifier);
iounmap(ar7_wdt);
- release_mem_region(ar7_regs_wdt, sizeof(struct ar7_wdt));
+ release_mem_region(ar7_regs_wdt->start, resource_size(ar7_regs_wdt));
+
+ return 0;
+}
+
+static void ar7_wdt_shutdown(struct platform_device *pdev)
+{
+ if (!nowayout)
+ ar7_wdt_disable_wdt();
+}
+
+static struct platform_driver ar7_wdt_driver = {
+ .probe = ar7_wdt_probe,
+ .remove = __devexit_p(ar7_wdt_remove),
+ .shutdown = ar7_wdt_shutdown,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "ar7_wdt",
+ },
+};
+
+static int __init ar7_wdt_init(void)
+{
+ return platform_driver_register(&ar7_wdt_driver);
+}
+
+static void __exit ar7_wdt_cleanup(void)
+{
+ platform_driver_unregister(&ar7_wdt_driver);
}
module_init(ar7_wdt_init);
diff --git a/drivers/watchdog/booke_wdt.c b/drivers/watchdog/booke_wdt.c
index 225398fd504..e8380ef65c1 100644
--- a/drivers/watchdog/booke_wdt.c
+++ b/drivers/watchdog/booke_wdt.c
@@ -22,6 +22,8 @@
#include <asm/reg_booke.h>
#include <asm/system.h>
+#include <asm/time.h>
+#include <asm/div64.h>
/* If the kernel parameter wdt=1, the watchdog will be enabled at boot.
* Also, the wdt_period sets the watchdog timer period timeout.
@@ -32,7 +34,7 @@
*/
#ifdef CONFIG_FSL_BOOKE
-#define WDT_PERIOD_DEFAULT 63 /* Ex. wdt_period=28 bus=333Mhz,reset=~40sec */
+#define WDT_PERIOD_DEFAULT 38 /* Ex. wdt_period=28 bus=333Mhz,reset=~40sec */
#else
#define WDT_PERIOD_DEFAULT 3 /* Refer to the PPC40x and PPC4xx manuals */
#endif /* for timing information */
@@ -41,7 +43,7 @@ u32 booke_wdt_enabled;
u32 booke_wdt_period = WDT_PERIOD_DEFAULT;
#ifdef CONFIG_FSL_BOOKE
-#define WDTP(x) ((((63-x)&0x3)<<30)|(((63-x)&0x3c)<<15))
+#define WDTP(x) ((((x)&0x3)<<30)|(((x)&0x3c)<<15))
#define WDTP_MASK (WDTP(0))
#else
#define WDTP(x) (TCR_WP(x))
@@ -50,6 +52,45 @@ u32 booke_wdt_period = WDT_PERIOD_DEFAULT;
static DEFINE_SPINLOCK(booke_wdt_lock);
+/* For the specified period, determine the number of seconds
+ * corresponding to the reset time. There will be a watchdog
+ * exception at approximately 3/5 of this time.
+ *
+ * The formula to calculate this is given by:
+ * 2.5 * (2^(63-period+1)) / timebase_freq
+ *
+ * In order to simplify things, we assume that period is
+ * at least 1. This will still result in a very long timeout.
+ */
+static unsigned long long period_to_sec(unsigned int period)
+{
+ unsigned long long tmp = 1ULL << (64 - period);
+ unsigned long tmp2 = ppc_tb_freq;
+
+ /* tmp may be a very large number and we don't want to overflow,
+ * so divide the timebase freq instead of multiplying tmp
+ */
+ tmp2 = tmp2 / 5 * 2;
+
+ do_div(tmp, tmp2);
+ return tmp;
+}
+
+/*
+ * This procedure will find the highest period which will give a timeout
+ * greater than the one required. e.g. for a bus speed of 66666666 and
+ * and a parameter of 2 secs, then this procedure will return a value of 38.
+ */
+static unsigned int sec_to_period(unsigned int secs)
+{
+ unsigned int period;
+ for (period = 63; period > 0; period--) {
+ if (period_to_sec(period) >= secs)
+ return period;
+ }
+ return 0;
+}
+
static void __booke_wdt_ping(void *data)
{
mtspr(SPRN_TSR, TSR_ENW|TSR_WIS);
@@ -93,7 +134,7 @@ static long booke_wdt_ioctl(struct file *file,
switch (cmd) {
case WDIOC_GETSUPPORT:
- if (copy_to_user(arg, &ident, sizeof(struct watchdog_info)))
+ if (copy_to_user((void *)arg, &ident, sizeof(ident)))
return -EFAULT;
case WDIOC_GETSTATUS:
return put_user(ident.options, p);
@@ -115,8 +156,16 @@ static long booke_wdt_ioctl(struct file *file,
booke_wdt_ping();
return 0;
case WDIOC_SETTIMEOUT:
- if (get_user(booke_wdt_period, p))
+ if (get_user(tmp, p))
return -EFAULT;
+#ifdef CONFIG_FSL_BOOKE
+ /* period of 1 gives the largest possible timeout */
+ if (tmp > period_to_sec(1))
+ return -EINVAL;
+ booke_wdt_period = sec_to_period(tmp);
+#else
+ booke_wdt_period = tmp;
+#endif
mtspr(SPRN_TCR, (mfspr(SPRN_TCR) & ~WDTP_MASK) |
WDTP(booke_wdt_period));
return 0;
diff --git a/drivers/watchdog/coh901327_wdt.c b/drivers/watchdog/coh901327_wdt.c
index aec7cefdef2..381026c0bd7 100644
--- a/drivers/watchdog/coh901327_wdt.c
+++ b/drivers/watchdog/coh901327_wdt.c
@@ -110,7 +110,7 @@ static void coh901327_enable(u16 timeout)
* Wait 3 32 kHz cycles for it to take effect
*/
freq = clk_get_rate(clk);
- delay_ns = (1000000000 + freq - 1) / freq; /* Freq to ns and round up */
+ delay_ns = DIV_ROUND_UP(1000000000, freq); /* Freq to ns and round up */
delay_ns = 3 * delay_ns; /* Wait 3 cycles */
ndelay(delay_ns);
/* Enable the watchdog interrupt */
diff --git a/drivers/watchdog/davinci_wdt.c b/drivers/watchdog/davinci_wdt.c
index 83e22e7ea4a..9d7520fa9e9 100644
--- a/drivers/watchdog/davinci_wdt.c
+++ b/drivers/watchdog/davinci_wdt.c
@@ -25,6 +25,7 @@
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
+#include <linux/clk.h>
#define MODULE_NAME "DAVINCI-WDT: "
@@ -69,6 +70,7 @@ static unsigned long wdt_status;
static struct resource *wdt_mem;
static void __iomem *wdt_base;
+struct clk *wdt_clk;
static void wdt_service(void)
{
@@ -86,6 +88,9 @@ static void wdt_enable(void)
{
u32 tgcr;
u32 timer_margin;
+ unsigned long wdt_freq;
+
+ wdt_freq = clk_get_rate(wdt_clk);
spin_lock(&io_lock);
@@ -99,9 +104,9 @@ static void wdt_enable(void)
iowrite32(0, wdt_base + TIM12);
iowrite32(0, wdt_base + TIM34);
/* set timeout period */
- timer_margin = (((u64)heartbeat * CLOCK_TICK_RATE) & 0xffffffff);
+ timer_margin = (((u64)heartbeat * wdt_freq) & 0xffffffff);
iowrite32(timer_margin, wdt_base + PRD12);
- timer_margin = (((u64)heartbeat * CLOCK_TICK_RATE) >> 32);
+ timer_margin = (((u64)heartbeat * wdt_freq) >> 32);
iowrite32(timer_margin, wdt_base + PRD34);
/* enable run continuously */
iowrite32(ENAMODE12_PERIODIC, wdt_base + TCR);
@@ -199,6 +204,12 @@ static int __devinit davinci_wdt_probe(struct platform_device *pdev)
struct resource *res;
struct device *dev = &pdev->dev;
+ wdt_clk = clk_get(dev, NULL);
+ if (WARN_ON(IS_ERR(wdt_clk)))
+ return PTR_ERR(wdt_clk);
+
+ clk_enable(wdt_clk);
+
if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT)
heartbeat = DEFAULT_HEARTBEAT;
@@ -245,6 +256,10 @@ static int __devexit davinci_wdt_remove(struct platform_device *pdev)
kfree(wdt_mem);
wdt_mem = NULL;
}
+
+ clk_disable(wdt_clk);
+ clk_put(wdt_clk);
+
return 0;
}
diff --git a/drivers/watchdog/iop_wdt.c b/drivers/watchdog/iop_wdt.c
index 0c905967669..aef94789019 100644
--- a/drivers/watchdog/iop_wdt.c
+++ b/drivers/watchdog/iop_wdt.c
@@ -139,7 +139,7 @@ static long iop_wdt_ioctl(struct file *file,
switch (cmd) {
case WDIOC_GETSUPPORT:
- if (copy_to_user(argp, &ident, sizeof ident))
+ if (copy_to_user(argp, &ident, sizeof(ident)))
ret = -EFAULT;
else
ret = 0;
diff --git a/drivers/watchdog/nuc900_wdt.c b/drivers/watchdog/nuc900_wdt.c
new file mode 100644
index 00000000000..adefe3a9d51
--- /dev/null
+++ b/drivers/watchdog/nuc900_wdt.c
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2009 Nuvoton technology corporation.
+ *
+ * Wan ZongShun <mcuos.com@gmail.com>
+ *
+ * This program 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;version 2 of the License.
+ *
+ */
+
+#include <linux/bitops.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/uaccess.h>
+
+#define REG_WTCR 0x1c
+#define WTCLK (0x01 << 10)
+#define WTE (0x01 << 7) /*wdt enable*/
+#define WTIS (0x03 << 4)
+#define WTIF (0x01 << 3)
+#define WTRF (0x01 << 2)
+#define WTRE (0x01 << 1)
+#define WTR (0x01 << 0)
+/*
+ * The watchdog time interval can be calculated via following formula:
+ * WTIS real time interval (formula)
+ * 0x00 ((2^ 14 ) * ((external crystal freq) / 256))seconds
+ * 0x01 ((2^ 16 ) * ((external crystal freq) / 256))seconds
+ * 0x02 ((2^ 18 ) * ((external crystal freq) / 256))seconds
+ * 0x03 ((2^ 20 ) * ((external crystal freq) / 256))seconds
+ *
+ * The external crystal freq is 15Mhz in the nuc900 evaluation board.
+ * So 0x00 = +-0.28 seconds, 0x01 = +-1.12 seconds, 0x02 = +-4.48 seconds,
+ * 0x03 = +- 16.92 seconds..
+ */
+#define WDT_HW_TIMEOUT 0x02
+#define WDT_TIMEOUT (HZ/2)
+#define WDT_HEARTBEAT 15
+
+static int heartbeat = WDT_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. "
+ "(default = " __MODULE_STRING(WDT_HEARTBEAT) ")");
+
+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) ")");
+
+struct nuc900_wdt {
+ struct resource *res;
+ struct clk *wdt_clock;
+ struct platform_device *pdev;
+ void __iomem *wdt_base;
+ char expect_close;
+ struct timer_list timer;
+ spinlock_t wdt_lock;
+ unsigned long next_heartbeat;
+};
+
+static unsigned long nuc900wdt_busy;
+struct nuc900_wdt *nuc900_wdt;
+
+static inline void nuc900_wdt_keepalive(void)
+{
+ unsigned int val;
+
+ spin_lock(&nuc900_wdt->wdt_lock);
+
+ val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR);
+ val |= (WTR | WTIF);
+ __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR);
+
+ spin_unlock(&nuc900_wdt->wdt_lock);
+}
+
+static inline void nuc900_wdt_start(void)
+{
+ unsigned int val;
+
+ spin_lock(&nuc900_wdt->wdt_lock);
+
+ val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR);
+ val |= (WTRE | WTE | WTR | WTCLK | WTIF);
+ val &= ~WTIS;
+ val |= (WDT_HW_TIMEOUT << 0x04);
+ __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR);
+
+ spin_unlock(&nuc900_wdt->wdt_lock);
+
+ nuc900_wdt->next_heartbeat = jiffies + heartbeat * HZ;
+ mod_timer(&nuc900_wdt->timer, jiffies + WDT_TIMEOUT);
+}
+
+static inline void nuc900_wdt_stop(void)
+{
+ unsigned int val;
+
+ del_timer(&nuc900_wdt->timer);
+
+ spin_lock(&nuc900_wdt->wdt_lock);
+
+ val = __raw_readl(nuc900_wdt->wdt_base + REG_WTCR);
+ val &= ~WTE;
+ __raw_writel(val, nuc900_wdt->wdt_base + REG_WTCR);
+
+ spin_unlock(&nuc900_wdt->wdt_lock);
+}
+
+static inline void nuc900_wdt_ping(void)
+{
+ nuc900_wdt->next_heartbeat = jiffies + heartbeat * HZ;
+}
+
+static int nuc900_wdt_open(struct inode *inode, struct file *file)
+{
+
+ if (test_and_set_bit(0, &nuc900wdt_busy))
+ return -EBUSY;
+
+ nuc900_wdt_start();
+
+ return nonseekable_open(inode, file);
+}
+
+static int nuc900_wdt_close(struct inode *inode, struct file *file)
+{
+ if (nuc900_wdt->expect_close == 42)
+ nuc900_wdt_stop();
+ else {
+ dev_crit(&nuc900_wdt->pdev->dev,
+ "Unexpected close, not stopping watchdog!\n");
+ nuc900_wdt_ping();
+ }
+
+ nuc900_wdt->expect_close = 0;
+ clear_bit(0, &nuc900wdt_busy);
+ return 0;
+}
+
+static const struct watchdog_info nuc900_wdt_info = {
+ .identity = "nuc900 watchdog",
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
+ WDIOF_MAGICCLOSE,
+};
+
+static long nuc900_wdt_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int new_value;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ return copy_to_user(argp, &nuc900_wdt_info,
+ sizeof(nuc900_wdt_info)) ? -EFAULT : 0;
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ return put_user(0, p);
+
+ case WDIOC_KEEPALIVE:
+ nuc900_wdt_ping();
+ return 0;
+
+ case WDIOC_SETTIMEOUT:
+ if (get_user(new_value, p))
+ return -EFAULT;
+
+ heartbeat = new_value;
+ nuc900_wdt_ping();
+
+ return put_user(new_value, p);
+ case WDIOC_GETTIMEOUT:
+ return put_user(heartbeat, p);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static ssize_t nuc900_wdt_write(struct file *file, const char __user *data,
+ size_t len, loff_t *ppos)
+{
+ if (!len)
+ return 0;
+
+ /* Scan for magic character */
+ if (!nowayout) {
+ size_t i;
+
+ nuc900_wdt->expect_close = 0;
+
+ for (i = 0; i < len; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V') {
+ nuc900_wdt->expect_close = 42;
+ break;
+ }
+ }
+ }
+
+ nuc900_wdt_ping();
+ return len;
+}
+
+static void nuc900_wdt_timer_ping(unsigned long data)
+{
+ if (time_before(jiffies, nuc900_wdt->next_heartbeat)) {
+ nuc900_wdt_keepalive();
+ mod_timer(&nuc900_wdt->timer, jiffies + WDT_TIMEOUT);
+ } else
+ dev_warn(&nuc900_wdt->pdev->dev, "Will reset the machine !\n");
+}
+
+static const struct file_operations nuc900wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .unlocked_ioctl = nuc900_wdt_ioctl,
+ .open = nuc900_wdt_open,
+ .release = nuc900_wdt_close,
+ .write = nuc900_wdt_write,
+};
+
+static struct miscdevice nuc900wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &nuc900wdt_fops,
+};
+
+static int __devinit nuc900wdt_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ nuc900_wdt = kzalloc(sizeof(struct nuc900_wdt), GFP_KERNEL);
+ if (!nuc900_wdt)
+ return -ENOMEM;
+
+ nuc900_wdt->pdev = pdev;
+
+ spin_lock_init(&nuc900_wdt->wdt_lock);
+
+ nuc900_wdt->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (nuc900_wdt->res == NULL) {
+ dev_err(&pdev->dev, "no memory resource specified\n");
+ ret = -ENOENT;
+ goto err_get;
+ }
+
+ if (!request_mem_region(nuc900_wdt->res->start,
+ resource_size(nuc900_wdt->res), pdev->name)) {
+ dev_err(&pdev->dev, "failed to get memory region\n");
+ ret = -ENOENT;
+ goto err_get;
+ }
+
+ nuc900_wdt->wdt_base = ioremap(nuc900_wdt->res->start,
+ resource_size(nuc900_wdt->res));
+ if (nuc900_wdt->wdt_base == NULL) {
+ dev_err(&pdev->dev, "failed to ioremap() region\n");
+ ret = -EINVAL;
+ goto err_req;
+ }
+
+ nuc900_wdt->wdt_clock = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(nuc900_wdt->wdt_clock)) {
+ dev_err(&pdev->dev, "failed to find watchdog clock source\n");
+ ret = PTR_ERR(nuc900_wdt->wdt_clock);
+ goto err_map;
+ }
+
+ clk_enable(nuc900_wdt->wdt_clock);
+
+ setup_timer(&nuc900_wdt->timer, nuc900_wdt_timer_ping, 0);
+
+ if (misc_register(&nuc900wdt_miscdev)) {
+ dev_err(&pdev->dev, "err register miscdev on minor=%d (%d)\n",
+ WATCHDOG_MINOR, ret);
+ goto err_clk;
+ }
+
+ return 0;
+
+err_clk:
+ clk_disable(nuc900_wdt->wdt_clock);
+ clk_put(nuc900_wdt->wdt_clock);
+err_map:
+ iounmap(nuc900_wdt->wdt_base);
+err_req:
+ release_mem_region(nuc900_wdt->res->start,
+ resource_size(nuc900_wdt->res));
+err_get:
+ kfree(nuc900_wdt);
+ return ret;
+}
+
+static int __devexit nuc900wdt_remove(struct platform_device *pdev)
+{
+ misc_deregister(&nuc900wdt_miscdev);
+
+ clk_disable(nuc900_wdt->wdt_clock);
+ clk_put(nuc900_wdt->wdt_clock);
+
+ iounmap(nuc900_wdt->wdt_base);
+
+ release_mem_region(nuc900_wdt->res->start,
+ resource_size(nuc900_wdt->res));
+
+ kfree(nuc900_wdt);
+
+ return 0;
+}
+
+static struct platform_driver nuc900wdt_driver = {
+ .probe = nuc900wdt_probe,
+ .remove = __devexit_p(nuc900wdt_remove),
+ .driver = {
+ .name = "nuc900-wdt",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init nuc900_wdt_init(void)
+{
+ return platform_driver_register(&nuc900wdt_driver);
+}
+
+static void __exit nuc900_wdt_exit(void)
+{
+ platform_driver_unregister(&nuc900wdt_driver);
+}
+
+module_init(nuc900_wdt_init);
+module_exit(nuc900_wdt_exit);
+
+MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>");
+MODULE_DESCRIPTION("Watchdog driver for NUC900");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS("platform:nuc900-wdt");
diff --git a/drivers/watchdog/rm9k_wdt.c b/drivers/watchdog/rm9k_wdt.c
index 2e444264226..bb66958b943 100644
--- a/drivers/watchdog/rm9k_wdt.c
+++ b/drivers/watchdog/rm9k_wdt.c
@@ -340,7 +340,7 @@ static const struct resource *wdt_gpi_get_resource(struct platform_device *pdv,
const char *name, unsigned int type)
{
char buf[80];
- if (snprintf(buf, sizeof buf, "%s_0", name) >= sizeof buf)
+ if (snprintf(buf, sizeof(buf), "%s_0", name) >= sizeof(buf))
return NULL;
return platform_get_resource_byname(pdv, type, buf);
}
diff --git a/drivers/watchdog/sbc_fitpc2_wdt.c b/drivers/watchdog/sbc_fitpc2_wdt.c
new file mode 100644
index 00000000000..852ca197791
--- /dev/null
+++ b/drivers/watchdog/sbc_fitpc2_wdt.c
@@ -0,0 +1,267 @@
+/*
+ * Watchdog driver for SBC-FITPC2 board
+ *
+ * Author: Denis Turischev <denis@compulab.co.il>
+ *
+ * Adapted from the IXP2000 watchdog driver by Deepak Saxena.
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME " WATCHDOG: " fmt
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/dmi.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+
+#include <asm/system.h>
+
+static int nowayout = WATCHDOG_NOWAYOUT;
+static unsigned int margin = 60; /* (secs) Default is 1 minute */
+static unsigned long wdt_status;
+static DEFINE_SPINLOCK(wdt_lock);
+
+#define WDT_IN_USE 0
+#define WDT_OK_TO_CLOSE 1
+
+#define COMMAND_PORT 0x4c
+#define DATA_PORT 0x48
+
+#define IFACE_ON_COMMAND 1
+#define REBOOT_COMMAND 2
+
+#define WATCHDOG_NAME "SBC-FITPC2 Watchdog"
+
+static void wdt_send_data(unsigned char command, unsigned char data)
+{
+ outb(command, COMMAND_PORT);
+ mdelay(100);
+ outb(data, DATA_PORT);
+ mdelay(200);
+}
+
+static void wdt_enable(void)
+{
+ spin_lock(&wdt_lock);
+ wdt_send_data(IFACE_ON_COMMAND, 1);
+ wdt_send_data(REBOOT_COMMAND, margin);
+ spin_unlock(&wdt_lock);
+}
+
+static void wdt_disable(void)
+{
+ spin_lock(&wdt_lock);
+ wdt_send_data(IFACE_ON_COMMAND, 0);
+ wdt_send_data(REBOOT_COMMAND, 0);
+ spin_unlock(&wdt_lock);
+}
+
+static int fitpc2_wdt_open(struct inode *inode, struct file *file)
+{
+ if (test_and_set_bit(WDT_IN_USE, &wdt_status))
+ return -EBUSY;
+
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ wdt_enable();
+
+ return nonseekable_open(inode, file);
+}
+
+static ssize_t fitpc2_wdt_write(struct file *file, const char *data,
+ size_t len, loff_t *ppos)
+{
+ size_t i;
+
+ if (!len)
+ return 0;
+
+ if (nowayout) {
+ len = 0;
+ goto out;
+ }
+
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ for (i = 0; i != len; i++) {
+ char c;
+
+ if (get_user(c, data + i))
+ return -EFAULT;
+
+ if (c == 'V')
+ set_bit(WDT_OK_TO_CLOSE, &wdt_status);
+ }
+
+out:
+ wdt_enable();
+
+ return len;
+}
+
+
+static struct watchdog_info ident = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
+ WDIOF_KEEPALIVEPING,
+ .identity = WATCHDOG_NAME,
+};
+
+
+static long fitpc2_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -ENOTTY;
+ int time;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = copy_to_user((struct watchdog_info *)arg, &ident,
+ sizeof(ident)) ? -EFAULT : 0;
+ break;
+
+ case WDIOC_GETSTATUS:
+ ret = put_user(0, (int *)arg);
+ break;
+
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(0, (int *)arg);
+ break;
+
+ case WDIOC_KEEPALIVE:
+ wdt_enable();
+ ret = 0;
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(time, (int *)arg);
+ if (ret)
+ break;
+
+ if (time < 31 || time > 255) {
+ ret = -EINVAL;
+ break;
+ }
+
+ margin = time;
+ wdt_enable();
+ /* Fall through */
+
+ case WDIOC_GETTIMEOUT:
+ ret = put_user(margin, (int *)arg);
+ break;
+ }
+
+ return ret;
+}
+
+static int fitpc2_wdt_release(struct inode *inode, struct file *file)
+{
+ if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
+ wdt_disable();
+ pr_info("Device disabled\n");
+ } else {
+ pr_warning("Device closed unexpectedly -"
+ " timer will not stop\n");
+ wdt_enable();
+ }
+
+ clear_bit(WDT_IN_USE, &wdt_status);
+ clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+ return 0;
+}
+
+
+static const struct file_operations fitpc2_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = fitpc2_wdt_write,
+ .unlocked_ioctl = fitpc2_wdt_ioctl,
+ .open = fitpc2_wdt_open,
+ .release = fitpc2_wdt_release,
+};
+
+static struct miscdevice fitpc2_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &fitpc2_wdt_fops,
+};
+
+static int __init fitpc2_wdt_init(void)
+{
+ int err;
+
+ if (strcmp("SBC-FITPC2", dmi_get_system_info(DMI_BOARD_NAME))) {
+ pr_info("board name is: %s. Should be SBC-FITPC2\n",
+ dmi_get_system_info(DMI_BOARD_NAME));
+ return -ENODEV;
+ }
+
+ if (!request_region(COMMAND_PORT, 1, WATCHDOG_NAME)) {
+ pr_err("I/O address 0x%04x already in use\n", COMMAND_PORT);
+ return -EIO;
+ }
+
+ if (!request_region(DATA_PORT, 1, WATCHDOG_NAME)) {
+ pr_err("I/O address 0x%04x already in use\n", DATA_PORT);
+ err = -EIO;
+ goto err_data_port;
+ }
+
+ if (margin < 31 || margin > 255) {
+ pr_err("margin must be in range 31 - 255"
+ " seconds, you tried to set %d\n", margin);
+ err = -EINVAL;
+ goto err_margin;
+ }
+
+ err = misc_register(&fitpc2_wdt_miscdev);
+ if (!err) {
+ pr_err("cannot register miscdev on minor=%d (err=%d)\n",
+ WATCHDOG_MINOR, err);
+ goto err_margin;
+ }
+
+ return 0;
+
+err_margin:
+ release_region(DATA_PORT, 1);
+err_data_port:
+ release_region(COMMAND_PORT, 1);
+
+ return err;
+}
+
+static void __exit fitpc2_wdt_exit(void)
+{
+ misc_deregister(&fitpc2_wdt_miscdev);
+ release_region(DATA_PORT, 1);
+ release_region(COMMAND_PORT, 1);
+}
+
+module_init(fitpc2_wdt_init);
+module_exit(fitpc2_wdt_exit);
+
+MODULE_AUTHOR("Denis Turischev <denis@compulab.co.il>");
+MODULE_DESCRIPTION("SBC-FITPC2 Watchdog");
+
+module_param(margin, int, 0);
+MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)");
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
diff --git a/drivers/watchdog/sc1200wdt.c b/drivers/watchdog/sc1200wdt.c
index b5e19c1820a..c01daca8405 100644
--- a/drivers/watchdog/sc1200wdt.c
+++ b/drivers/watchdog/sc1200wdt.c
@@ -197,7 +197,7 @@ static long sc1200wdt_ioctl(struct file *file, unsigned int cmd,
switch (cmd) {
case WDIOC_GETSUPPORT:
- if (copy_to_user(argp, &ident, sizeof ident))
+ if (copy_to_user(argp, &ident, sizeof(ident)))
return -EFAULT;
return 0;
diff --git a/drivers/watchdog/wdt_pci.c b/drivers/watchdog/wdt_pci.c
index 7a1bdc7c95a..f368dd87083 100644
--- a/drivers/watchdog/wdt_pci.c
+++ b/drivers/watchdog/wdt_pci.c
@@ -80,7 +80,7 @@ static unsigned long open_lock;
static DEFINE_SPINLOCK(wdtpci_lock);
static char expect_close;
-static int io;
+static resource_size_t io;
static int irq;
/* Default timeout */
@@ -647,14 +647,15 @@ static int __devinit wdtpci_init_one(struct pci_dev *dev,
goto out_pci;
}
- irq = dev->irq;
- io = pci_resource_start(dev, 2);
-
- if (request_region(io, 16, "wdt_pci") == NULL) {
- printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", io);
+ if (pci_request_region(dev, 2, "wdt_pci")) {
+ printk(KERN_ERR PFX "I/O address 0x%llx already in use\n",
+ (unsigned long long)pci_resource_start(dev, 2));
goto out_pci;
}
+ irq = dev->irq;
+ io = pci_resource_start(dev, 2);
+
if (request_irq(irq, wdtpci_interrupt, IRQF_DISABLED | IRQF_SHARED,
"wdt_pci", &wdtpci_miscdev)) {
printk(KERN_ERR PFX "IRQ %d is not free\n", irq);
@@ -662,8 +663,8 @@ static int __devinit wdtpci_init_one(struct pci_dev *dev,
}
printk(KERN_INFO
- "PCI-WDT500/501 (PCI-WDG-CSM) driver 0.10 at 0x%04x (Interrupt %d)\n",
- io, irq);
+ "PCI-WDT500/501 (PCI-WDG-CSM) driver 0.10 at 0x%llx (Interrupt %d)\n",
+ (unsigned long long)io, irq);
/* Check that the heartbeat value is within its range;
if not reset to the default */
@@ -717,7 +718,7 @@ out_rbt:
out_irq:
free_irq(irq, &wdtpci_miscdev);
out_reg:
- release_region(io, 16);
+ pci_release_region(dev, 2);
out_pci:
pci_disable_device(dev);
goto out;
@@ -733,7 +734,7 @@ static void __devexit wdtpci_remove_one(struct pci_dev *pdev)
misc_deregister(&temp_miscdev);
unregister_reboot_notifier(&wdtpci_notifier);
free_irq(irq, &wdtpci_miscdev);
- release_region(io, 16);
+ pci_release_region(pdev, 2);
pci_disable_device(pdev);
dev_count--;
}
diff --git a/drivers/watchdog/wm831x_wdt.c b/drivers/watchdog/wm831x_wdt.c
new file mode 100644
index 00000000000..775bcd807f3
--- /dev/null
+++ b/drivers/watchdog/wm831x_wdt.c
@@ -0,0 +1,441 @@
+/*
+ * Watchdog driver for the wm831x PMICs
+ *
+ * Copyright (C) 2009 Wolfson Microelectronics
+ *
+ * This program 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
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/platform_device.h>
+#include <linux/watchdog.h>
+#include <linux/uaccess.h>
+#include <linux/gpio.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/pdata.h>
+#include <linux/mfd/wm831x/watchdog.h>
+
+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 unsigned long wm831x_wdt_users;
+static struct miscdevice wm831x_wdt_miscdev;
+static int wm831x_wdt_expect_close;
+static DEFINE_MUTEX(wdt_mutex);
+static struct wm831x *wm831x;
+static unsigned int update_gpio;
+static unsigned int update_state;
+
+/* We can't use the sub-second values here but they're included
+ * for completeness. */
+static struct {
+ int time; /* Seconds */
+ u16 val; /* WDOG_TO value */
+} wm831x_wdt_cfgs[] = {
+ { 1, 2 },
+ { 2, 3 },
+ { 4, 4 },
+ { 8, 5 },
+ { 16, 6 },
+ { 32, 7 },
+ { 33, 7 }, /* Actually 32.768s so include both, others round down */
+};
+
+static int wm831x_wdt_set_timeout(struct wm831x *wm831x, u16 value)
+{
+ int ret;
+
+ mutex_lock(&wdt_mutex);
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret == 0) {
+ ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
+ WM831X_WDOG_TO_MASK, value);
+ wm831x_reg_lock(wm831x);
+ } else {
+ dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+ ret);
+ }
+
+ mutex_unlock(&wdt_mutex);
+
+ return ret;
+}
+
+static int wm831x_wdt_start(struct wm831x *wm831x)
+{
+ int ret;
+
+ mutex_lock(&wdt_mutex);
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret == 0) {
+ ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
+ WM831X_WDOG_ENA, WM831X_WDOG_ENA);
+ wm831x_reg_lock(wm831x);
+ } else {
+ dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+ ret);
+ }
+
+ mutex_unlock(&wdt_mutex);
+
+ return ret;
+}
+
+static int wm831x_wdt_stop(struct wm831x *wm831x)
+{
+ int ret;
+
+ mutex_lock(&wdt_mutex);
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret == 0) {
+ ret = wm831x_set_bits(wm831x, WM831X_WATCHDOG,
+ WM831X_WDOG_ENA, 0);
+ wm831x_reg_lock(wm831x);
+ } else {
+ dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+ ret);
+ }
+
+ mutex_unlock(&wdt_mutex);
+
+ return ret;
+}
+
+static int wm831x_wdt_kick(struct wm831x *wm831x)
+{
+ int ret;
+ u16 reg;
+
+ mutex_lock(&wdt_mutex);
+
+ if (update_gpio) {
+ gpio_set_value_cansleep(update_gpio, update_state);
+ update_state = !update_state;
+ ret = 0;
+ goto out;
+ }
+
+
+ reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
+
+ if (!(reg & WM831X_WDOG_RST_SRC)) {
+ dev_err(wm831x->dev, "Hardware watchdog update unsupported\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ reg |= WM831X_WDOG_RESET;
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret == 0) {
+ ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg);
+ wm831x_reg_lock(wm831x);
+ } else {
+ dev_err(wm831x->dev, "Failed to unlock security key: %d\n",
+ ret);
+ }
+
+out:
+ mutex_unlock(&wdt_mutex);
+
+ return ret;
+}
+
+static int wm831x_wdt_open(struct inode *inode, struct file *file)
+{
+ int ret;
+
+ if (!wm831x)
+ return -ENODEV;
+
+ if (test_and_set_bit(0, &wm831x_wdt_users))
+ return -EBUSY;
+
+ ret = wm831x_wdt_start(wm831x);
+ if (ret != 0)
+ return ret;
+
+ return nonseekable_open(inode, file);
+}
+
+static int wm831x_wdt_release(struct inode *inode, struct file *file)
+{
+ if (wm831x_wdt_expect_close)
+ wm831x_wdt_stop(wm831x);
+ else {
+ dev_warn(wm831x->dev, "Watchdog device closed uncleanly\n");
+ wm831x_wdt_kick(wm831x);
+ }
+
+ clear_bit(0, &wm831x_wdt_users);
+
+ return 0;
+}
+
+static ssize_t wm831x_wdt_write(struct file *file,
+ const char __user *data, size_t count,
+ loff_t *ppos)
+{
+ size_t i;
+
+ if (count) {
+ wm831x_wdt_kick(wm831x);
+
+ if (!nowayout) {
+ /* In case it was set long ago */
+ wm831x_wdt_expect_close = 0;
+
+ /* scan to see whether or not we got the magic
+ character */
+ for (i = 0; i != count; i++) {
+ char c;
+ if (get_user(c, data + i))
+ return -EFAULT;
+ if (c == 'V')
+ wm831x_wdt_expect_close = 42;
+ }
+ }
+ }
+ return count;
+}
+
+static struct watchdog_info ident = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+ .identity = "WM831x Watchdog",
+};
+
+static long wm831x_wdt_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -ENOTTY, time, i;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ u16 reg;
+
+ switch (cmd) {
+ case WDIOC_GETSUPPORT:
+ ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+ break;
+
+ case WDIOC_GETSTATUS:
+ case WDIOC_GETBOOTSTATUS:
+ ret = put_user(0, p);
+ break;
+
+ case WDIOC_SETOPTIONS:
+ {
+ int options;
+
+ if (get_user(options, p))
+ return -EFAULT;
+
+ ret = -EINVAL;
+
+ /* Setting both simultaneously means at least one must fail */
+ if (options == WDIOS_DISABLECARD)
+ ret = wm831x_wdt_start(wm831x);
+
+ if (options == WDIOS_ENABLECARD)
+ ret = wm831x_wdt_stop(wm831x);
+ break;
+ }
+
+ case WDIOC_KEEPALIVE:
+ ret = wm831x_wdt_kick(wm831x);
+ break;
+
+ case WDIOC_SETTIMEOUT:
+ ret = get_user(time, p);
+ if (ret)
+ break;
+
+ if (time == 0) {
+ if (nowayout)
+ ret = -EINVAL;
+ else
+ wm831x_wdt_stop(wm831x);
+ break;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++)
+ if (wm831x_wdt_cfgs[i].time == time)
+ break;
+ if (i == ARRAY_SIZE(wm831x_wdt_cfgs))
+ ret = -EINVAL;
+ else
+ ret = wm831x_wdt_set_timeout(wm831x,
+ wm831x_wdt_cfgs[i].val);
+ break;
+
+ case WDIOC_GETTIMEOUT:
+ reg = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
+ reg &= WM831X_WDOG_TO_MASK;
+ for (i = 0; i < ARRAY_SIZE(wm831x_wdt_cfgs); i++)
+ if (wm831x_wdt_cfgs[i].val == reg)
+ break;
+ if (i == ARRAY_SIZE(wm831x_wdt_cfgs)) {
+ dev_warn(wm831x->dev,
+ "Unknown watchdog configuration: %x\n", reg);
+ ret = -EINVAL;
+ } else
+ ret = put_user(wm831x_wdt_cfgs[i].time, p);
+
+ }
+
+ return ret;
+}
+
+static const struct file_operations wm831x_wdt_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = wm831x_wdt_write,
+ .unlocked_ioctl = wm831x_wdt_ioctl,
+ .open = wm831x_wdt_open,
+ .release = wm831x_wdt_release,
+};
+
+static struct miscdevice wm831x_wdt_miscdev = {
+ .minor = WATCHDOG_MINOR,
+ .name = "watchdog",
+ .fops = &wm831x_wdt_fops,
+};
+
+static int __devinit wm831x_wdt_probe(struct platform_device *pdev)
+{
+ struct wm831x_pdata *chip_pdata;
+ struct wm831x_watchdog_pdata *pdata;
+ int reg, ret;
+
+ wm831x = dev_get_drvdata(pdev->dev.parent);
+
+ ret = wm831x_reg_read(wm831x, WM831X_WATCHDOG);
+ if (ret < 0) {
+ dev_err(wm831x->dev, "Failed to read watchdog status: %d\n",
+ ret);
+ goto err;
+ }
+ reg = ret;
+
+ if (reg & WM831X_WDOG_DEBUG)
+ dev_warn(wm831x->dev, "Watchdog is paused\n");
+
+ /* Apply any configuration */
+ if (pdev->dev.parent->platform_data) {
+ chip_pdata = pdev->dev.parent->platform_data;
+ pdata = chip_pdata->watchdog;
+ } else {
+ pdata = NULL;
+ }
+
+ if (pdata) {
+ reg &= ~(WM831X_WDOG_SECACT_MASK | WM831X_WDOG_PRIMACT_MASK |
+ WM831X_WDOG_RST_SRC);
+
+ reg |= pdata->primary << WM831X_WDOG_PRIMACT_SHIFT;
+ reg |= pdata->secondary << WM831X_WDOG_SECACT_SHIFT;
+ reg |= pdata->software << WM831X_WDOG_RST_SRC_SHIFT;
+
+ if (pdata->update_gpio) {
+ ret = gpio_request(pdata->update_gpio,
+ "Watchdog update");
+ if (ret < 0) {
+ dev_err(wm831x->dev,
+ "Failed to request update GPIO: %d\n",
+ ret);
+ goto err;
+ }
+
+ ret = gpio_direction_output(pdata->update_gpio, 0);
+ if (ret != 0) {
+ dev_err(wm831x->dev,
+ "gpio_direction_output returned: %d\n",
+ ret);
+ goto err_gpio;
+ }
+
+ update_gpio = pdata->update_gpio;
+
+ /* Make sure the watchdog takes hardware updates */
+ reg |= WM831X_WDOG_RST_SRC;
+ }
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret == 0) {
+ ret = wm831x_reg_write(wm831x, WM831X_WATCHDOG, reg);
+ wm831x_reg_lock(wm831x);
+ } else {
+ dev_err(wm831x->dev,
+ "Failed to unlock security key: %d\n", ret);
+ goto err_gpio;
+ }
+ }
+
+ wm831x_wdt_miscdev.parent = &pdev->dev;
+
+ ret = misc_register(&wm831x_wdt_miscdev);
+ if (ret != 0) {
+ dev_err(wm831x->dev, "Failed to register miscdev: %d\n", ret);
+ goto err_gpio;
+ }
+
+ return 0;
+
+err_gpio:
+ if (update_gpio) {
+ gpio_free(update_gpio);
+ update_gpio = 0;
+ }
+err:
+ return ret;
+}
+
+static int __devexit wm831x_wdt_remove(struct platform_device *pdev)
+{
+ if (update_gpio) {
+ gpio_free(update_gpio);
+ update_gpio = 0;
+ }
+
+ misc_deregister(&wm831x_wdt_miscdev);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_wdt_driver = {
+ .probe = wm831x_wdt_probe,
+ .remove = __devexit_p(wm831x_wdt_remove),
+ .driver = {
+ .name = "wm831x-watchdog",
+ },
+};
+
+static int __init wm831x_wdt_init(void)
+{
+ return platform_driver_register(&wm831x_wdt_driver);
+}
+module_init(wm831x_wdt_init);
+
+static void __exit wm831x_wdt_exit(void)
+{
+ platform_driver_unregister(&wm831x_wdt_driver);
+}
+module_exit(wm831x_wdt_exit);
+
+MODULE_AUTHOR("Mark Brown");
+MODULE_DESCRIPTION("WM831x Watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-watchdog");