diff options
author | Wolfgang Wiedmeyer <wolfgit@wiedmeyer.de> | 2015-10-23 03:29:33 +0200 |
---|---|---|
committer | Wolfgang Wiedmeyer <wolfgit@wiedmeyer.de> | 2015-10-23 03:29:33 +0200 |
commit | 15dfd0df63ce6847081d09b2bbd567cc0cc4eae1 (patch) | |
tree | 3b73f24fcef970bfcace3cbb297cfa57f3994682 /drivers/base | |
parent | 328aa7a45af61bc0060c80847daa67fef7b9c0d0 (diff) | |
parent | 0149138c4142da287d23f9d5c6038f7fb5e30ac2 (diff) | |
download | kernel_samsung_smdk4412-15dfd0df63ce6847081d09b2bbd567cc0cc4eae1.zip kernel_samsung_smdk4412-15dfd0df63ce6847081d09b2bbd567cc0cc4eae1.tar.gz kernel_samsung_smdk4412-15dfd0df63ce6847081d09b2bbd567cc0cc4eae1.tar.bz2 |
initial merge with 3.2.72
Diffstat (limited to 'drivers/base')
-rw-r--r-- | drivers/base/power/common.c | 86 | ||||
-rw-r--r-- | drivers/base/power/domain.c | 1328 | ||||
-rw-r--r-- | drivers/base/power/qos.c | 414 | ||||
-rw-r--r-- | drivers/base/regmap/Kconfig | 15 | ||||
-rw-r--r-- | drivers/base/regmap/Makefile | 4 | ||||
-rw-r--r-- | drivers/base/regmap/internal.h | 128 | ||||
-rw-r--r-- | drivers/base/regmap/regcache-indexed.c | 64 | ||||
-rw-r--r-- | drivers/base/regmap/regcache-lzo.c | 361 | ||||
-rw-r--r-- | drivers/base/regmap/regcache-rbtree.c | 345 | ||||
-rw-r--r-- | drivers/base/regmap/regcache.c | 402 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-debugfs.c | 209 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-i2c.c | 114 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-spi.c | 73 | ||||
-rw-r--r-- | drivers/base/regmap/regmap.c | 592 |
14 files changed, 4135 insertions, 0 deletions
diff --git a/drivers/base/power/common.c b/drivers/base/power/common.c new file mode 100644 index 0000000..4af7c1c --- /dev/null +++ b/drivers/base/power/common.c @@ -0,0 +1,86 @@ +/* + * drivers/base/power/common.c - Common device power management code. + * + * Copyright (C) 2011 Rafael J. Wysocki <rjw@sisk.pl>, Renesas Electronics Corp. + * + * This file is released under the GPLv2. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/pm_clock.h> + +/** + * dev_pm_get_subsys_data - Create or refcount power.subsys_data for device. + * @dev: Device to handle. + * + * If power.subsys_data is NULL, point it to a new object, otherwise increment + * its reference counter. Return 1 if a new object has been created, otherwise + * return 0 or error code. + */ +int dev_pm_get_subsys_data(struct device *dev) +{ + struct pm_subsys_data *psd; + int ret = 0; + + psd = kzalloc(sizeof(*psd), GFP_KERNEL); + if (!psd) + return -ENOMEM; + + spin_lock_irq(&dev->power.lock); + + if (dev->power.subsys_data) { + dev->power.subsys_data->refcount++; + } else { + spin_lock_init(&psd->lock); + psd->refcount = 1; + dev->power.subsys_data = psd; + pm_clk_init(dev); + psd = NULL; + ret = 1; + } + + spin_unlock_irq(&dev->power.lock); + + /* kfree() verifies that its argument is nonzero. */ + kfree(psd); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_get_subsys_data); + +/** + * dev_pm_put_subsys_data - Drop reference to power.subsys_data. + * @dev: Device to handle. + * + * If the reference counter of power.subsys_data is zero after dropping the + * reference, power.subsys_data is removed. Return 1 if that happens or 0 + * otherwise. + */ +int dev_pm_put_subsys_data(struct device *dev) +{ + struct pm_subsys_data *psd; + int ret = 0; + + spin_lock_irq(&dev->power.lock); + + psd = dev_to_psd(dev); + if (!psd) { + ret = -EINVAL; + goto out; + } + + if (--psd->refcount == 0) { + dev->power.subsys_data = NULL; + kfree(psd); + ret = 1; + } + + out: + spin_unlock_irq(&dev->power.lock); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_put_subsys_data); diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c new file mode 100644 index 0000000..6790cf7 --- /dev/null +++ b/drivers/base/power/domain.c @@ -0,0 +1,1328 @@ +/* + * drivers/base/power/domain.c - Common code related to device power domains. + * + * Copyright (C) 2011 Rafael J. Wysocki <rjw@sisk.pl>, Renesas Electronics Corp. + * + * This file is released under the GPLv2. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> +#include <linux/pm_domain.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/sched.h> +#include <linux/suspend.h> + +static LIST_HEAD(gpd_list); +static DEFINE_MUTEX(gpd_list_lock); + +#ifdef CONFIG_PM + +static struct generic_pm_domain *dev_to_genpd(struct device *dev) +{ + if (IS_ERR_OR_NULL(dev->pm_domain)) + return ERR_PTR(-EINVAL); + + return pd_to_genpd(dev->pm_domain); +} + +static bool genpd_sd_counter_dec(struct generic_pm_domain *genpd) +{ + bool ret = false; + + if (!WARN_ON(atomic_read(&genpd->sd_count) == 0)) + ret = !!atomic_dec_and_test(&genpd->sd_count); + + return ret; +} + +static void genpd_sd_counter_inc(struct generic_pm_domain *genpd) +{ + atomic_inc(&genpd->sd_count); + smp_mb__after_atomic_inc(); +} + +static void genpd_acquire_lock(struct generic_pm_domain *genpd) +{ + DEFINE_WAIT(wait); + + mutex_lock(&genpd->lock); + /* + * Wait for the domain to transition into either the active, + * or the power off state. + */ + for (;;) { + prepare_to_wait(&genpd->status_wait_queue, &wait, + TASK_UNINTERRUPTIBLE); + if (genpd->status == GPD_STATE_ACTIVE + || genpd->status == GPD_STATE_POWER_OFF) + break; + mutex_unlock(&genpd->lock); + + schedule(); + + mutex_lock(&genpd->lock); + } + finish_wait(&genpd->status_wait_queue, &wait); +} + +static void genpd_release_lock(struct generic_pm_domain *genpd) +{ + mutex_unlock(&genpd->lock); +} + +static void genpd_set_active(struct generic_pm_domain *genpd) +{ + if (genpd->resume_count == 0) + genpd->status = GPD_STATE_ACTIVE; +} + +/** + * __pm_genpd_poweron - Restore power to a given PM domain and its masters. + * @genpd: PM domain to power up. + * + * Restore power to @genpd and all of its masters so that it is possible to + * resume a device belonging to it. + */ +int __pm_genpd_poweron(struct generic_pm_domain *genpd) + __releases(&genpd->lock) __acquires(&genpd->lock) +{ + struct gpd_link *link; + DEFINE_WAIT(wait); + int ret = 0; + + /* If the domain's master is being waited for, we have to wait too. */ + for (;;) { + prepare_to_wait(&genpd->status_wait_queue, &wait, + TASK_UNINTERRUPTIBLE); + if (genpd->status != GPD_STATE_WAIT_MASTER) + break; + mutex_unlock(&genpd->lock); + + schedule(); + + mutex_lock(&genpd->lock); + } + finish_wait(&genpd->status_wait_queue, &wait); + + if (genpd->status == GPD_STATE_ACTIVE + || (genpd->prepared_count > 0 && genpd->suspend_power_off)) + return 0; + + if (genpd->status != GPD_STATE_POWER_OFF) { + genpd_set_active(genpd); + return 0; + } + + /* + * The list is guaranteed not to change while the loop below is being + * executed, unless one of the masters' .power_on() callbacks fiddles + * with it. + */ + list_for_each_entry(link, &genpd->slave_links, slave_node) { + genpd_sd_counter_inc(link->master); + genpd->status = GPD_STATE_WAIT_MASTER; + + mutex_unlock(&genpd->lock); + + ret = pm_genpd_poweron(link->master); + + mutex_lock(&genpd->lock); + + /* + * The "wait for parent" status is guaranteed not to change + * while the master is powering on. + */ + genpd->status = GPD_STATE_POWER_OFF; + wake_up_all(&genpd->status_wait_queue); + if (ret) { + genpd_sd_counter_dec(link->master); + goto err; + } + } + + if (genpd->power_on) { + ret = genpd->power_on(genpd); + if (ret) + goto err; + } + + genpd_set_active(genpd); + + return 0; + + err: + list_for_each_entry_continue_reverse(link, &genpd->slave_links, slave_node) + genpd_sd_counter_dec(link->master); + + return ret; +} + +/** + * pm_genpd_poweron - Restore power to a given PM domain and its masters. + * @genpd: PM domain to power up. + */ +int pm_genpd_poweron(struct generic_pm_domain *genpd) +{ + int ret; + + mutex_lock(&genpd->lock); + ret = __pm_genpd_poweron(genpd); + mutex_unlock(&genpd->lock); + return ret; +} + +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PM_RUNTIME + +/** + * __pm_genpd_save_device - Save the pre-suspend state of a device. + * @pdd: Domain data of the device to save the state of. + * @genpd: PM domain the device belongs to. + */ +static int __pm_genpd_save_device(struct pm_domain_data *pdd, + struct generic_pm_domain *genpd) + __releases(&genpd->lock) __acquires(&genpd->lock) +{ + struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd); + struct device *dev = pdd->dev; + struct device_driver *drv = dev->driver; + int ret = 0; + + if (gpd_data->need_restore) + return 0; + + mutex_unlock(&genpd->lock); + + if (drv && drv->pm && drv->pm->runtime_suspend) { + if (genpd->start_device) + genpd->start_device(dev); + + ret = drv->pm->runtime_suspend(dev); + + if (genpd->stop_device) + genpd->stop_device(dev); + } + + mutex_lock(&genpd->lock); + + if (!ret) + gpd_data->need_restore = true; + + return ret; +} + +/** + * __pm_genpd_restore_device - Restore the pre-suspend state of a device. + * @pdd: Domain data of the device to restore the state of. + * @genpd: PM domain the device belongs to. + */ +static void __pm_genpd_restore_device(struct pm_domain_data *pdd, + struct generic_pm_domain *genpd) + __releases(&genpd->lock) __acquires(&genpd->lock) +{ + struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd); + struct device *dev = pdd->dev; + struct device_driver *drv = dev->driver; + + if (!gpd_data->need_restore) + return; + + mutex_unlock(&genpd->lock); + + if (drv && drv->pm && drv->pm->runtime_resume) { + if (genpd->start_device) + genpd->start_device(dev); + + drv->pm->runtime_resume(dev); + + if (genpd->stop_device) + genpd->stop_device(dev); + } + + mutex_lock(&genpd->lock); + + gpd_data->need_restore = false; +} + +/** + * genpd_abort_poweroff - Check if a PM domain power off should be aborted. + * @genpd: PM domain to check. + * + * Return true if a PM domain's status changed to GPD_STATE_ACTIVE during + * a "power off" operation, which means that a "power on" has occured in the + * meantime, or if its resume_count field is different from zero, which means + * that one of its devices has been resumed in the meantime. + */ +static bool genpd_abort_poweroff(struct generic_pm_domain *genpd) +{ + return genpd->status == GPD_STATE_WAIT_MASTER + || genpd->status == GPD_STATE_ACTIVE || genpd->resume_count > 0; +} + +/** + * genpd_queue_power_off_work - Queue up the execution of pm_genpd_poweroff(). + * @genpd: PM domait to power off. + * + * Queue up the execution of pm_genpd_poweroff() unless it's already been done + * before. + */ +void genpd_queue_power_off_work(struct generic_pm_domain *genpd) +{ + if (!work_pending(&genpd->power_off_work)) + queue_work(pm_wq, &genpd->power_off_work); +} + +/** + * pm_genpd_poweroff - Remove power from a given PM domain. + * @genpd: PM domain to power down. + * + * If all of the @genpd's devices have been suspended and all of its subdomains + * have been powered down, run the runtime suspend callbacks provided by all of + * the @genpd's devices' drivers and remove power from @genpd. + */ +static int pm_genpd_poweroff(struct generic_pm_domain *genpd) + __releases(&genpd->lock) __acquires(&genpd->lock) +{ + struct pm_domain_data *pdd; + struct gpd_link *link; + unsigned int not_suspended; + int ret = 0; + + start: + /* + * Do not try to power off the domain in the following situations: + * (1) The domain is already in the "power off" state. + * (2) The domain is waiting for its master to power up. + * (3) One of the domain's devices is being resumed right now. + * (4) System suspend is in progress. + */ + if (genpd->status == GPD_STATE_POWER_OFF + || genpd->status == GPD_STATE_WAIT_MASTER + || genpd->resume_count > 0 || genpd->prepared_count > 0) + return 0; + + if (atomic_read(&genpd->sd_count) > 0) + return -EBUSY; + + not_suspended = 0; + list_for_each_entry(pdd, &genpd->dev_list, list_node) + if (pdd->dev->driver && (!pm_runtime_suspended(pdd->dev) + || pdd->dev->power.irq_safe)) + not_suspended++; + + if (not_suspended > genpd->in_progress) + return -EBUSY; + + if (genpd->poweroff_task) { + /* + * Another instance of pm_genpd_poweroff() is executing + * callbacks, so tell it to start over and return. + */ + genpd->status = GPD_STATE_REPEAT; + return 0; + } + + if (genpd->gov && genpd->gov->power_down_ok) { + if (!genpd->gov->power_down_ok(&genpd->domain)) + return -EAGAIN; + } + + genpd->status = GPD_STATE_BUSY; + genpd->poweroff_task = current; + + list_for_each_entry_reverse(pdd, &genpd->dev_list, list_node) { + ret = atomic_read(&genpd->sd_count) == 0 ? + __pm_genpd_save_device(pdd, genpd) : -EBUSY; + + if (genpd_abort_poweroff(genpd)) + goto out; + + if (ret) { + genpd_set_active(genpd); + goto out; + } + + if (genpd->status == GPD_STATE_REPEAT) { + genpd->poweroff_task = NULL; + goto start; + } + } + + if (genpd->power_off) { + if (atomic_read(&genpd->sd_count) > 0) { + ret = -EBUSY; + goto out; + } + + /* + * If sd_count > 0 at this point, one of the subdomains hasn't + * managed to call pm_genpd_poweron() for the master yet after + * incrementing it. In that case pm_genpd_poweron() will wait + * for us to drop the lock, so we can call .power_off() and let + * the pm_genpd_poweron() restore power for us (this shouldn't + * happen very often). + */ + ret = genpd->power_off(genpd); + if (ret == -EBUSY) { + genpd_set_active(genpd); + goto out; + } + } + + genpd->status = GPD_STATE_POWER_OFF; + + list_for_each_entry(link, &genpd->slave_links, slave_node) { + genpd_sd_counter_dec(link->master); + genpd_queue_power_off_work(link->master); + } + + out: + genpd->poweroff_task = NULL; + wake_up_all(&genpd->status_wait_queue); + return ret; +} + +/** + * genpd_power_off_work_fn - Power off PM domain whose subdomain count is 0. + * @work: Work structure used for scheduling the execution of this function. + */ +static void genpd_power_off_work_fn(struct work_struct *work) +{ + struct generic_pm_domain *genpd; + + genpd = container_of(work, struct generic_pm_domain, power_off_work); + + genpd_acquire_lock(genpd); + pm_genpd_poweroff(genpd); + genpd_release_lock(genpd); +} + +/** + * pm_genpd_runtime_suspend - Suspend a device belonging to I/O PM domain. + * @dev: Device to suspend. + * + * Carry out a runtime suspend of a device under the assumption that its + * pm_domain field points to the domain member of an object of type + * struct generic_pm_domain representing a PM domain consisting of I/O devices. + */ +static int pm_genpd_runtime_suspend(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + might_sleep_if(!genpd->dev_irq_safe); + + if (genpd->stop_device) { + int ret = genpd->stop_device(dev); + if (ret) + return ret; + } + + /* + * If power.irq_safe is set, this routine will be run with interrupts + * off, so it can't use mutexes. + */ + if (dev->power.irq_safe) + return 0; + + mutex_lock(&genpd->lock); + genpd->in_progress++; + pm_genpd_poweroff(genpd); + genpd->in_progress--; + mutex_unlock(&genpd->lock); + + return 0; +} + +/** + * pm_genpd_runtime_resume - Resume a device belonging to I/O PM domain. + * @dev: Device to resume. + * + * Carry out a runtime resume of a device under the assumption that its + * pm_domain field points to the domain member of an object of type + * struct generic_pm_domain representing a PM domain consisting of I/O devices. + */ +static int pm_genpd_runtime_resume(struct device *dev) +{ + struct generic_pm_domain *genpd; + DEFINE_WAIT(wait); + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + might_sleep_if(!genpd->dev_irq_safe); + + /* If power.irq_safe, the PM domain is never powered off. */ + if (dev->power.irq_safe) + goto out; + + mutex_lock(&genpd->lock); + ret = __pm_genpd_poweron(genpd); + if (ret) { + mutex_unlock(&genpd->lock); + return ret; + } + genpd->status = GPD_STATE_BUSY; + genpd->resume_count++; + for (;;) { + prepare_to_wait(&genpd->status_wait_queue, &wait, + TASK_UNINTERRUPTIBLE); + /* + * If current is the powering off task, we have been called + * reentrantly from one of the device callbacks, so we should + * not wait. + */ + if (!genpd->poweroff_task || genpd->poweroff_task == current) + break; + mutex_unlock(&genpd->lock); + + schedule(); + + mutex_lock(&genpd->lock); + } + finish_wait(&genpd->status_wait_queue, &wait); + __pm_genpd_restore_device(dev->power.subsys_data->domain_data, genpd); + genpd->resume_count--; + genpd_set_active(genpd); + wake_up_all(&genpd->status_wait_queue); + mutex_unlock(&genpd->lock); + + out: + if (genpd->start_device) + genpd->start_device(dev); + + return 0; +} + +/** + * pm_genpd_poweroff_unused - Power off all PM domains with no devices in use. + */ +void pm_genpd_poweroff_unused(void) +{ + struct generic_pm_domain *genpd; + + mutex_lock(&gpd_list_lock); + + list_for_each_entry(genpd, &gpd_list, gpd_list_node) + genpd_queue_power_off_work(genpd); + + mutex_unlock(&gpd_list_lock); +} + +#else + +static inline void genpd_power_off_work_fn(struct work_struct *work) {} + +#define pm_genpd_runtime_suspend NULL +#define pm_genpd_runtime_resume NULL + +#endif /* CONFIG_PM_RUNTIME */ + +#ifdef CONFIG_PM_SLEEP + +/** + * pm_genpd_sync_poweroff - Synchronously power off a PM domain and its masters. + * @genpd: PM domain to power off, if possible. + * + * Check if the given PM domain can be powered off (during system suspend or + * hibernation) and do that if so. Also, in that case propagate to its masters. + * + * This function is only called in "noirq" stages of system power transitions, + * so it need not acquire locks (all of the "noirq" callbacks are executed + * sequentially, so it is guaranteed that it will never run twice in parallel). + */ +static void pm_genpd_sync_poweroff(struct generic_pm_domain *genpd) +{ + struct gpd_link *link; + + if (genpd->status == GPD_STATE_POWER_OFF) + return; + + if (genpd->suspended_count != genpd->device_count + || atomic_read(&genpd->sd_count) > 0) + return; + + if (genpd->power_off) + genpd->power_off(genpd); + + genpd->status = GPD_STATE_POWER_OFF; + + list_for_each_entry(link, &genpd->slave_links, slave_node) { + genpd_sd_counter_dec(link->master); + pm_genpd_sync_poweroff(link->master); + } +} + +/** + * resume_needed - Check whether to resume a device before system suspend. + * @dev: Device to check. + * @genpd: PM domain the device belongs to. + * + * There are two cases in which a device that can wake up the system from sleep + * states should be resumed by pm_genpd_prepare(): (1) if the device is enabled + * to wake up the system and it has to remain active for this purpose while the + * system is in the sleep state and (2) if the device is not enabled to wake up + * the system from sleep states and it generally doesn't generate wakeup signals + * by itself (those signals are generated on its behalf by other parts of the + * system). In the latter case it may be necessary to reconfigure the device's + * wakeup settings during system suspend, because it may have been set up to + * signal remote wakeup from the system's working state as needed by runtime PM. + * Return 'true' in either of the above cases. + */ +static bool resume_needed(struct device *dev, struct generic_pm_domain *genpd) +{ + bool active_wakeup; + + if (!device_can_wakeup(dev)) + return false; + + active_wakeup = genpd->active_wakeup && genpd->active_wakeup(dev); + return device_may_wakeup(dev) ? active_wakeup : !active_wakeup; +} + +/** + * pm_genpd_prepare - Start power transition of a device in a PM domain. + * @dev: Device to start the transition of. + * + * Start a power transition of a device (during a system-wide power transition) + * under the assumption that its pm_domain field points to the domain member of + * an object of type struct generic_pm_domain representing a PM domain + * consisting of I/O devices. + */ +static int pm_genpd_prepare(struct device *dev) +{ + struct generic_pm_domain *genpd; + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + /* + * If a wakeup request is pending for the device, it should be woken up + * at this point and a system wakeup event should be reported if it's + * set up to wake up the system from sleep states. + */ + pm_runtime_get_noresume(dev); + if (pm_runtime_barrier(dev) && device_may_wakeup(dev)) + pm_wakeup_event(dev, 0); + + if (pm_wakeup_pending()) { + pm_runtime_put_sync(dev); + return -EBUSY; + } + + if (resume_needed(dev, genpd)) + pm_runtime_resume(dev); + + genpd_acquire_lock(genpd); + + if (genpd->prepared_count++ == 0) + genpd->suspend_power_off = genpd->status == GPD_STATE_POWER_OFF; + + genpd_release_lock(genpd); + + if (genpd->suspend_power_off) { + pm_runtime_put_noidle(dev); + return 0; + } + + /* + * The PM domain must be in the GPD_STATE_ACTIVE state at this point, + * so pm_genpd_poweron() will return immediately, but if the device + * is suspended (e.g. it's been stopped by .stop_device()), we need + * to make it operational. + */ + pm_runtime_resume(dev); + __pm_runtime_disable(dev, false); + + ret = pm_generic_prepare(dev); + if (ret) { + mutex_lock(&genpd->lock); + + if (--genpd->prepared_count == 0) + genpd->suspend_power_off = false; + + mutex_unlock(&genpd->lock); + pm_runtime_enable(dev); + } + + pm_runtime_put_sync(dev); + return ret; +} + +/** + * pm_genpd_suspend - Suspend a device belonging to an I/O PM domain. + * @dev: Device to suspend. + * + * Suspend a device under the assumption that its pm_domain field points to the + * domain member of an object of type struct generic_pm_domain representing + * a PM domain consisting of I/O devices. + */ +static int pm_genpd_suspend(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + return genpd->suspend_power_off ? 0 : pm_generic_suspend(dev); +} + +/** + * pm_genpd_suspend_noirq - Late suspend of a device from an I/O PM domain. + * @dev: Device to suspend. + * + * Carry out a late suspend of a device under the assumption that its + * pm_domain field points to the domain member of an object of type + * struct generic_pm_domain representing a PM domain consisting of I/O devices. + */ +static int pm_genpd_suspend_noirq(struct device *dev) +{ + struct generic_pm_domain *genpd; + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + if (genpd->suspend_power_off) + return 0; + + ret = pm_generic_suspend_noirq(dev); + if (ret) + return ret; + + if (dev->power.wakeup_path + && genpd->active_wakeup && genpd->active_wakeup(dev)) + return 0; + + if (genpd->stop_device) + genpd->stop_device(dev); + + /* + * Since all of the "noirq" callbacks are executed sequentially, it is + * guaranteed that this function will never run twice in parallel for + * the same PM domain, so it is not necessary to use locking here. + */ + genpd->suspended_count++; + pm_genpd_sync_poweroff(genpd); + + return 0; +} + +/** + * pm_genpd_resume_noirq - Early resume of a device from an I/O power domain. + * @dev: Device to resume. + * + * Carry out an early resume of a device under the assumption that its + * pm_domain field points to the domain member of an object of type + * struct generic_pm_domain representing a power domain consisting of I/O + * devices. + */ +static int pm_genpd_resume_noirq(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + if (genpd->suspend_power_off) + return 0; + + /* + * Since all of the "noirq" callbacks are executed sequentially, it is + * guaranteed that this function will never run twice in parallel for + * the same PM domain, so it is not necessary to use locking here. + */ + pm_genpd_poweron(genpd); + genpd->suspended_count--; + if (genpd->start_device) + genpd->start_device(dev); + + return pm_generic_resume_noirq(dev); +} + +/** + * pm_genpd_resume - Resume a device belonging to an I/O power domain. + * @dev: Device to resume. + * + * Resume a device under the assumption that its pm_domain field points to the + * domain member of an object of type struct generic_pm_domain representing + * a power domain consisting of I/O devices. + */ +static int pm_genpd_resume(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + return genpd->suspend_power_off ? 0 : pm_generic_resume(dev); +} + +/** + * pm_genpd_freeze - Freeze a device belonging to an I/O power domain. + * @dev: Device to freeze. + * + * Freeze a device under the assumption that its pm_domain field points to the + * domain member of an object of type struct generic_pm_domain representing + * a power domain consisting of I/O devices. + */ +static int pm_genpd_freeze(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + return genpd->suspend_power_off ? 0 : pm_generic_freeze(dev); +} + +/** + * pm_genpd_freeze_noirq - Late freeze of a device from an I/O power domain. + * @dev: Device to freeze. + * + * Carry out a late freeze of a device under the assumption that its + * pm_domain field points to the domain member of an object of type + * struct generic_pm_domain representing a power domain consisting of I/O + * devices. + */ +static int pm_genpd_freeze_noirq(struct device *dev) +{ + struct generic_pm_domain *genpd; + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + if (genpd->suspend_power_off) + return 0; + + ret = pm_generic_freeze_noirq(dev); + if (ret) + return ret; + + if (genpd->stop_device) + genpd->stop_device(dev); + + return 0; +} + +/** + * pm_genpd_thaw_noirq - Early thaw of a device from an I/O power domain. + * @dev: Device to thaw. + * + * Carry out an early thaw of a device under the assumption that its + * pm_domain field points to the domain member of an object of type + * struct generic_pm_domain representing a power domain consisting of I/O + * devices. + */ +static int pm_genpd_thaw_noirq(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + if (genpd->suspend_power_off) + return 0; + + if (genpd->start_device) + genpd->start_device(dev); + + return pm_generic_thaw_noirq(dev); +} + +/** + * pm_genpd_thaw - Thaw a device belonging to an I/O power domain. + * @dev: Device to thaw. + * + * Thaw a device under the assumption that its pm_domain field points to the + * domain member of an object of type struct generic_pm_domain representing + * a power domain consisting of I/O devices. + */ +static int pm_genpd_thaw(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + return genpd->suspend_power_off ? 0 : pm_generic_thaw(dev); +} + +/** + * pm_genpd_dev_poweroff - Power off a device belonging to an I/O PM domain. + * @dev: Device to suspend. + * + * Power off a device under the assumption that its pm_domain field points to + * the domain member of an object of type struct generic_pm_domain representing + * a PM domain consisting of I/O devices. + */ +static int pm_genpd_dev_poweroff(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + return genpd->suspend_power_off ? 0 : pm_generic_poweroff(dev); +} + +/** + * pm_genpd_dev_poweroff_noirq - Late power off of a device from a PM domain. + * @dev: Device to suspend. + * + * Carry out a late powering off of a device under the assumption that its + * pm_domain field points to the domain member of an object of type + * struct generic_pm_domain representing a PM domain consisting of I/O devices. + */ +static int pm_genpd_dev_poweroff_noirq(struct device *dev) +{ + struct generic_pm_domain *genpd; + int ret; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + if (genpd->suspend_power_off) + return 0; + + ret = pm_generic_poweroff_noirq(dev); + if (ret) + return ret; + + if (dev->power.wakeup_path + && genpd->active_wakeup && genpd->active_wakeup(dev)) + return 0; + + if (genpd->stop_device) + genpd->stop_device(dev); + + /* + * Since all of the "noirq" callbacks are executed sequentially, it is + * guaranteed that this function will never run twice in parallel for + * the same PM domain, so it is not necessary to use locking here. + */ + genpd->suspended_count++; + pm_genpd_sync_poweroff(genpd); + + return 0; +} + +/** + * pm_genpd_restore_noirq - Early restore of a device from an I/O power domain. + * @dev: Device to resume. + * + * Carry out an early restore of a device under the assumption that its + * pm_domain field points to the domain member of an object of type + * struct generic_pm_domain representing a power domain consisting of I/O + * devices. + */ +static int pm_genpd_restore_noirq(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + /* + * Since all of the "noirq" callbacks are executed sequentially, it is + * guaranteed that this function will never run twice in parallel for + * the same PM domain, so it is not necessary to use locking here. + */ + genpd->status = GPD_STATE_POWER_OFF; + if (genpd->suspend_power_off) { + /* + * The boot kernel might put the domain into the power on state, + * so make sure it really is powered off. + */ + if (genpd->power_off) + genpd->power_off(genpd); + return 0; + } + + pm_genpd_poweron(genpd); + genpd->suspended_count--; + if (genpd->start_device) + genpd->start_device(dev); + + return pm_generic_restore_noirq(dev); +} + +/** + * pm_genpd_restore - Restore a device belonging to an I/O power domain. + * @dev: Device to resume. + * + * Restore a device under the assumption that its pm_domain field points to the + * domain member of an object of type struct generic_pm_domain representing + * a power domain consisting of I/O devices. + */ +static int pm_genpd_restore(struct device *dev) +{ + struct generic_pm_domain *genpd; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return -EINVAL; + + return genpd->suspend_power_off ? 0 : pm_generic_restore(dev); +} + +/** + * pm_genpd_complete - Complete power transition of a device in a power domain. + * @dev: Device to complete the transition of. + * + * Complete a power transition of a device (during a system-wide power + * transition) under the assumption that its pm_domain field points to the + * domain member of an object of type struct generic_pm_domain representing + * a power domain consisting of I/O devices. + */ +static void pm_genpd_complete(struct device *dev) +{ + struct generic_pm_domain *genpd; + bool run_complete; + + dev_dbg(dev, "%s()\n", __func__); + + genpd = dev_to_genpd(dev); + if (IS_ERR(genpd)) + return; + + mutex_lock(&genpd->lock); + + run_complete = !genpd->suspend_power_off; + if (--genpd->prepared_count == 0) + genpd->suspend_power_off = false; + + mutex_unlock(&genpd->lock); + + if (run_complete) { + pm_generic_complete(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_idle(dev); + } +} + +#else + +#define pm_genpd_prepare NULL +#define pm_genpd_suspend NULL +#define pm_genpd_suspend_noirq NULL +#define pm_genpd_resume_noirq NULL +#define pm_genpd_resume NULL +#define pm_genpd_freeze NULL +#define pm_genpd_freeze_noirq NULL +#define pm_genpd_thaw_noirq NULL +#define pm_genpd_thaw NULL +#define pm_genpd_dev_poweroff_noirq NULL +#define pm_genpd_dev_poweroff NULL +#define pm_genpd_restore_noirq NULL +#define pm_genpd_restore NULL +#define pm_genpd_complete NULL + +#endif /* CONFIG_PM_SLEEP */ + +/** + * pm_genpd_add_device - Add a device to an I/O PM domain. + * @genpd: PM domain to add the device to. + * @dev: Device to be added. + */ +int pm_genpd_add_device(struct generic_pm_domain *genpd, struct device *dev) +{ + struct generic_pm_domain_data *gpd_data; + struct pm_domain_data *pdd; + int ret = 0; + + dev_dbg(dev, "%s()\n", __func__); + + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev)) + return -EINVAL; + + genpd_acquire_lock(genpd); + + if (genpd->status == GPD_STATE_POWER_OFF) { + ret = -EINVAL; + goto out; + } + + if (genpd->prepared_count > 0) { + ret = -EAGAIN; + goto out; + } + + list_for_each_entry(pdd, &genpd->dev_list, list_node) + if (pdd->dev == dev) { + ret = -EINVAL; + goto out; + } + + gpd_data = kzalloc(sizeof(*gpd_data), GFP_KERNEL); + if (!gpd_data) { + ret = -ENOMEM; + goto out; + } + + genpd->device_count++; + + dev->pm_domain = &genpd->domain; + dev_pm_get_subsys_data(dev); + dev->power.subsys_data->domain_data = &gpd_data->base; + gpd_data->base.dev = dev; + gpd_data->need_restore = false; + list_add_tail(&gpd_data->base.list_node, &genpd->dev_list); + + out: + genpd_release_lock(genpd); + + return ret; +} + +/** + * pm_genpd_remove_device - Remove a device from an I/O PM domain. + * @genpd: PM domain to remove the device from. + * @dev: Device to be removed. + */ +int pm_genpd_remove_device(struct generic_pm_domain *genpd, + struct device *dev) +{ + struct pm_domain_data *pdd; + int ret = -EINVAL; + + dev_dbg(dev, "%s()\n", __func__); + + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(dev)) + return -EINVAL; + + genpd_acquire_lock(genpd); + + if (genpd->prepared_count > 0) { + ret = -EAGAIN; + goto out; + } + + list_for_each_entry(pdd, &genpd->dev_list, list_node) { + if (pdd->dev != dev) + continue; + + list_del_init(&pdd->list_node); + pdd->dev = NULL; + dev_pm_put_subsys_data(dev); + dev->pm_domain = NULL; + kfree(to_gpd_data(pdd)); + + genpd->device_count--; + + ret = 0; + break; + } + + out: + genpd_release_lock(genpd); + + return ret; +} + +/** + * pm_genpd_add_subdomain - Add a subdomain to an I/O PM domain. + * @genpd: Master PM domain to add the subdomain to. + * @subdomain: Subdomain to be added. + */ +int pm_genpd_add_subdomain(struct generic_pm_domain *genpd, + struct generic_pm_domain *subdomain) +{ + struct gpd_link *link; + int ret = 0; + + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(subdomain)) + return -EINVAL; + + start: + genpd_acquire_lock(genpd); + mutex_lock_nested(&subdomain->lock, SINGLE_DEPTH_NESTING); + + if (subdomain->status != GPD_STATE_POWER_OFF + && subdomain->status != GPD_STATE_ACTIVE) { + mutex_unlock(&subdomain->lock); + genpd_release_lock(genpd); + goto start; + } + + if (genpd->status == GPD_STATE_POWER_OFF + && subdomain->status != GPD_STATE_POWER_OFF) { + ret = -EINVAL; + goto out; + } + + list_for_each_entry(link, &genpd->slave_links, slave_node) { + if (link->slave == subdomain && link->master == genpd) { + ret = -EINVAL; + goto out; + } + } + + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (!link) { + ret = -ENOMEM; + goto out; + } + link->master = genpd; + list_add_tail(&link->master_node, &genpd->master_links); + link->slave = subdomain; + list_add_tail(&link->slave_node, &subdomain->slave_links); + if (subdomain->status != GPD_STATE_POWER_OFF) + genpd_sd_counter_inc(genpd); + + out: + mutex_unlock(&subdomain->lock); + genpd_release_lock(genpd); + + return ret; +} + +/** + * pm_genpd_remove_subdomain - Remove a subdomain from an I/O PM domain. + * @genpd: Master PM domain to remove the subdomain from. + * @subdomain: Subdomain to be removed. + */ +int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd, + struct generic_pm_domain *subdomain) +{ + struct gpd_link *link; + int ret = -EINVAL; + + if (IS_ERR_OR_NULL(genpd) || IS_ERR_OR_NULL(subdomain)) + return -EINVAL; + + start: + genpd_acquire_lock(genpd); + + list_for_each_entry(link, &genpd->master_links, master_node) { + if (link->slave != subdomain) + continue; + + mutex_lock_nested(&subdomain->lock, SINGLE_DEPTH_NESTING); + + if (subdomain->status != GPD_STATE_POWER_OFF + && subdomain->status != GPD_STATE_ACTIVE) { + mutex_unlock(&subdomain->lock); + genpd_release_lock(genpd); + goto start; + } + + list_del(&link->master_node); + list_del(&link->slave_node); + kfree(link); + if (subdomain->status != GPD_STATE_POWER_OFF) + genpd_sd_counter_dec(genpd); + + mutex_unlock(&subdomain->lock); + + ret = 0; + break; + } + + genpd_release_lock(genpd); + + return ret; +} + +/** + * pm_genpd_init - Initialize a generic I/O PM domain object. + * @genpd: PM domain object to initialize. + * @gov: PM domain governor to associate with the domain (may be NULL). + * @is_off: Initial value of the domain's power_is_off field. + */ +void pm_genpd_init(struct generic_pm_domain *genpd, + struct dev_power_governor *gov, bool is_off) +{ + if (IS_ERR_OR_NULL(genpd)) + return; + + INIT_LIST_HEAD(&genpd->master_links); + INIT_LIST_HEAD(&genpd->slave_links); + INIT_LIST_HEAD(&genpd->dev_list); + mutex_init(&genpd->lock); + genpd->gov = gov; + INIT_WORK(&genpd->power_off_work, genpd_power_off_work_fn); + genpd->in_progress = 0; + atomic_set(&genpd->sd_count, 0); + genpd->status = is_off ? GPD_STATE_POWER_OFF : GPD_STATE_ACTIVE; + init_waitqueue_head(&genpd->status_wait_queue); + genpd->poweroff_task = NULL; + genpd->resume_count = 0; + genpd->device_count = 0; + genpd->suspended_count = 0; + genpd->domain.ops.runtime_suspend = pm_genpd_runtime_suspend; + genpd->domain.ops.runtime_resume = pm_genpd_runtime_resume; + genpd->domain.ops.runtime_idle = pm_generic_runtime_idle; + genpd->domain.ops.prepare = pm_genpd_prepare; + genpd->domain.ops.suspend = pm_genpd_suspend; + genpd->domain.ops.suspend_noirq = pm_genpd_suspend_noirq; + genpd->domain.ops.resume_noirq = pm_genpd_resume_noirq; + genpd->domain.ops.resume = pm_genpd_resume; + genpd->domain.ops.freeze = pm_genpd_freeze; + genpd->domain.ops.freeze_noirq = pm_genpd_freeze_noirq; + genpd->domain.ops.thaw_noirq = pm_genpd_thaw_noirq; + genpd->domain.ops.thaw = pm_genpd_thaw; + genpd->domain.ops.poweroff = pm_genpd_dev_poweroff; + genpd->domain.ops.poweroff_noirq = pm_genpd_dev_poweroff_noirq; + genpd->domain.ops.restore_noirq = pm_genpd_restore_noirq; + genpd->domain.ops.restore = pm_genpd_restore; + genpd->domain.ops.complete = pm_genpd_complete; + mutex_lock(&gpd_list_lock); + list_add(&genpd->gpd_list_node, &gpd_list); + mutex_unlock(&gpd_list_lock); +} diff --git a/drivers/base/power/qos.c b/drivers/base/power/qos.c new file mode 100644 index 0000000..86de6c5 --- /dev/null +++ b/drivers/base/power/qos.c @@ -0,0 +1,414 @@ +/* + * Devices PM QoS constraints management + * + * Copyright (C) 2011 Texas Instruments, Inc. + * + * 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. + * + * + * This module exposes the interface to kernel space for specifying + * per-device PM QoS dependencies. It provides infrastructure for registration + * of: + * + * Dependents on a QoS value : register requests + * Watchers of QoS value : get notified when target QoS value changes + * + * This QoS design is best effort based. Dependents register their QoS needs. + * Watchers register to keep track of the current QoS needs of the system. + * Watchers can register different types of notification callbacks: + * . a per-device notification callback using the dev_pm_qos_*_notifier API. + * The notification chain data is stored in the per-device constraint + * data struct. + * . a system-wide notification callback using the dev_pm_qos_*_global_notifier + * API. The notification chain data is stored in a static variable. + * + * Note about the per-device constraint data struct allocation: + * . The per-device constraints data struct ptr is tored into the device + * dev_pm_info. + * . To minimize the data usage by the per-device constraints, the data struct + * is only allocated at the first call to dev_pm_qos_add_request. + * . The data is later free'd when the device is removed from the system. + * . A global mutex protects the constraints users from the data being + * allocated and free'd. + */ + +#include <linux/pm_qos.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/mutex.h> +#include <linux/export.h> + + +static DEFINE_MUTEX(dev_pm_qos_mtx); + +static BLOCKING_NOTIFIER_HEAD(dev_pm_notifiers); + +/** + * dev_pm_qos_read_value - Get PM QoS constraint for a given device. + * @dev: Device to get the PM QoS constraint value for. + */ +s32 dev_pm_qos_read_value(struct device *dev) +{ + struct pm_qos_constraints *c; + unsigned long flags; + s32 ret = 0; + + spin_lock_irqsave(&dev->power.lock, flags); + + c = dev->power.constraints; + if (c) + ret = pm_qos_read_value(c); + + spin_unlock_irqrestore(&dev->power.lock, flags); + + return ret; +} + +/* + * apply_constraint + * @req: constraint request to apply + * @action: action to perform add/update/remove, of type enum pm_qos_req_action + * @value: defines the qos request + * + * Internal function to update the constraints list using the PM QoS core + * code and if needed call the per-device and the global notification + * callbacks + */ +static int apply_constraint(struct dev_pm_qos_request *req, + enum pm_qos_req_action action, int value) +{ + int ret, curr_value; + + ret = pm_qos_update_target(req->dev->power.constraints, + &req->node, action, value); + + if (ret) { + /* Call the global callbacks if needed */ + curr_value = pm_qos_read_value(req->dev->power.constraints); + blocking_notifier_call_chain(&dev_pm_notifiers, + (unsigned long)curr_value, + req); + } + + return ret; +} + +/* + * dev_pm_qos_constraints_allocate + * @dev: device to allocate data for + * + * Called at the first call to add_request, for constraint data allocation + * Must be called with the dev_pm_qos_mtx mutex held + */ +static int dev_pm_qos_constraints_allocate(struct device *dev) +{ + struct pm_qos_constraints *c; + struct blocking_notifier_head *n; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + + n = kzalloc(sizeof(*n), GFP_KERNEL); + if (!n) { + kfree(c); + return -ENOMEM; + } + BLOCKING_INIT_NOTIFIER_HEAD(n); + + plist_head_init(&c->list); + c->target_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; + c->default_value = PM_QOS_DEV_LAT_DEFAULT_VALUE; + c->type = PM_QOS_MIN; + c->notifiers = n; + + spin_lock_irq(&dev->power.lock); + dev->power.constraints = c; + spin_unlock_irq(&dev->power.lock); + + return 0; +} + +/** + * dev_pm_qos_constraints_init - Initalize device's PM QoS constraints pointer. + * @dev: target device + * + * Called from the device PM subsystem during device insertion under + * device_pm_lock(). + */ +void dev_pm_qos_constraints_init(struct device *dev) +{ + mutex_lock(&dev_pm_qos_mtx); + dev->power.constraints = NULL; + dev->power.power_state = PMSG_ON; + mutex_unlock(&dev_pm_qos_mtx); +} + +/** + * dev_pm_qos_constraints_destroy + * @dev: target device + * + * Called from the device PM subsystem on device removal under device_pm_lock(). + */ +void dev_pm_qos_constraints_destroy(struct device *dev) +{ + struct dev_pm_qos_request *req, *tmp; + struct pm_qos_constraints *c; + + mutex_lock(&dev_pm_qos_mtx); + + dev->power.power_state = PMSG_INVALID; + c = dev->power.constraints; + if (!c) + goto out; + + /* Flush the constraints list for the device */ + plist_for_each_entry_safe(req, tmp, &c->list, node) { + /* + * Update constraints list and call the notification + * callbacks if needed + */ + apply_constraint(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE); + memset(req, 0, sizeof(*req)); + } + + spin_lock_irq(&dev->power.lock); + dev->power.constraints = NULL; + spin_unlock_irq(&dev->power.lock); + + kfree(c->notifiers); + kfree(c); + + out: + mutex_unlock(&dev_pm_qos_mtx); +} + +/** + * dev_pm_qos_add_request - inserts new qos request into the list + * @dev: target device for the constraint + * @req: pointer to a preallocated handle + * @value: defines the qos request + * + * This function inserts a new entry in the device constraints list of + * requested qos performance characteristics. It recomputes the aggregate + * QoS expectations of parameters and initializes the dev_pm_qos_request + * handle. Caller needs to save this handle for later use in updates and + * removal. + * + * Returns 1 if the aggregated constraint value has changed, + * 0 if the aggregated constraint value has not changed, + * -EINVAL in case of wrong parameters, -ENOMEM if there's not enough memory + * to allocate for data structures, -ENODEV if the device has just been removed + * from the system. + */ +int dev_pm_qos_add_request(struct device *dev, struct dev_pm_qos_request *req, + s32 value) +{ + int ret = 0; + + if (!dev || !req) /*guard against callers passing in null */ + return -EINVAL; + + if (WARN(dev_pm_qos_request_active(req), + "%s() called for already added request\n", __func__)) + return -EINVAL; + + req->dev = dev; + + mutex_lock(&dev_pm_qos_mtx); + + if (!dev->power.constraints) { + if (dev->power.power_state.event == PM_EVENT_INVALID) { + /* The device has been removed from the system. */ + req->dev = NULL; + ret = -ENODEV; + goto out; + } else { + /* + * Allocate the constraints data on the first call to + * add_request, i.e. only if the data is not already + * allocated and if the device has not been removed. + */ + ret = dev_pm_qos_constraints_allocate(dev); + } + } + + if (!ret) + ret = apply_constraint(req, PM_QOS_ADD_REQ, value); + + out: + mutex_unlock(&dev_pm_qos_mtx); + + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_add_request); + +/** + * dev_pm_qos_update_request - modifies an existing qos request + * @req : handle to list element holding a dev_pm_qos request to use + * @new_value: defines the qos request + * + * Updates an existing dev PM qos request along with updating the + * target value. + * + * Attempts are made to make this code callable on hot code paths. + * + * Returns 1 if the aggregated constraint value has changed, + * 0 if the aggregated constraint value has not changed, + * -EINVAL in case of wrong parameters, -ENODEV if the device has been + * removed from the system + */ +int dev_pm_qos_update_request(struct dev_pm_qos_request *req, + s32 new_value) +{ + int ret = 0; + + if (!req) /*guard against callers passing in null */ + return -EINVAL; + + if (WARN(!dev_pm_qos_request_active(req), + "%s() called for unknown object\n", __func__)) + return -EINVAL; + + mutex_lock(&dev_pm_qos_mtx); + + if (req->dev->power.constraints) { + if (new_value != req->node.prio) + ret = apply_constraint(req, PM_QOS_UPDATE_REQ, + new_value); + } else { + /* Return if the device has been removed */ + ret = -ENODEV; + } + + mutex_unlock(&dev_pm_qos_mtx); + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_update_request); + +/** + * dev_pm_qos_remove_request - modifies an existing qos request + * @req: handle to request list element + * + * Will remove pm qos request from the list of constraints and + * recompute the current target value. Call this on slow code paths. + * + * Returns 1 if the aggregated constraint value has changed, + * 0 if the aggregated constraint value has not changed, + * -EINVAL in case of wrong parameters, -ENODEV if the device has been + * removed from the system + */ +int dev_pm_qos_remove_request(struct dev_pm_qos_request *req) +{ + int ret = 0; + + if (!req) /*guard against callers passing in null */ + return -EINVAL; + + if (WARN(!dev_pm_qos_request_active(req), + "%s() called for unknown object\n", __func__)) + return -EINVAL; + + mutex_lock(&dev_pm_qos_mtx); + + if (req->dev->power.constraints) { + ret = apply_constraint(req, PM_QOS_REMOVE_REQ, + PM_QOS_DEFAULT_VALUE); + memset(req, 0, sizeof(*req)); + } else { + /* Return if the device has been removed */ + ret = -ENODEV; + } + + mutex_unlock(&dev_pm_qos_mtx); + return ret; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_remove_request); + +/** + * dev_pm_qos_add_notifier - sets notification entry for changes to target value + * of per-device PM QoS constraints + * + * @dev: target device for the constraint + * @notifier: notifier block managed by caller. + * + * Will register the notifier into a notification chain that gets called + * upon changes to the target value for the device. + */ +int dev_pm_qos_add_notifier(struct device *dev, struct notifier_block *notifier) +{ + int retval = 0; + + mutex_lock(&dev_pm_qos_mtx); + + /* Silently return if the constraints object is not present. */ + if (dev->power.constraints) + retval = blocking_notifier_chain_register( + dev->power.constraints->notifiers, + notifier); + + mutex_unlock(&dev_pm_qos_mtx); + return retval; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_add_notifier); + +/** + * dev_pm_qos_remove_notifier - deletes notification for changes to target value + * of per-device PM QoS constraints + * + * @dev: target device for the constraint + * @notifier: notifier block to be removed. + * + * Will remove the notifier from the notification chain that gets called + * upon changes to the target value. + */ +int dev_pm_qos_remove_notifier(struct device *dev, + struct notifier_block *notifier) +{ + int retval = 0; + + mutex_lock(&dev_pm_qos_mtx); + + /* Silently return if the constraints object is not present. */ + if (dev->power.constraints) + retval = blocking_notifier_chain_unregister( + dev->power.constraints->notifiers, + notifier); + + mutex_unlock(&dev_pm_qos_mtx); + return retval; +} +EXPORT_SYMBOL_GPL(dev_pm_qos_remove_notifier); + +/** + * dev_pm_qos_add_global_notifier - sets notification entry for changes to + * target value of the PM QoS constraints for any device + * + * @notifier: notifier block managed by caller. + * + * Will register the notifier into a notification chain that gets called + * upon changes to the target value for any device. + */ +int dev_pm_qos_add_global_notifier(struct notifier_block *notifier) +{ + return blocking_notifier_chain_register(&dev_pm_notifiers, notifier); +} +EXPORT_SYMBOL_GPL(dev_pm_qos_add_global_notifier); + +/** + * dev_pm_qos_remove_global_notifier - deletes notification for changes to + * target value of PM QoS constraints for any device + * + * @notifier: notifier block to be removed. + * + * Will remove the notifier from the notification chain that gets called + * upon changes to the target value for any device. + */ +int dev_pm_qos_remove_global_notifier(struct notifier_block *notifier) +{ + return blocking_notifier_chain_unregister(&dev_pm_notifiers, notifier); +} +EXPORT_SYMBOL_GPL(dev_pm_qos_remove_global_notifier); diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig new file mode 100644 index 0000000..2fc6a66 --- /dev/null +++ b/drivers/base/regmap/Kconfig @@ -0,0 +1,15 @@ +# Generic register map support. There are no user servicable options here, +# this is an API intended to be used by other kernel subsystems. These +# subsystems should select the appropriate symbols. + +config REGMAP + default y if (REGMAP_I2C || REGMAP_SPI) + select LZO_COMPRESS + select LZO_DECOMPRESS + bool + +config REGMAP_I2C + tristate + +config REGMAP_SPI + tristate diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile new file mode 100644 index 0000000..0573c8a --- /dev/null +++ b/drivers/base/regmap/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_REGMAP) += regmap.o regcache.o regcache-indexed.o regcache-rbtree.o regcache-lzo.o +obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o +obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o +obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h new file mode 100644 index 0000000..348ff02 --- /dev/null +++ b/drivers/base/regmap/internal.h @@ -0,0 +1,128 @@ +/* + * Register map access API internal header + * + * Copyright 2011 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 version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _REGMAP_INTERNAL_H +#define _REGMAP_INTERNAL_H + +#include <linux/regmap.h> +#include <linux/fs.h> + +struct regmap; +struct regcache_ops; + +struct regmap_format { + size_t buf_size; + size_t reg_bytes; + size_t val_bytes; + void (*format_write)(struct regmap *map, + unsigned int reg, unsigned int val); + void (*format_reg)(void *buf, unsigned int reg); + void (*format_val)(void *buf, unsigned int val); + unsigned int (*parse_val)(void *buf); +}; + +struct regmap { + struct mutex lock; + + struct device *dev; /* Device we do I/O on */ + void *work_buf; /* Scratch buffer used to format I/O */ + struct regmap_format format; /* Buffer format */ + const struct regmap_bus *bus; + +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif + + unsigned int max_register; + bool (*writeable_reg)(struct device *dev, unsigned int reg); + bool (*readable_reg)(struct device *dev, unsigned int reg); + bool (*volatile_reg)(struct device *dev, unsigned int reg); + bool (*precious_reg)(struct device *dev, unsigned int reg); + + u8 read_flag_mask; + u8 write_flag_mask; + + /* regcache specific members */ + const struct regcache_ops *cache_ops; + enum regcache_type cache_type; + + /* number of bytes in reg_defaults_raw */ + unsigned int cache_size_raw; + /* number of bytes per word in reg_defaults_raw */ + unsigned int cache_word_size; + /* number of entries in reg_defaults */ + unsigned int num_reg_defaults; + /* number of entries in reg_defaults_raw */ + unsigned int num_reg_defaults_raw; + + /* if set, only the cache is modified not the HW */ + unsigned int cache_only:1; + /* if set, only the HW is modified not the cache */ + unsigned int cache_bypass:1; + /* if set, remember to free reg_defaults_raw */ + unsigned int cache_free:1; + + struct reg_default *reg_defaults; + const void *reg_defaults_raw; + void *cache; +}; + +struct regcache_ops { + const char *name; + enum regcache_type type; + int (*init)(struct regmap *map); + int (*exit)(struct regmap *map); + int (*read)(struct regmap *map, unsigned int reg, unsigned int *value); + int (*write)(struct regmap *map, unsigned int reg, unsigned int value); + int (*sync)(struct regmap *map); +}; + +bool regmap_writeable(struct regmap *map, unsigned int reg); +bool regmap_readable(struct regmap *map, unsigned int reg); +bool regmap_volatile(struct regmap *map, unsigned int reg); +bool regmap_precious(struct regmap *map, unsigned int reg); + +int _regmap_write(struct regmap *map, unsigned int reg, + unsigned int val); + +#ifdef CONFIG_DEBUG_FS +extern void regmap_debugfs_initcall(void); +extern void regmap_debugfs_init(struct regmap *map); +extern void regmap_debugfs_exit(struct regmap *map); +#else +static inline void regmap_debugfs_initcall(void) { } +static inline void regmap_debugfs_init(struct regmap *map) { } +static inline void regmap_debugfs_exit(struct regmap *map) { } +#endif + +/* regcache core declarations */ +int regcache_init(struct regmap *map); +void regcache_exit(struct regmap *map); +int regcache_read(struct regmap *map, + unsigned int reg, unsigned int *value); +int regcache_write(struct regmap *map, + unsigned int reg, unsigned int value); +int regcache_sync(struct regmap *map); + +unsigned int regcache_get_val(const void *base, unsigned int idx, + unsigned int word_size); +bool regcache_set_val(void *base, unsigned int idx, + unsigned int val, unsigned int word_size); +int regcache_lookup_reg(struct regmap *map, unsigned int reg); +int regcache_insert_reg(struct regmap *map, unsigned int reg, + unsigned int val); + +extern struct regcache_ops regcache_indexed_ops; +extern struct regcache_ops regcache_rbtree_ops; +extern struct regcache_ops regcache_lzo_ops; + +#endif diff --git a/drivers/base/regmap/regcache-indexed.c b/drivers/base/regmap/regcache-indexed.c new file mode 100644 index 0000000..507731a --- /dev/null +++ b/drivers/base/regmap/regcache-indexed.c @@ -0,0 +1,64 @@ +/* + * Register cache access API - indexed caching support + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos <dp@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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/slab.h> + +#include "internal.h" + +static int regcache_indexed_read(struct regmap *map, unsigned int reg, + unsigned int *value) +{ + int ret; + + ret = regcache_lookup_reg(map, reg); + if (ret >= 0) + *value = map->reg_defaults[ret].def; + + return ret; +} + +static int regcache_indexed_write(struct regmap *map, unsigned int reg, + unsigned int value) +{ + int ret; + + ret = regcache_lookup_reg(map, reg); + if (ret < 0) + return regcache_insert_reg(map, reg, value); + map->reg_defaults[ret].def = value; + return 0; +} + +static int regcache_indexed_sync(struct regmap *map) +{ + unsigned int i; + int ret; + + for (i = 0; i < map->num_reg_defaults; i++) { + ret = _regmap_write(map, map->reg_defaults[i].reg, + map->reg_defaults[i].def); + if (ret < 0) + return ret; + dev_dbg(map->dev, "Synced register %#x, value %#x\n", + map->reg_defaults[i].reg, + map->reg_defaults[i].def); + } + return 0; +} + +struct regcache_ops regcache_indexed_ops = { + .type = REGCACHE_INDEXED, + .name = "indexed", + .read = regcache_indexed_read, + .write = regcache_indexed_write, + .sync = regcache_indexed_sync +}; diff --git a/drivers/base/regmap/regcache-lzo.c b/drivers/base/regmap/regcache-lzo.c new file mode 100644 index 0000000..066aeec --- /dev/null +++ b/drivers/base/regmap/regcache-lzo.c @@ -0,0 +1,361 @@ +/* + * Register cache access API - LZO caching support + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos <dp@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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/slab.h> +#include <linux/lzo.h> + +#include "internal.h" + +struct regcache_lzo_ctx { + void *wmem; + void *dst; + const void *src; + size_t src_len; + size_t dst_len; + size_t decompressed_size; + unsigned long *sync_bmp; + int sync_bmp_nbits; +}; + +#define LZO_BLOCK_NUM 8 +static int regcache_lzo_block_count(void) +{ + return LZO_BLOCK_NUM; +} + +static int regcache_lzo_prepare(struct regcache_lzo_ctx *lzo_ctx) +{ + lzo_ctx->wmem = kmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL); + if (!lzo_ctx->wmem) + return -ENOMEM; + return 0; +} + +static int regcache_lzo_compress(struct regcache_lzo_ctx *lzo_ctx) +{ + size_t compress_size; + int ret; + + ret = lzo1x_1_compress(lzo_ctx->src, lzo_ctx->src_len, + lzo_ctx->dst, &compress_size, lzo_ctx->wmem); + if (ret != LZO_E_OK || compress_size > lzo_ctx->dst_len) + return -EINVAL; + lzo_ctx->dst_len = compress_size; + return 0; +} + +static int regcache_lzo_decompress(struct regcache_lzo_ctx *lzo_ctx) +{ + size_t dst_len; + int ret; + + dst_len = lzo_ctx->dst_len; + ret = lzo1x_decompress_safe(lzo_ctx->src, lzo_ctx->src_len, + lzo_ctx->dst, &dst_len); + if (ret != LZO_E_OK || dst_len != lzo_ctx->dst_len) + return -EINVAL; + return 0; +} + +static int regcache_lzo_compress_cache_block(struct regmap *map, + struct regcache_lzo_ctx *lzo_ctx) +{ + int ret; + + lzo_ctx->dst_len = lzo1x_worst_compress(PAGE_SIZE); + lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL); + if (!lzo_ctx->dst) { + lzo_ctx->dst_len = 0; + return -ENOMEM; + } + + ret = regcache_lzo_compress(lzo_ctx); + if (ret < 0) + return ret; + return 0; +} + +static int regcache_lzo_decompress_cache_block(struct regmap *map, + struct regcache_lzo_ctx *lzo_ctx) +{ + int ret; + + lzo_ctx->dst_len = lzo_ctx->decompressed_size; + lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL); + if (!lzo_ctx->dst) { + lzo_ctx->dst_len = 0; + return -ENOMEM; + } + + ret = regcache_lzo_decompress(lzo_ctx); + if (ret < 0) + return ret; + return 0; +} + +static inline int regcache_lzo_get_blkindex(struct regmap *map, + unsigned int reg) +{ + return (reg * map->cache_word_size) / + DIV_ROUND_UP(map->cache_size_raw, regcache_lzo_block_count()); +} + +static inline int regcache_lzo_get_blkpos(struct regmap *map, + unsigned int reg) +{ + return reg % (DIV_ROUND_UP(map->cache_size_raw, regcache_lzo_block_count()) / + map->cache_word_size); +} + +static inline int regcache_lzo_get_blksize(struct regmap *map) +{ + return DIV_ROUND_UP(map->cache_size_raw, regcache_lzo_block_count()); +} + +static int regcache_lzo_init(struct regmap *map) +{ + struct regcache_lzo_ctx **lzo_blocks; + size_t bmp_size; + int ret, i, blksize, blkcount; + const char *p, *end; + unsigned long *sync_bmp; + + ret = 0; + + blkcount = regcache_lzo_block_count(); + map->cache = kzalloc(blkcount * sizeof *lzo_blocks, + GFP_KERNEL); + if (!map->cache) + return -ENOMEM; + lzo_blocks = map->cache; + + /* + * allocate a bitmap to be used when syncing the cache with + * the hardware. Each time a register is modified, the corresponding + * bit is set in the bitmap, so we know that we have to sync + * that register. + */ + bmp_size = map->num_reg_defaults_raw; + sync_bmp = kmalloc(BITS_TO_LONGS(bmp_size) * sizeof(long), + GFP_KERNEL); + if (!sync_bmp) { + ret = -ENOMEM; + goto err; + } + bitmap_zero(sync_bmp, bmp_size); + + /* allocate the lzo blocks and initialize them */ + for (i = 0; i < blkcount; i++) { + lzo_blocks[i] = kzalloc(sizeof **lzo_blocks, + GFP_KERNEL); + if (!lzo_blocks[i]) { + kfree(sync_bmp); + ret = -ENOMEM; + goto err; + } + lzo_blocks[i]->sync_bmp = sync_bmp; + lzo_blocks[i]->sync_bmp_nbits = bmp_size; + /* alloc the working space for the compressed block */ + ret = regcache_lzo_prepare(lzo_blocks[i]); + if (ret < 0) + goto err; + } + + blksize = regcache_lzo_get_blksize(map); + p = map->reg_defaults_raw; + end = map->reg_defaults_raw + map->cache_size_raw; + /* compress the register map and fill the lzo blocks */ + for (i = 0; i < blkcount; i++, p += blksize) { + lzo_blocks[i]->src = p; + if (p + blksize > end) + lzo_blocks[i]->src_len = end - p; + else + lzo_blocks[i]->src_len = blksize; + ret = regcache_lzo_compress_cache_block(map, + lzo_blocks[i]); + if (ret < 0) + goto err; + lzo_blocks[i]->decompressed_size = + lzo_blocks[i]->src_len; + } + + return 0; +err: + regcache_exit(map); + return ret; +} + +static int regcache_lzo_exit(struct regmap *map) +{ + struct regcache_lzo_ctx **lzo_blocks; + int i, blkcount; + + lzo_blocks = map->cache; + if (!lzo_blocks) + return 0; + + blkcount = regcache_lzo_block_count(); + /* + * the pointer to the bitmap used for syncing the cache + * is shared amongst all lzo_blocks. Ensure it is freed + * only once. + */ + if (lzo_blocks[0]) + kfree(lzo_blocks[0]->sync_bmp); + for (i = 0; i < blkcount; i++) { + if (lzo_blocks[i]) { + kfree(lzo_blocks[i]->wmem); + kfree(lzo_blocks[i]->dst); + } + /* each lzo_block is a pointer returned by kmalloc or NULL */ + kfree(lzo_blocks[i]); + } + kfree(lzo_blocks); + map->cache = NULL; + return 0; +} + +static int regcache_lzo_read(struct regmap *map, + unsigned int reg, unsigned int *value) +{ + struct regcache_lzo_ctx *lzo_block, **lzo_blocks; + int ret, blkindex, blkpos; + size_t blksize, tmp_dst_len; + void *tmp_dst; + + /* index of the compressed lzo block */ + blkindex = regcache_lzo_get_blkindex(map, reg); + /* register index within the decompressed block */ + blkpos = regcache_lzo_get_blkpos(map, reg); + /* size of the compressed block */ + blksize = regcache_lzo_get_blksize(map); + lzo_blocks = map->cache; + lzo_block = lzo_blocks[blkindex]; + + /* save the pointer and length of the compressed block */ + tmp_dst = lzo_block->dst; + tmp_dst_len = lzo_block->dst_len; + + /* prepare the source to be the compressed block */ + lzo_block->src = lzo_block->dst; + lzo_block->src_len = lzo_block->dst_len; + + /* decompress the block */ + ret = regcache_lzo_decompress_cache_block(map, lzo_block); + if (ret >= 0) + /* fetch the value from the cache */ + *value = regcache_get_val(lzo_block->dst, blkpos, + map->cache_word_size); + + kfree(lzo_block->dst); + /* restore the pointer and length of the compressed block */ + lzo_block->dst = tmp_dst; + lzo_block->dst_len = tmp_dst_len; + + return ret; +} + +static int regcache_lzo_write(struct regmap *map, + unsigned int reg, unsigned int value) +{ + struct regcache_lzo_ctx *lzo_block, **lzo_blocks; + int ret, blkindex, blkpos; + size_t blksize, tmp_dst_len; + void *tmp_dst; + + /* index of the compressed lzo block */ + blkindex = regcache_lzo_get_blkindex(map, reg); + /* register index within the decompressed block */ + blkpos = regcache_lzo_get_blkpos(map, reg); + /* size of the compressed block */ + blksize = regcache_lzo_get_blksize(map); + lzo_blocks = map->cache; + lzo_block = lzo_blocks[blkindex]; + + /* save the pointer and length of the compressed block */ + tmp_dst = lzo_block->dst; + tmp_dst_len = lzo_block->dst_len; + + /* prepare the source to be the compressed block */ + lzo_block->src = lzo_block->dst; + lzo_block->src_len = lzo_block->dst_len; + + /* decompress the block */ + ret = regcache_lzo_decompress_cache_block(map, lzo_block); + if (ret < 0) { + kfree(lzo_block->dst); + goto out; + } + + /* write the new value to the cache */ + if (regcache_set_val(lzo_block->dst, blkpos, value, + map->cache_word_size)) { + kfree(lzo_block->dst); + goto out; + } + + /* prepare the source to be the decompressed block */ + lzo_block->src = lzo_block->dst; + lzo_block->src_len = lzo_block->dst_len; + + /* compress the block */ + ret = regcache_lzo_compress_cache_block(map, lzo_block); + if (ret < 0) { + kfree(lzo_block->dst); + kfree(lzo_block->src); + goto out; + } + + /* set the bit so we know we have to sync this register */ + set_bit(reg, lzo_block->sync_bmp); + kfree(tmp_dst); + kfree(lzo_block->src); + return 0; +out: + lzo_block->dst = tmp_dst; + lzo_block->dst_len = tmp_dst_len; + return ret; +} + +static int regcache_lzo_sync(struct regmap *map) +{ + struct regcache_lzo_ctx **lzo_blocks; + unsigned int val; + int i; + int ret; + + lzo_blocks = map->cache; + for_each_set_bit(i, lzo_blocks[0]->sync_bmp, lzo_blocks[0]->sync_bmp_nbits) { + ret = regcache_read(map, i, &val); + if (ret) + return ret; + map->cache_bypass = 1; + ret = _regmap_write(map, i, val); + map->cache_bypass = 0; + if (ret) + return ret; + dev_dbg(map->dev, "Synced register %#x, value %#x\n", + i, val); + } + + return 0; +} + +struct regcache_ops regcache_lzo_ops = { + .type = REGCACHE_LZO, + .name = "lzo", + .init = regcache_lzo_init, + .exit = regcache_lzo_exit, + .read = regcache_lzo_read, + .write = regcache_lzo_write, + .sync = regcache_lzo_sync +}; diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c new file mode 100644 index 0000000..e314984 --- /dev/null +++ b/drivers/base/regmap/regcache-rbtree.c @@ -0,0 +1,345 @@ +/* + * Register cache access API - rbtree caching support + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos <dp@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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/slab.h> +#include <linux/rbtree.h> + +#include "internal.h" + +static int regcache_rbtree_write(struct regmap *map, unsigned int reg, + unsigned int value); + +struct regcache_rbtree_node { + /* the actual rbtree node holding this block */ + struct rb_node node; + /* base register handled by this block */ + unsigned int base_reg; + /* block of adjacent registers */ + void *block; + /* number of registers available in the block */ + unsigned int blklen; +} __attribute__ ((packed)); + +struct regcache_rbtree_ctx { + struct rb_root root; + struct regcache_rbtree_node *cached_rbnode; +}; + +static inline void regcache_rbtree_get_base_top_reg( + struct regcache_rbtree_node *rbnode, + unsigned int *base, unsigned int *top) +{ + *base = rbnode->base_reg; + *top = rbnode->base_reg + rbnode->blklen - 1; +} + +static unsigned int regcache_rbtree_get_register( + struct regcache_rbtree_node *rbnode, unsigned int idx, + unsigned int word_size) +{ + return regcache_get_val(rbnode->block, idx, word_size); +} + +static void regcache_rbtree_set_register(struct regcache_rbtree_node *rbnode, + unsigned int idx, unsigned int val, + unsigned int word_size) +{ + regcache_set_val(rbnode->block, idx, val, word_size); +} + +static struct regcache_rbtree_node *regcache_rbtree_lookup(struct regmap *map, + unsigned int reg) +{ + struct regcache_rbtree_ctx *rbtree_ctx = map->cache; + struct rb_node *node; + struct regcache_rbtree_node *rbnode; + unsigned int base_reg, top_reg; + + rbnode = rbtree_ctx->cached_rbnode; + if (rbnode) { + regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg); + if (reg >= base_reg && reg <= top_reg) + return rbnode; + } + + node = rbtree_ctx->root.rb_node; + while (node) { + rbnode = container_of(node, struct regcache_rbtree_node, node); + regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg); + if (reg >= base_reg && reg <= top_reg) { + rbtree_ctx->cached_rbnode = rbnode; + return rbnode; + } else if (reg > top_reg) { + node = node->rb_right; + } else if (reg < base_reg) { + node = node->rb_left; + } + } + + return NULL; +} + +static int regcache_rbtree_insert(struct rb_root *root, + struct regcache_rbtree_node *rbnode) +{ + struct rb_node **new, *parent; + struct regcache_rbtree_node *rbnode_tmp; + unsigned int base_reg_tmp, top_reg_tmp; + unsigned int base_reg; + + parent = NULL; + new = &root->rb_node; + while (*new) { + rbnode_tmp = container_of(*new, struct regcache_rbtree_node, + node); + /* base and top registers of the current rbnode */ + regcache_rbtree_get_base_top_reg(rbnode_tmp, &base_reg_tmp, + &top_reg_tmp); + /* base register of the rbnode to be added */ + base_reg = rbnode->base_reg; + parent = *new; + /* if this register has already been inserted, just return */ + if (base_reg >= base_reg_tmp && + base_reg <= top_reg_tmp) + return 0; + else if (base_reg > top_reg_tmp) + new = &((*new)->rb_right); + else if (base_reg < base_reg_tmp) + new = &((*new)->rb_left); + } + + /* insert the node into the rbtree */ + rb_link_node(&rbnode->node, parent, new); + rb_insert_color(&rbnode->node, root); + + return 1; +} + +static int regcache_rbtree_init(struct regmap *map) +{ + struct regcache_rbtree_ctx *rbtree_ctx; + int i; + int ret; + + map->cache = kmalloc(sizeof *rbtree_ctx, GFP_KERNEL); + if (!map->cache) + return -ENOMEM; + + rbtree_ctx = map->cache; + rbtree_ctx->root = RB_ROOT; + rbtree_ctx->cached_rbnode = NULL; + + for (i = 0; i < map->num_reg_defaults; i++) { + ret = regcache_rbtree_write(map, + map->reg_defaults[i].reg, + map->reg_defaults[i].def); + if (ret) + goto err; + } + + return 0; + +err: + regcache_exit(map); + return ret; +} + +static int regcache_rbtree_exit(struct regmap *map) +{ + struct rb_node *next; + struct regcache_rbtree_ctx *rbtree_ctx; + struct regcache_rbtree_node *rbtree_node; + + /* if we've already been called then just return */ + rbtree_ctx = map->cache; + if (!rbtree_ctx) + return 0; + + /* free up the rbtree */ + next = rb_first(&rbtree_ctx->root); + while (next) { + rbtree_node = rb_entry(next, struct regcache_rbtree_node, node); + next = rb_next(&rbtree_node->node); + rb_erase(&rbtree_node->node, &rbtree_ctx->root); + kfree(rbtree_node->block); + kfree(rbtree_node); + } + + /* release the resources */ + kfree(map->cache); + map->cache = NULL; + + return 0; +} + +static int regcache_rbtree_read(struct regmap *map, + unsigned int reg, unsigned int *value) +{ + struct regcache_rbtree_node *rbnode; + unsigned int reg_tmp; + + rbnode = regcache_rbtree_lookup(map, reg); + if (rbnode) { + reg_tmp = reg - rbnode->base_reg; + *value = regcache_rbtree_get_register(rbnode, reg_tmp, + map->cache_word_size); + } else { + return -ENOENT; + } + + return 0; +} + + +static int regcache_rbtree_insert_to_block(struct regcache_rbtree_node *rbnode, + unsigned int pos, unsigned int reg, + unsigned int value, unsigned int word_size) +{ + u8 *blk; + + blk = krealloc(rbnode->block, + (rbnode->blklen + 1) * word_size, GFP_KERNEL); + if (!blk) + return -ENOMEM; + + /* insert the register value in the correct place in the rbnode block */ + memmove(blk + (pos + 1) * word_size, + blk + pos * word_size, + (rbnode->blklen - pos) * word_size); + + /* update the rbnode block, its size and the base register */ + rbnode->block = blk; + rbnode->blklen++; + if (!pos) + rbnode->base_reg = reg; + + regcache_rbtree_set_register(rbnode, pos, value, word_size); + return 0; +} + +static int regcache_rbtree_write(struct regmap *map, unsigned int reg, + unsigned int value) +{ + struct regcache_rbtree_ctx *rbtree_ctx; + struct regcache_rbtree_node *rbnode, *rbnode_tmp; + struct rb_node *node; + unsigned int val; + unsigned int reg_tmp; + unsigned int pos; + int i; + int ret; + + rbtree_ctx = map->cache; + /* if we can't locate it in the cached rbnode we'll have + * to traverse the rbtree looking for it. + */ + rbnode = regcache_rbtree_lookup(map, reg); + if (rbnode) { + reg_tmp = reg - rbnode->base_reg; + val = regcache_rbtree_get_register(rbnode, reg_tmp, + map->cache_word_size); + if (val == value) + return 0; + regcache_rbtree_set_register(rbnode, reg_tmp, value, + map->cache_word_size); + } else { + /* look for an adjacent register to the one we are about to add */ + for (node = rb_first(&rbtree_ctx->root); node; + node = rb_next(node)) { + rbnode_tmp = rb_entry(node, struct regcache_rbtree_node, node); + for (i = 0; i < rbnode_tmp->blklen; i++) { + reg_tmp = rbnode_tmp->base_reg + i; + if (abs(reg_tmp - reg) != 1) + continue; + /* decide where in the block to place our register */ + if (reg_tmp + 1 == reg) + pos = i + 1; + else + pos = i; + ret = regcache_rbtree_insert_to_block(rbnode_tmp, pos, + reg, value, + map->cache_word_size); + if (ret) + return ret; + rbtree_ctx->cached_rbnode = rbnode_tmp; + return 0; + } + } + /* we did not manage to find a place to insert it in an existing + * block so create a new rbnode with a single register in its block. + * This block will get populated further if any other adjacent + * registers get modified in the future. + */ + rbnode = kzalloc(sizeof *rbnode, GFP_KERNEL); + if (!rbnode) + return -ENOMEM; + rbnode->blklen = 1; + rbnode->base_reg = reg; + rbnode->block = kmalloc(rbnode->blklen * map->cache_word_size, + GFP_KERNEL); + if (!rbnode->block) { + kfree(rbnode); + return -ENOMEM; + } + regcache_rbtree_set_register(rbnode, 0, value, map->cache_word_size); + regcache_rbtree_insert(&rbtree_ctx->root, rbnode); + rbtree_ctx->cached_rbnode = rbnode; + } + + return 0; +} + +static int regcache_rbtree_sync(struct regmap *map) +{ + struct regcache_rbtree_ctx *rbtree_ctx; + struct rb_node *node; + struct regcache_rbtree_node *rbnode; + unsigned int regtmp; + unsigned int val; + int ret; + int i; + + rbtree_ctx = map->cache; + for (node = rb_first(&rbtree_ctx->root); node; node = rb_next(node)) { + rbnode = rb_entry(node, struct regcache_rbtree_node, node); + for (i = 0; i < rbnode->blklen; i++) { + regtmp = rbnode->base_reg + i; + val = regcache_rbtree_get_register(rbnode, i, + map->cache_word_size); + + /* Is this the hardware default? If so skip. */ + ret = regcache_lookup_reg(map, i); + if (ret > 0 && val == map->reg_defaults[ret].def) + continue; + + map->cache_bypass = 1; + ret = _regmap_write(map, regtmp, val); + map->cache_bypass = 0; + if (ret) + return ret; + dev_dbg(map->dev, "Synced register %#x, value %#x\n", + regtmp, val); + } + } + + return 0; +} + +struct regcache_ops regcache_rbtree_ops = { + .type = REGCACHE_RBTREE, + .name = "rbtree", + .init = regcache_rbtree_init, + .exit = regcache_rbtree_exit, + .read = regcache_rbtree_read, + .write = regcache_rbtree_write, + .sync = regcache_rbtree_sync +}; diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c new file mode 100644 index 0000000..64004b0 --- /dev/null +++ b/drivers/base/regmap/regcache.c @@ -0,0 +1,402 @@ +/* + * Register cache access API + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos <dp@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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/slab.h> +#include <linux/export.h> +#include <trace/events/regmap.h> +#include <linux/bsearch.h> +#include <linux/sort.h> + +#include "internal.h" + +static const struct regcache_ops *cache_types[] = { + ®cache_indexed_ops, + ®cache_rbtree_ops, + ®cache_lzo_ops, +}; + +static int regcache_hw_init(struct regmap *map) +{ + int i, j; + int ret; + int count; + unsigned int val; + void *tmp_buf; + + if (!map->num_reg_defaults_raw) + return -EINVAL; + + if (!map->reg_defaults_raw) { + dev_warn(map->dev, "No cache defaults, reading back from HW\n"); + tmp_buf = kmalloc(map->cache_size_raw, GFP_KERNEL); + if (!tmp_buf) + return -EINVAL; + ret = regmap_bulk_read(map, 0, tmp_buf, + map->num_reg_defaults_raw); + if (ret < 0) { + kfree(tmp_buf); + return ret; + } + map->reg_defaults_raw = tmp_buf; + map->cache_free = 1; + } + + /* calculate the size of reg_defaults */ + for (count = 0, i = 0; i < map->num_reg_defaults_raw; i++) { + val = regcache_get_val(map->reg_defaults_raw, + i, map->cache_word_size); + if (regmap_volatile(map, i)) + continue; + count++; + } + + map->reg_defaults = kmalloc(count * sizeof(struct reg_default), + GFP_KERNEL); + if (!map->reg_defaults) + return -ENOMEM; + + /* fill the reg_defaults */ + map->num_reg_defaults = count; + for (i = 0, j = 0; i < map->num_reg_defaults_raw; i++) { + val = regcache_get_val(map->reg_defaults_raw, + i, map->cache_word_size); + if (regmap_volatile(map, i)) + continue; + map->reg_defaults[j].reg = i; + map->reg_defaults[j].def = val; + j++; + } + + return 0; +} + +int regcache_init(struct regmap *map) +{ + int ret; + int i; + void *tmp_buf; + + if (map->cache_type == REGCACHE_NONE) { + map->cache_bypass = true; + return 0; + } + + for (i = 0; i < ARRAY_SIZE(cache_types); i++) + if (cache_types[i]->type == map->cache_type) + break; + + if (i == ARRAY_SIZE(cache_types)) { + dev_err(map->dev, "Could not match compress type: %d\n", + map->cache_type); + return -EINVAL; + } + + map->cache = NULL; + map->cache_ops = cache_types[i]; + + if (!map->cache_ops->read || + !map->cache_ops->write || + !map->cache_ops->name) + return -EINVAL; + + /* We still need to ensure that the reg_defaults + * won't vanish from under us. We'll need to make + * a copy of it. + */ + if (map->reg_defaults) { + if (!map->num_reg_defaults) + return -EINVAL; + tmp_buf = kmemdup(map->reg_defaults, map->num_reg_defaults * + sizeof(struct reg_default), GFP_KERNEL); + if (!tmp_buf) + return -ENOMEM; + map->reg_defaults = tmp_buf; + } else if (map->num_reg_defaults_raw) { + /* Some devices such as PMICs don't have cache defaults, + * we cope with this by reading back the HW registers and + * crafting the cache defaults by hand. + */ + ret = regcache_hw_init(map); + if (ret < 0) + return ret; + } + + if (!map->max_register) + map->max_register = map->num_reg_defaults_raw; + + if (map->cache_ops->init) { + dev_dbg(map->dev, "Initializing %s cache\n", + map->cache_ops->name); + return map->cache_ops->init(map); + } + return 0; +} + +void regcache_exit(struct regmap *map) +{ + if (map->cache_type == REGCACHE_NONE) + return; + + BUG_ON(!map->cache_ops); + + kfree(map->reg_defaults); + if (map->cache_free) + kfree(map->reg_defaults_raw); + + if (map->cache_ops->exit) { + dev_dbg(map->dev, "Destroying %s cache\n", + map->cache_ops->name); + map->cache_ops->exit(map); + } +} + +/** + * regcache_read: Fetch the value of a given register from the cache. + * + * @map: map to configure. + * @reg: The register index. + * @value: The value to be returned. + * + * Return a negative value on failure, 0 on success. + */ +int regcache_read(struct regmap *map, + unsigned int reg, unsigned int *value) +{ + if (map->cache_type == REGCACHE_NONE) + return -ENOSYS; + + BUG_ON(!map->cache_ops); + + if (!regmap_readable(map, reg)) + return -EIO; + + if (!regmap_volatile(map, reg)) + return map->cache_ops->read(map, reg, value); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(regcache_read); + +/** + * regcache_write: Set the value of a given register in the cache. + * + * @map: map to configure. + * @reg: The register index. + * @value: The new register value. + * + * Return a negative value on failure, 0 on success. + */ +int regcache_write(struct regmap *map, + unsigned int reg, unsigned int value) +{ + if (map->cache_type == REGCACHE_NONE) + return 0; + + BUG_ON(!map->cache_ops); + + if (!regmap_writeable(map, reg)) + return -EIO; + + if (!regmap_volatile(map, reg)) + return map->cache_ops->write(map, reg, value); + + return 0; +} +EXPORT_SYMBOL_GPL(regcache_write); + +/** + * regcache_sync: Sync the register cache with the hardware. + * + * @map: map to configure. + * + * Any registers that should not be synced should be marked as + * volatile. In general drivers can choose not to use the provided + * syncing functionality if they so require. + * + * Return a negative value on failure, 0 on success. + */ +int regcache_sync(struct regmap *map) +{ + int ret = 0; + unsigned int val; + unsigned int i; + const char *name; + unsigned int bypass; + + BUG_ON(!map->cache_ops); + + mutex_lock(&map->lock); + /* Remember the initial bypass state */ + bypass = map->cache_bypass; + dev_dbg(map->dev, "Syncing %s cache\n", + map->cache_ops->name); + name = map->cache_ops->name; + trace_regcache_sync(map->dev, name, "start"); + if (map->cache_ops->sync) { + ret = map->cache_ops->sync(map); + } else { + for (i = 0; i < map->num_reg_defaults; i++) { + ret = regcache_read(map, i, &val); + if (ret < 0) + goto out; + map->cache_bypass = 1; + ret = _regmap_write(map, i, val); + map->cache_bypass = 0; + if (ret < 0) + goto out; + dev_dbg(map->dev, "Synced register %#x, value %#x\n", + map->reg_defaults[i].reg, + map->reg_defaults[i].def); + } + + } +out: + trace_regcache_sync(map->dev, name, "stop"); + /* Restore the bypass state */ + map->cache_bypass = bypass; + mutex_unlock(&map->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(regcache_sync); + +/** + * regcache_cache_only: Put a register map into cache only mode + * + * @map: map to configure + * @cache_only: flag if changes should be written to the hardware + * + * When a register map is marked as cache only writes to the register + * map API will only update the register cache, they will not cause + * any hardware changes. This is useful for allowing portions of + * drivers to act as though the device were functioning as normal when + * it is disabled for power saving reasons. + */ +void regcache_cache_only(struct regmap *map, bool enable) +{ + mutex_lock(&map->lock); + WARN_ON(map->cache_bypass && enable); + map->cache_only = enable; + mutex_unlock(&map->lock); +} +EXPORT_SYMBOL_GPL(regcache_cache_only); + +/** + * regcache_cache_bypass: Put a register map into cache bypass mode + * + * @map: map to configure + * @cache_bypass: flag if changes should not be written to the hardware + * + * When a register map is marked with the cache bypass option, writes + * to the register map API will only update the hardware and not the + * the cache directly. This is useful when syncing the cache back to + * the hardware. + */ +void regcache_cache_bypass(struct regmap *map, bool enable) +{ + mutex_lock(&map->lock); + WARN_ON(map->cache_only && enable); + map->cache_bypass = enable; + mutex_unlock(&map->lock); +} +EXPORT_SYMBOL_GPL(regcache_cache_bypass); + +bool regcache_set_val(void *base, unsigned int idx, + unsigned int val, unsigned int word_size) +{ + switch (word_size) { + case 1: { + u8 *cache = base; + if (cache[idx] == val) + return true; + cache[idx] = val; + break; + } + case 2: { + u16 *cache = base; + if (cache[idx] == val) + return true; + cache[idx] = val; + break; + } + default: + BUG(); + } + /* unreachable */ + return false; +} + +unsigned int regcache_get_val(const void *base, unsigned int idx, + unsigned int word_size) +{ + if (!base) + return -EINVAL; + + switch (word_size) { + case 1: { + const u8 *cache = base; + return cache[idx]; + } + case 2: { + const u16 *cache = base; + return cache[idx]; + } + default: + BUG(); + } + /* unreachable */ + return -1; +} + +static int regcache_default_cmp(const void *a, const void *b) +{ + const struct reg_default *_a = a; + const struct reg_default *_b = b; + + return _a->reg - _b->reg; +} + +int regcache_lookup_reg(struct regmap *map, unsigned int reg) +{ + struct reg_default key; + struct reg_default *r; + + key.reg = reg; + key.def = 0; + + r = bsearch(&key, map->reg_defaults, map->num_reg_defaults, + sizeof(struct reg_default), regcache_default_cmp); + + if (r) + return r - map->reg_defaults; + else + return -ENOENT; +} + +int regcache_insert_reg(struct regmap *map, unsigned int reg, + unsigned int val) +{ + void *tmp; + + tmp = krealloc(map->reg_defaults, + (map->num_reg_defaults + 1) * sizeof(struct reg_default), + GFP_KERNEL); + if (!tmp) + return -ENOMEM; + map->reg_defaults = tmp; + map->num_reg_defaults++; + map->reg_defaults[map->num_reg_defaults - 1].reg = reg; + map->reg_defaults[map->num_reg_defaults - 1].def = val; + sort(map->reg_defaults, map->num_reg_defaults, + sizeof(struct reg_default), regcache_default_cmp, NULL); + return 0; +} diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c new file mode 100644 index 0000000..cd252e0 --- /dev/null +++ b/drivers/base/regmap/regmap-debugfs.c @@ -0,0 +1,209 @@ +/* + * Register map access API - debugfs + * + * Copyright 2011 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> + +#include "internal.h" + +static struct dentry *regmap_debugfs_root; + +/* Calculate the length of a fixed format */ +static size_t regmap_calc_reg_len(int max_val, char *buf, size_t buf_size) +{ + snprintf(buf, buf_size, "%x", max_val); + return strlen(buf); +} + +static int regmap_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + int reg_len, val_len, tot_len; + size_t buf_pos = 0; + loff_t p = 0; + ssize_t ret; + int i; + struct regmap *map = file->private_data; + char *buf; + unsigned int val; + + if (*ppos < 0 || !count) + return -EINVAL; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* Calculate the length of a fixed format */ + reg_len = regmap_calc_reg_len(map->max_register, buf, count); + val_len = 2 * map->format.val_bytes; + tot_len = reg_len + val_len + 3; /* : \n */ + + for (i = 0; i < map->max_register + 1; i++) { + if (!regmap_readable(map, i)) + continue; + + if (regmap_precious(map, i)) + continue; + + /* If we're in the region the user is trying to read */ + if (p >= *ppos) { + /* ...but not beyond it */ + if (buf_pos + 1 + tot_len >= count) + break; + + /* Format the register */ + snprintf(buf + buf_pos, count - buf_pos, "%.*x: ", + reg_len, i); + buf_pos += reg_len + 2; + + /* Format the value, write all X if we can't read */ + ret = regmap_read(map, i, &val); + if (ret == 0) + snprintf(buf + buf_pos, count - buf_pos, + "%.*x", val_len, val); + else + memset(buf + buf_pos, 'X', val_len); + buf_pos += 2 * map->format.val_bytes; + + buf[buf_pos++] = '\n'; + } + p += tot_len; + } + + ret = buf_pos; + + if (copy_to_user(user_buf, buf, buf_pos)) { + ret = -EFAULT; + goto out; + } + + *ppos += buf_pos; + +out: + kfree(buf); + return ret; +} + +static const struct file_operations regmap_map_fops = { + .open = regmap_open_file, + .read = regmap_map_read_file, + .llseek = default_llseek, +}; + +static ssize_t regmap_access_read_file(struct file *file, + char __user *user_buf, size_t count, + loff_t *ppos) +{ + int reg_len, tot_len; + size_t buf_pos = 0; + loff_t p = 0; + ssize_t ret; + int i; + struct regmap *map = file->private_data; + char *buf; + + if (*ppos < 0 || !count) + return -EINVAL; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* Calculate the length of a fixed format */ + reg_len = regmap_calc_reg_len(map->max_register, buf, count); + tot_len = reg_len + 10; /* ': R W V P\n' */ + + for (i = 0; i < map->max_register + 1; i++) { + /* Ignore registers which are neither readable nor writable */ + if (!regmap_readable(map, i) && !regmap_writeable(map, i)) + continue; + + /* If we're in the region the user is trying to read */ + if (p >= *ppos) { + /* ...but not beyond it */ + if (buf_pos >= count - 1 - tot_len) + break; + + /* Format the register */ + snprintf(buf + buf_pos, count - buf_pos, + "%.*x: %c %c %c %c\n", + reg_len, i, + regmap_readable(map, i) ? 'y' : 'n', + regmap_writeable(map, i) ? 'y' : 'n', + regmap_volatile(map, i) ? 'y' : 'n', + regmap_precious(map, i) ? 'y' : 'n'); + + buf_pos += tot_len; + } + p += tot_len; + } + + ret = buf_pos; + + if (copy_to_user(user_buf, buf, buf_pos)) { + ret = -EFAULT; + goto out; + } + + *ppos += buf_pos; + +out: + kfree(buf); + return ret; +} + +static const struct file_operations regmap_access_fops = { + .open = regmap_open_file, + .read = regmap_access_read_file, + .llseek = default_llseek, +}; + +void regmap_debugfs_init(struct regmap *map) +{ + map->debugfs = debugfs_create_dir(dev_name(map->dev), + regmap_debugfs_root); + if (!map->debugfs) { + dev_warn(map->dev, "Failed to create debugfs directory\n"); + return; + } + + if (map->max_register) { + debugfs_create_file("registers", 0400, map->debugfs, + map, ®map_map_fops); + debugfs_create_file("access", 0400, map->debugfs, + map, ®map_access_fops); + } +} + +void regmap_debugfs_exit(struct regmap *map) +{ + debugfs_remove_recursive(map->debugfs); +} + +void regmap_debugfs_initcall(void) +{ + regmap_debugfs_root = debugfs_create_dir("regmap", NULL); + if (!regmap_debugfs_root) { + pr_warn("regmap: Failed to create debugfs root\n"); + return; + } +} diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c new file mode 100644 index 0000000..38621ec --- /dev/null +++ b/drivers/base/regmap/regmap-i2c.c @@ -0,0 +1,114 @@ +/* + * Register map access API - I2C support + * + * Copyright 2011 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/regmap.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/init.h> + +static int regmap_i2c_write(struct device *dev, const void *data, size_t count) +{ + struct i2c_client *i2c = to_i2c_client(dev); + int ret; + + ret = i2c_master_send(i2c, data, count); + if (ret == count) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +static int regmap_i2c_gather_write(struct device *dev, + const void *reg, size_t reg_size, + const void *val, size_t val_size) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct i2c_msg xfer[2]; + int ret; + + /* If the I2C controller can't do a gather tell the core, it + * will substitute in a linear write for us. + */ + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_PROTOCOL_MANGLING)) + return -ENOTSUPP; + + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = reg_size; + xfer[0].buf = (void *)reg; + + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_NOSTART; + xfer[1].len = val_size; + xfer[1].buf = (void *)val; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret == 2) + return 0; + if (ret < 0) + return ret; + else + return -EIO; +} + +static int regmap_i2c_read(struct device *dev, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct i2c_msg xfer[2]; + int ret; + + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = reg_size; + xfer[0].buf = (void *)reg; + + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = val_size; + xfer[1].buf = val; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret == 2) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +static struct regmap_bus regmap_i2c = { + .write = regmap_i2c_write, + .gather_write = regmap_i2c_gather_write, + .read = regmap_i2c_read, +}; + +/** + * regmap_init_i2c(): Initialise register map + * + * @i2c: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. + */ +struct regmap *regmap_init_i2c(struct i2c_client *i2c, + const struct regmap_config *config) +{ + return regmap_init(&i2c->dev, ®map_i2c, config); +} +EXPORT_SYMBOL_GPL(regmap_init_i2c); + +MODULE_LICENSE("GPL"); diff --git a/drivers/base/regmap/regmap-spi.c b/drivers/base/regmap/regmap-spi.c new file mode 100644 index 0000000..2560658 --- /dev/null +++ b/drivers/base/regmap/regmap-spi.c @@ -0,0 +1,73 @@ +/* + * Register map access API - SPI support + * + * Copyright 2011 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/regmap.h> +#include <linux/spi/spi.h> +#include <linux/init.h> +#include <linux/module.h> + +static int regmap_spi_write(struct device *dev, const void *data, size_t count) +{ + struct spi_device *spi = to_spi_device(dev); + + return spi_write(spi, data, count); +} + +static int regmap_spi_gather_write(struct device *dev, + const void *reg, size_t reg_len, + const void *val, size_t val_len) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_message m; + struct spi_transfer t[2] = { { .tx_buf = reg, .len = reg_len, }, + { .tx_buf = val, .len = val_len, }, }; + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + return spi_sync(spi, &m); +} + +static int regmap_spi_read(struct device *dev, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct spi_device *spi = to_spi_device(dev); + + return spi_write_then_read(spi, reg, reg_size, val, val_size); +} + +static struct regmap_bus regmap_spi = { + .write = regmap_spi_write, + .gather_write = regmap_spi_gather_write, + .read = regmap_spi_read, + .read_flag_mask = 0x80, +}; + +/** + * regmap_init_spi(): Initialise register map + * + * @spi: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. + */ +struct regmap *regmap_init_spi(struct spi_device *spi, + const struct regmap_config *config) +{ + return regmap_init(&spi->dev, ®map_spi, config); +} +EXPORT_SYMBOL_GPL(regmap_init_spi); + +MODULE_LICENSE("GPL"); diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c new file mode 100644 index 0000000..ea78bc4 --- /dev/null +++ b/drivers/base/regmap/regmap.c @@ -0,0 +1,592 @@ +/* + * Register map access API + * + * Copyright 2011 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 version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/err.h> + +#define CREATE_TRACE_POINTS +#include <trace/events/regmap.h> + +#include "internal.h" + +bool regmap_writeable(struct regmap *map, unsigned int reg) +{ + if (map->max_register && reg > map->max_register) + return false; + + if (map->writeable_reg) + return map->writeable_reg(map->dev, reg); + + return true; +} + +bool regmap_readable(struct regmap *map, unsigned int reg) +{ + if (map->max_register && reg > map->max_register) + return false; + + if (map->format.format_write) + return false; + + if (map->readable_reg) + return map->readable_reg(map->dev, reg); + + return true; +} + +bool regmap_volatile(struct regmap *map, unsigned int reg) +{ + if (!map->format.format_write && !regmap_readable(map, reg)) + return false; + + if (map->volatile_reg) + return map->volatile_reg(map->dev, reg); + + return true; +} + +bool regmap_precious(struct regmap *map, unsigned int reg) +{ + if (!regmap_readable(map, reg)) + return false; + + if (map->precious_reg) + return map->precious_reg(map->dev, reg); + + return false; +} + +static void regmap_format_4_12_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + __be16 *out = map->work_buf; + *out = cpu_to_be16((reg << 12) | val); +} + +static void regmap_format_7_9_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + __be16 *out = map->work_buf; + *out = cpu_to_be16((reg << 9) | val); +} + +static void regmap_format_8(void *buf, unsigned int val) +{ + u8 *b = buf; + + b[0] = val; +} + +static void regmap_format_16(void *buf, unsigned int val) +{ + __be16 *b = buf; + + b[0] = cpu_to_be16(val); +} + +static unsigned int regmap_parse_8(void *buf) +{ + u8 *b = buf; + + return b[0]; +} + +static unsigned int regmap_parse_16(void *buf) +{ + __be16 *b = buf; + + b[0] = be16_to_cpu(b[0]); + + return b[0]; +} + +/** + * regmap_init(): Initialise register map + * + * @dev: Device that will be interacted with + * @bus: Bus-specific callbacks to use with device + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. This function should generally not be called + * directly, it should be called by bus-specific init functions. + */ +struct regmap *regmap_init(struct device *dev, + const struct regmap_bus *bus, + const struct regmap_config *config) +{ + struct regmap *map; + int ret = -EINVAL; + + if (!bus || !config) + return NULL; + + map = kzalloc(sizeof(*map), GFP_KERNEL); + if (map == NULL) { + ret = -ENOMEM; + goto err; + } + + mutex_init(&map->lock); + map->format.buf_size = (config->reg_bits + config->val_bits) / 8; + map->format.reg_bytes = config->reg_bits / 8; + map->format.val_bytes = config->val_bits / 8; + map->dev = dev; + map->bus = bus; + map->max_register = config->max_register; + map->writeable_reg = config->writeable_reg; + map->readable_reg = config->readable_reg; + map->volatile_reg = config->volatile_reg; + map->precious_reg = config->precious_reg; + map->cache_type = config->cache_type; + map->reg_defaults = config->reg_defaults; + map->num_reg_defaults = config->num_reg_defaults; + map->num_reg_defaults_raw = config->num_reg_defaults_raw; + map->reg_defaults_raw = config->reg_defaults_raw; + map->cache_size_raw = (config->val_bits / 8) * config->num_reg_defaults_raw; + map->cache_word_size = config->val_bits / 8; + + if (config->read_flag_mask || config->write_flag_mask) { + map->read_flag_mask = config->read_flag_mask; + map->write_flag_mask = config->write_flag_mask; + } else { + map->read_flag_mask = bus->read_flag_mask; + } + + switch (config->reg_bits) { + case 4: + switch (config->val_bits) { + case 12: + map->format.format_write = regmap_format_4_12_write; + break; + default: + goto err_map; + } + break; + + case 7: + switch (config->val_bits) { + case 9: + map->format.format_write = regmap_format_7_9_write; + break; + default: + goto err_map; + } + break; + + case 8: + map->format.format_reg = regmap_format_8; + break; + + case 16: + map->format.format_reg = regmap_format_16; + break; + + default: + goto err_map; + } + + switch (config->val_bits) { + case 8: + map->format.format_val = regmap_format_8; + map->format.parse_val = regmap_parse_8; + break; + case 16: + map->format.format_val = regmap_format_16; + map->format.parse_val = regmap_parse_16; + break; + } + + if (!map->format.format_write && + !(map->format.format_reg && map->format.format_val)) + goto err_map; + + map->work_buf = kmalloc(map->format.buf_size, GFP_KERNEL); + if (map->work_buf == NULL) { + ret = -ENOMEM; + goto err_map; + } + + ret = regcache_init(map); + if (ret < 0) + goto err_map; + + regmap_debugfs_init(map); + + return map; + +err_map: + kfree(map); +err: + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(regmap_init); + +/** + * regmap_exit(): Free a previously allocated register map + */ +void regmap_exit(struct regmap *map) +{ + regcache_exit(map); + regmap_debugfs_exit(map); + kfree(map->work_buf); + kfree(map); +} +EXPORT_SYMBOL_GPL(regmap_exit); + +static int _regmap_raw_write(struct regmap *map, unsigned int reg, + const void *val, size_t val_len) +{ + u8 *u8 = map->work_buf; + void *buf; + int ret = -ENOTSUPP; + size_t len; + int i; + + /* Check for unwritable registers before we start */ + if (map->writeable_reg) + for (i = 0; i < val_len / map->format.val_bytes; i++) + if (!map->writeable_reg(map->dev, reg + i)) + return -EINVAL; + + map->format.format_reg(map->work_buf, reg); + + u8[0] |= map->write_flag_mask; + + trace_regmap_hw_write_start(map->dev, reg, + val_len / map->format.val_bytes); + + /* If we're doing a single register write we can probably just + * send the work_buf directly, otherwise try to do a gather + * write. + */ + if (val == map->work_buf + map->format.reg_bytes) + ret = map->bus->write(map->dev, map->work_buf, + map->format.reg_bytes + val_len); + else if (map->bus->gather_write) + ret = map->bus->gather_write(map->dev, map->work_buf, + map->format.reg_bytes, + val, val_len); + + /* If that didn't work fall back on linearising by hand. */ + if (ret == -ENOTSUPP) { + len = map->format.reg_bytes + val_len; + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, map->work_buf, map->format.reg_bytes); + memcpy(buf + map->format.reg_bytes, val, val_len); + ret = map->bus->write(map->dev, buf, len); + + kfree(buf); + } + + trace_regmap_hw_write_done(map->dev, reg, + val_len / map->format.val_bytes); + + return ret; +} + +int _regmap_write(struct regmap *map, unsigned int reg, + unsigned int val) +{ + int ret; + BUG_ON(!map->format.format_write && !map->format.format_val); + + if (!map->cache_bypass) { + ret = regcache_write(map, reg, val); + if (ret != 0) + return ret; + if (map->cache_only) + return 0; + } + + trace_regmap_reg_write(map->dev, reg, val); + + if (map->format.format_write) { + map->format.format_write(map, reg, val); + + trace_regmap_hw_write_start(map->dev, reg, 1); + + ret = map->bus->write(map->dev, map->work_buf, + map->format.buf_size); + + trace_regmap_hw_write_done(map->dev, reg, 1); + + return ret; + } else { + map->format.format_val(map->work_buf + map->format.reg_bytes, + val); + return _regmap_raw_write(map, reg, + map->work_buf + map->format.reg_bytes, + map->format.val_bytes); + } +} + +/** + * regmap_write(): Write a value to a single register + * + * @map: Register map to write to + * @reg: Register to write to + * @val: Value to be written + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_write(struct regmap *map, unsigned int reg, unsigned int val) +{ + int ret; + + mutex_lock(&map->lock); + + ret = _regmap_write(map, reg, val); + + mutex_unlock(&map->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_write); + +/** + * regmap_raw_write(): Write raw values to one or more registers + * + * @map: Register map to write to + * @reg: Initial register to write to + * @val: Block of data to be written, laid out for direct transmission to the + * device + * @val_len: Length of data pointed to by val. + * + * This function is intended to be used for things like firmware + * download where a large block of data needs to be transferred to the + * device. No formatting will be done on the data provided. + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_raw_write(struct regmap *map, unsigned int reg, + const void *val, size_t val_len) +{ + int ret; + + WARN_ON(map->cache_type != REGCACHE_NONE); + + mutex_lock(&map->lock); + + ret = _regmap_raw_write(map, reg, val, val_len); + + mutex_unlock(&map->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_raw_write); + +static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val, + unsigned int val_len) +{ + u8 *u8 = map->work_buf; + int ret; + + map->format.format_reg(map->work_buf, reg); + + /* + * Some buses or devices flag reads by setting the high bits in the + * register addresss; since it's always the high bits for all + * current formats we can do this here rather than in + * formatting. This may break if we get interesting formats. + */ + u8[0] |= map->read_flag_mask; + + trace_regmap_hw_read_start(map->dev, reg, + val_len / map->format.val_bytes); + + ret = map->bus->read(map->dev, map->work_buf, map->format.reg_bytes, + val, val_len); + + trace_regmap_hw_read_done(map->dev, reg, + val_len / map->format.val_bytes); + + return ret; +} + +static int _regmap_read(struct regmap *map, unsigned int reg, + unsigned int *val) +{ + int ret; + + if (!map->format.parse_val) + return -EINVAL; + + if (!map->cache_bypass) { + ret = regcache_read(map, reg, val); + if (ret == 0) + return 0; + } + + if (map->cache_only) + return -EBUSY; + + ret = _regmap_raw_read(map, reg, map->work_buf, map->format.val_bytes); + if (ret == 0) { + *val = map->format.parse_val(map->work_buf); + trace_regmap_reg_read(map->dev, reg, *val); + } + + return ret; +} + +/** + * regmap_read(): Read a value from a single register + * + * @map: Register map to write to + * @reg: Register to be read from + * @val: Pointer to store read value + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val) +{ + int ret; + + mutex_lock(&map->lock); + + ret = _regmap_read(map, reg, val); + + mutex_unlock(&map->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_read); + +/** + * regmap_raw_read(): Read raw data from the device + * + * @map: Register map to write to + * @reg: First register to be read from + * @val: Pointer to store read value + * @val_len: Size of data to read + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, + size_t val_len) +{ + int ret; + int i; + bool vol = true; + + for (i = 0; i < val_len / map->format.val_bytes; i++) + if (!regmap_volatile(map, reg + i)) + vol = false; + + WARN_ON(!vol && map->cache_type != REGCACHE_NONE); + + mutex_lock(&map->lock); + + ret = _regmap_raw_read(map, reg, val, val_len); + + mutex_unlock(&map->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_raw_read); + +/** + * regmap_bulk_read(): Read multiple registers from the device + * + * @map: Register map to write to + * @reg: First register to be read from + * @val: Pointer to store read value, in native register size for device + * @val_count: Number of registers to read + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, + size_t val_count) +{ + int ret, i; + size_t val_bytes = map->format.val_bytes; + bool vol = true; + + if (!map->format.parse_val) + return -EINVAL; + + /* Is this a block of volatile registers? */ + for (i = 0; i < val_count; i++) + if (!regmap_volatile(map, reg + i)) + vol = false; + + if (vol || map->cache_type == REGCACHE_NONE) { + ret = regmap_raw_read(map, reg, val, val_bytes * val_count); + if (ret != 0) + return ret; + + for (i = 0; i < val_count * val_bytes; i += val_bytes) + map->format.parse_val(val + i); + } else { + for (i = 0; i < val_count; i++) { + ret = regmap_read(map, reg + i, val + (i * val_bytes)); + if (ret != 0) + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(regmap_bulk_read); + +/** + * remap_update_bits: Perform a read/modify/write cycle on the register map + * + * @map: Register map to update + * @reg: Register to update + * @mask: Bitmask to change + * @val: New value for bitmask + * + * Returns zero for success, a negative number on error. + */ +int regmap_update_bits(struct regmap *map, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int ret; + unsigned int tmp; + + mutex_lock(&map->lock); + + ret = _regmap_read(map, reg, &tmp); + if (ret != 0) + goto out; + + tmp &= ~mask; + tmp |= val & mask; + + ret = _regmap_write(map, reg, tmp); + +out: + mutex_unlock(&map->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_update_bits); + +static int __init regmap_initcall(void) +{ + regmap_debugfs_initcall(); + + return 0; +} +postcore_initcall(regmap_initcall); |