diff options
| author | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
| commit | 8e4bff4cab0ddac6060645b0715210484d02ff40 (patch) | |
| tree | 3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/hwspinlock | |
Diffstat (limited to 'drivers/hwspinlock')
| -rw-r--r-- | drivers/hwspinlock/Kconfig | 42 | ||||
| -rw-r--r-- | drivers/hwspinlock/Makefile | 8 | ||||
| -rw-r--r-- | drivers/hwspinlock/hwspinlock_core.c | 601 | ||||
| -rw-r--r-- | drivers/hwspinlock/hwspinlock_internal.h | 77 | ||||
| -rw-r--r-- | drivers/hwspinlock/omap_hwspinlock.c | 190 | ||||
| -rw-r--r-- | drivers/hwspinlock/sci_hwspinlock.c | 265 | ||||
| -rw-r--r-- | drivers/hwspinlock/sprd_hwspinlock.c | 525 | ||||
| -rw-r--r-- | drivers/hwspinlock/u8500_hsem.c | 197 |
8 files changed, 1905 insertions, 0 deletions
diff --git a/drivers/hwspinlock/Kconfig b/drivers/hwspinlock/Kconfig new file mode 100644 index 00000000..781fbfb5 --- /dev/null +++ b/drivers/hwspinlock/Kconfig @@ -0,0 +1,42 @@ +# +# Generic HWSPINLOCK framework +# + +# HWSPINLOCK always gets selected by whoever wants it. +config HWSPINLOCK + tristate + +menu "Hardware Spinlock drivers" + +config HWSPINLOCK_OMAP + tristate "OMAP Hardware Spinlock device" + depends on ARCH_OMAP4 || SOC_OMAP5 + select HWSPINLOCK + help + Say y here to support the OMAP Hardware Spinlock device (firstly + introduced in OMAP4). + + If unsure, say N. + +config HSEM_U8500 + tristate "STE Hardware Semaphore functionality" + depends on ARCH_U8500 + select HWSPINLOCK + help + Say y here to support the STE Hardware Semaphore functionality, which + provides a synchronisation mechanism for the various processor on the + SoC. + + If unsure, say N. + +config HWSPINLOCK_SCI + tristate "SC Hardware Spinlock device" + depends on ARCH_SCX35 || ARCH_SC8825 + select HWSPINLOCK + help + Say y here to support the SPRD Hardware Spinlock device (firstly + introduced in tiger socs). + + If unsure, say N. + +endmenu diff --git a/drivers/hwspinlock/Makefile b/drivers/hwspinlock/Makefile new file mode 100644 index 00000000..b8ba4543 --- /dev/null +++ b/drivers/hwspinlock/Makefile @@ -0,0 +1,8 @@ +# +# Generic Hardware Spinlock framework +# + +obj-$(CONFIG_HWSPINLOCK) += hwspinlock_core.o +obj-$(CONFIG_HWSPINLOCK_OMAP) += omap_hwspinlock.o +obj-$(CONFIG_HSEM_U8500) += u8500_hsem.o +obj-$(CONFIG_HWSPINLOCK_SCI) += sprd_hwspinlock.o diff --git a/drivers/hwspinlock/hwspinlock_core.c b/drivers/hwspinlock/hwspinlock_core.c new file mode 100644 index 00000000..ce1b5d7b --- /dev/null +++ b/drivers/hwspinlock/hwspinlock_core.c @@ -0,0 +1,601 @@ +/* + * Hardware spinlock framework + * + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com + * + * Contact: Ohad Ben-Cohen <ohad@wizery.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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/err.h> +#include <linux/jiffies.h> +#include <linux/radix-tree.h> +#include <linux/hwspinlock.h> +#include <linux/pm_runtime.h> +#include <linux/mutex.h> + +#include "hwspinlock_internal.h" + +/* radix tree tags */ +#define HWSPINLOCK_UNUSED (0) /* tags an hwspinlock as unused */ + +/* + * A radix tree is used to maintain the available hwspinlock instances. + * The tree associates hwspinlock pointers with their integer key id, + * and provides easy-to-use API which makes the hwspinlock core code simple + * and easy to read. + * + * Radix trees are quick on lookups, and reasonably efficient in terms of + * storage, especially with high density usages such as this framework + * requires (a continuous range of integer keys, beginning with zero, is + * used as the ID's of the hwspinlock instances). + * + * The radix tree API supports tagging items in the tree, which this + * framework uses to mark unused hwspinlock instances (see the + * HWSPINLOCK_UNUSED tag above). As a result, the process of querying the + * tree, looking for an unused hwspinlock instance, is now reduced to a + * single radix tree API call. + */ +static RADIX_TREE(hwspinlock_tree, GFP_KERNEL); + +/* + * Synchronization of access to the tree is achieved using this mutex, + * as the radix-tree API requires that users provide all synchronisation. + * A mutex is needed because we're using non-atomic radix tree allocations. + */ +static DEFINE_MUTEX(hwspinlock_tree_lock); + + +/** + * __hwspin_trylock() - attempt to lock a specific hwspinlock + * @hwlock: an hwspinlock which we want to trylock + * @mode: controls whether local interrupts are disabled or not + * @flags: a pointer where the caller's interrupt state will be saved at (if + * requested) + * + * This function attempts to lock an hwspinlock, and will immediately + * fail if the hwspinlock is already taken. + * + * Upon a successful return from this function, preemption (and possibly + * interrupts) is disabled, so the caller must not sleep, and is advised to + * release the hwspinlock as soon as possible. This is required in order to + * minimize remote cores polling on the hardware interconnect. + * + * The user decides whether local interrupts are disabled or not, and if yes, + * whether he wants their previous state to be saved. It is up to the user + * to choose the appropriate @mode of operation, exactly the same way users + * should decide between spin_trylock, spin_trylock_irq and + * spin_trylock_irqsave. + * + * Returns 0 if we successfully locked the hwspinlock or -EBUSY if + * the hwspinlock was already taken. + * This function will never sleep. + */ +int __hwspin_trylock(struct hwspinlock *hwlock, int mode, unsigned long *flags) +{ + int ret; + + BUG_ON(!hwlock); + BUG_ON(!flags && mode == HWLOCK_IRQSTATE); + + /* + * This spin_lock{_irq, _irqsave} serves three purposes: + * + * 1. Disable preemption, in order to minimize the period of time + * in which the hwspinlock is taken. This is important in order + * to minimize the possible polling on the hardware interconnect + * by a remote user of this lock. + * 2. Make the hwspinlock SMP-safe (so we can take it from + * additional contexts on the local host). + * 3. Ensure that in_atomic/might_sleep checks catch potential + * problems with hwspinlock usage (e.g. scheduler checks like + * 'scheduling while atomic' etc.) + */ + if (mode == HWLOCK_IRQSTATE) + ret = spin_trylock_irqsave(&hwlock->lock, *flags); + else if (mode == HWLOCK_IRQ) + ret = spin_trylock_irq(&hwlock->lock); + else + ret = spin_trylock(&hwlock->lock); + + /* is lock already taken by another context on the local cpu ? */ + if (!ret) + return -EBUSY; + + /* try to take the hwspinlock device */ + ret = hwlock->bank->ops->trylock(hwlock); + + /* if hwlock is already taken, undo spin_trylock_* and exit */ + if (!ret) { + if (mode == HWLOCK_IRQSTATE) + spin_unlock_irqrestore(&hwlock->lock, *flags); + else if (mode == HWLOCK_IRQ) + spin_unlock_irq(&hwlock->lock); + else + spin_unlock(&hwlock->lock); + + return -EBUSY; + } + + /* + * We can be sure the other core's memory operations + * are observable to us only _after_ we successfully take + * the hwspinlock, and we must make sure that subsequent memory + * operations (both reads and writes) will not be reordered before + * we actually took the hwspinlock. + * + * Note: the implicit memory barrier of the spinlock above is too + * early, so we need this additional explicit memory barrier. + */ + mb(); + + return 0; +} +EXPORT_SYMBOL_GPL(__hwspin_trylock); + +/** + * __hwspin_lock_timeout() - lock an hwspinlock with timeout limit + * @hwlock: the hwspinlock to be locked + * @timeout: timeout value in msecs + * @mode: mode which controls whether local interrupts are disabled or not + * @flags: a pointer to where the caller's interrupt state will be saved at (if + * requested) + * + * This function locks the given @hwlock. If the @hwlock + * is already taken, the function will busy loop waiting for it to + * be released, but give up after @timeout msecs have elapsed. + * + * Upon a successful return from this function, preemption is disabled + * (and possibly local interrupts, too), so the caller must not sleep, + * and is advised to release the hwspinlock as soon as possible. + * This is required in order to minimize remote cores polling on the + * hardware interconnect. + * + * The user decides whether local interrupts are disabled or not, and if yes, + * whether he wants their previous state to be saved. It is up to the user + * to choose the appropriate @mode of operation, exactly the same way users + * should decide between spin_lock, spin_lock_irq and spin_lock_irqsave. + * + * Returns 0 when the @hwlock was successfully taken, and an appropriate + * error code otherwise (most notably -ETIMEDOUT if the @hwlock is still + * busy after @timeout msecs). The function will never sleep. + */ +int __hwspin_lock_timeout(struct hwspinlock *hwlock, unsigned int to, + int mode, unsigned long *flags) +{ + int ret; + unsigned long expire; + + expire = msecs_to_jiffies(to) + jiffies; + + for (;;) { + /* Try to take the hwspinlock */ + ret = __hwspin_trylock(hwlock, mode, flags); + if (ret != -EBUSY) + break; + + /* + * The lock is already taken, let's check if the user wants + * us to try again + */ + if (time_is_before_eq_jiffies(expire)) + return -ETIMEDOUT; + + /* + * Allow platform-specific relax handlers to prevent + * hogging the interconnect (no sleeping, though) + */ + if (hwlock->bank->ops->relax) + hwlock->bank->ops->relax(hwlock); + } + + return ret; +} +EXPORT_SYMBOL_GPL(__hwspin_lock_timeout); + +/** + * __hwspin_unlock() - unlock a specific hwspinlock + * @hwlock: a previously-acquired hwspinlock which we want to unlock + * @mode: controls whether local interrupts needs to be restored or not + * @flags: previous caller's interrupt state to restore (if requested) + * + * This function will unlock a specific hwspinlock, enable preemption and + * (possibly) enable interrupts or restore their previous state. + * @hwlock must be already locked before calling this function: it is a bug + * to call unlock on a @hwlock that is already unlocked. + * + * The user decides whether local interrupts should be enabled or not, and + * if yes, whether he wants their previous state to be restored. It is up + * to the user to choose the appropriate @mode of operation, exactly the + * same way users decide between spin_unlock, spin_unlock_irq and + * spin_unlock_irqrestore. + * + * The function will never sleep. + */ +void __hwspin_unlock(struct hwspinlock *hwlock, int mode, unsigned long *flags) +{ + BUG_ON(!hwlock); + BUG_ON(!flags && mode == HWLOCK_IRQSTATE); + + /* + * We must make sure that memory operations (both reads and writes), + * done before unlocking the hwspinlock, will not be reordered + * after the lock is released. + * + * That's the purpose of this explicit memory barrier. + * + * Note: the memory barrier induced by the spin_unlock below is too + * late; the other core is going to access memory soon after it will + * take the hwspinlock, and by then we want to be sure our memory + * operations are already observable. + */ + mb(); + + hwlock->bank->ops->unlock(hwlock); + + /* Undo the spin_trylock{_irq, _irqsave} called while locking */ + if (mode == HWLOCK_IRQSTATE) + spin_unlock_irqrestore(&hwlock->lock, *flags); + else if (mode == HWLOCK_IRQ) + spin_unlock_irq(&hwlock->lock); + else + spin_unlock(&hwlock->lock); +} +EXPORT_SYMBOL_GPL(__hwspin_unlock); + +static int hwspin_lock_register_single(struct hwspinlock *hwlock, int id) +{ + struct hwspinlock *tmp; + int ret; + + mutex_lock(&hwspinlock_tree_lock); + + ret = radix_tree_insert(&hwspinlock_tree, id, hwlock); + if (ret) { + if (ret == -EEXIST) + pr_err("hwspinlock id %d already exists!\n", id); + goto out; + } + + /* mark this hwspinlock as available */ + tmp = radix_tree_tag_set(&hwspinlock_tree, id, HWSPINLOCK_UNUSED); + + /* self-sanity check which should never fail */ + WARN_ON(tmp != hwlock); + +out: + mutex_unlock(&hwspinlock_tree_lock); + return 0; +} + +static struct hwspinlock *hwspin_lock_unregister_single(unsigned int id) +{ + struct hwspinlock *hwlock = NULL; + int ret; + + mutex_lock(&hwspinlock_tree_lock); + + /* make sure the hwspinlock is not in use (tag is set) */ + ret = radix_tree_tag_get(&hwspinlock_tree, id, HWSPINLOCK_UNUSED); + if (ret == 0) { + pr_err("hwspinlock %d still in use (or not present)\n", id); + goto out; + } + + hwlock = radix_tree_delete(&hwspinlock_tree, id); + if (!hwlock) { + pr_err("failed to delete hwspinlock %d\n", id); + goto out; + } + +out: + mutex_unlock(&hwspinlock_tree_lock); + return hwlock; +} + +/** + * hwspin_lock_register() - register a new hw spinlock device + * @bank: the hwspinlock device, which usually provides numerous hw locks + * @dev: the backing device + * @ops: hwspinlock handlers for this device + * @base_id: id of the first hardware spinlock in this bank + * @num_locks: number of hwspinlocks provided by this device + * + * This function should be called from the underlying platform-specific + * implementation, to register a new hwspinlock device instance. + * + * Should be called from a process context (might sleep) + * + * Returns 0 on success, or an appropriate error code on failure + */ +int hwspin_lock_register(struct hwspinlock_device *bank, struct device *dev, + const struct hwspinlock_ops *ops, int base_id, int num_locks) +{ + struct hwspinlock *hwlock; + int ret = 0, i; + + if (!bank || !ops || !dev || !num_locks || !ops->trylock || + !ops->unlock) { + pr_err("invalid parameters\n"); + return -EINVAL; + } + + bank->dev = dev; + bank->ops = ops; + bank->base_id = base_id; + bank->num_locks = num_locks; + + for (i = 0; i < num_locks; i++) { + hwlock = &bank->lock[i]; + + spin_lock_init(&hwlock->lock); + hwlock->bank = bank; + + ret = hwspin_lock_register_single(hwlock, base_id + i); + if (ret) + goto reg_failed; + } + + return 0; + +reg_failed: + while (--i >= 0) + hwspin_lock_unregister_single(base_id + i); + return ret; +} +EXPORT_SYMBOL_GPL(hwspin_lock_register); + +/** + * hwspin_lock_unregister() - unregister an hw spinlock device + * @bank: the hwspinlock device, which usually provides numerous hw locks + * + * This function should be called from the underlying platform-specific + * implementation, to unregister an existing (and unused) hwspinlock. + * + * Should be called from a process context (might sleep) + * + * Returns 0 on success, or an appropriate error code on failure + */ +int hwspin_lock_unregister(struct hwspinlock_device *bank) +{ + struct hwspinlock *hwlock, *tmp; + int i; + + for (i = 0; i < bank->num_locks; i++) { + hwlock = &bank->lock[i]; + + tmp = hwspin_lock_unregister_single(bank->base_id + i); + if (!tmp) + return -EBUSY; + + /* self-sanity check that should never fail */ + WARN_ON(tmp != hwlock); + } + + return 0; +} +EXPORT_SYMBOL_GPL(hwspin_lock_unregister); + +/** + * __hwspin_lock_request() - tag an hwspinlock as used and power it up + * + * This is an internal function that prepares an hwspinlock instance + * before it is given to the user. The function assumes that + * hwspinlock_tree_lock is taken. + * + * Returns 0 or positive to indicate success, and a negative value to + * indicate an error (with the appropriate error code) + */ +static int __hwspin_lock_request(struct hwspinlock *hwlock) +{ + struct device *dev = hwlock->bank->dev; + struct hwspinlock *tmp; + int ret; + + /* prevent underlying implementation from being removed */ + if (!try_module_get(dev->driver->owner)) { + dev_err(dev, "%s: can't get owner\n", __func__); + return -EINVAL; + } + + /* notify PM core that power is now needed */ + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "%s: can't power on device\n", __func__); + pm_runtime_put_noidle(dev); + module_put(dev->driver->owner); + return ret; + } + + /* mark hwspinlock as used, should not fail */ + tmp = radix_tree_tag_clear(&hwspinlock_tree, hwlock_to_id(hwlock), + HWSPINLOCK_UNUSED); + + /* self-sanity check that should never fail */ + WARN_ON(tmp != hwlock); + + return ret; +} + +/** + * hwspin_lock_get_id() - retrieve id number of a given hwspinlock + * @hwlock: a valid hwspinlock instance + * + * Returns the id number of a given @hwlock, or -EINVAL if @hwlock is invalid. + */ +int hwspin_lock_get_id(struct hwspinlock *hwlock) +{ + if (!hwlock) { + pr_err("invalid hwlock\n"); + return -EINVAL; + } + + return hwlock_to_id(hwlock); +} +EXPORT_SYMBOL_GPL(hwspin_lock_get_id); + +/** + * hwspin_lock_request() - request an hwspinlock + * + * This function should be called by users of the hwspinlock device, + * in order to dynamically assign them an unused hwspinlock. + * Usually the user of this lock will then have to communicate the lock's id + * to the remote core before it can be used for synchronization (to get the + * id of a given hwlock, use hwspin_lock_get_id()). + * + * Should be called from a process context (might sleep) + * + * Returns the address of the assigned hwspinlock, or NULL on error + */ +struct hwspinlock *hwspin_lock_request(void) +{ + struct hwspinlock *hwlock; + int ret; + + mutex_lock(&hwspinlock_tree_lock); + + /* look for an unused lock */ + ret = radix_tree_gang_lookup_tag(&hwspinlock_tree, (void **)&hwlock, + 0, 1, HWSPINLOCK_UNUSED); + if (ret == 0) { + pr_warn("a free hwspinlock is not available\n"); + hwlock = NULL; + goto out; + } + + /* sanity check that should never fail */ + WARN_ON(ret > 1); + + /* mark as used and power up */ + ret = __hwspin_lock_request(hwlock); + if (ret < 0) + hwlock = NULL; + +out: + mutex_unlock(&hwspinlock_tree_lock); + return hwlock; +} +EXPORT_SYMBOL_GPL(hwspin_lock_request); + +/** + * hwspin_lock_request_specific() - request for a specific hwspinlock + * @id: index of the specific hwspinlock that is requested + * + * This function should be called by users of the hwspinlock module, + * in order to assign them a specific hwspinlock. + * Usually early board code will be calling this function in order to + * reserve specific hwspinlock ids for predefined purposes. + * + * Should be called from a process context (might sleep) + * + * Returns the address of the assigned hwspinlock, or NULL on error + */ +struct hwspinlock *hwspin_lock_request_specific(unsigned int id) +{ + struct hwspinlock *hwlock; + int ret; + + mutex_lock(&hwspinlock_tree_lock); + + /* make sure this hwspinlock exists */ + hwlock = radix_tree_lookup(&hwspinlock_tree, id); + if (!hwlock) { + pr_warn("hwspinlock %u does not exist\n", id); + goto out; + } + + /* sanity check (this shouldn't happen) */ + WARN_ON(hwlock_to_id(hwlock) != id); + + /* make sure this hwspinlock is unused */ + ret = radix_tree_tag_get(&hwspinlock_tree, id, HWSPINLOCK_UNUSED); + if (ret == 0) { + pr_warn("hwspinlock %u is already in use\n", id); + hwlock = NULL; + goto out; + } + + /* mark as used and power up */ + ret = __hwspin_lock_request(hwlock); + if (ret < 0) + hwlock = NULL; + +out: + mutex_unlock(&hwspinlock_tree_lock); + return hwlock; +} +EXPORT_SYMBOL_GPL(hwspin_lock_request_specific); + +/** + * hwspin_lock_free() - free a specific hwspinlock + * @hwlock: the specific hwspinlock to free + * + * This function mark @hwlock as free again. + * Should only be called with an @hwlock that was retrieved from + * an earlier call to omap_hwspin_lock_request{_specific}. + * + * Should be called from a process context (might sleep) + * + * Returns 0 on success, or an appropriate error code on failure + */ +int hwspin_lock_free(struct hwspinlock *hwlock) +{ + struct device *dev; + struct hwspinlock *tmp; + int ret; + + if (!hwlock || !hwlock->bank) { + pr_err("invalid hwlock\n"); + return -EINVAL; + } + + dev = hwlock->bank->dev; + mutex_lock(&hwspinlock_tree_lock); + + /* make sure the hwspinlock is used */ + ret = radix_tree_tag_get(&hwspinlock_tree, hwlock_to_id(hwlock), + HWSPINLOCK_UNUSED); + if (ret == 1) { + dev_err(dev, "%s: hwlock is already free\n", __func__); + dump_stack(); + ret = -EINVAL; + goto out; + } + + /* notify the underlying device that power is not needed */ + ret = pm_runtime_put(dev); + if (ret < 0) + goto out; + + /* mark this hwspinlock as available */ + tmp = radix_tree_tag_set(&hwspinlock_tree, hwlock_to_id(hwlock), + HWSPINLOCK_UNUSED); + + /* sanity check (this shouldn't happen) */ + WARN_ON(tmp != hwlock); + + module_put(dev->driver->owner); + +out: + mutex_unlock(&hwspinlock_tree_lock); + return ret; +} +EXPORT_SYMBOL_GPL(hwspin_lock_free); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Hardware spinlock interface"); +MODULE_AUTHOR("Ohad Ben-Cohen <ohad@wizery.com>"); diff --git a/drivers/hwspinlock/hwspinlock_internal.h b/drivers/hwspinlock/hwspinlock_internal.h new file mode 100644 index 00000000..d26f78b8 --- /dev/null +++ b/drivers/hwspinlock/hwspinlock_internal.h @@ -0,0 +1,77 @@ +/* + * Hardware spinlocks internal header + * + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com + * + * Contact: Ohad Ben-Cohen <ohad@wizery.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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __HWSPINLOCK_HWSPINLOCK_H +#define __HWSPINLOCK_HWSPINLOCK_H + +#include <linux/spinlock.h> +#include <linux/device.h> + +struct hwspinlock_device; + +/** + * struct hwspinlock_ops - platform-specific hwspinlock handlers + * + * @trylock: make a single attempt to take the lock. returns 0 on + * failure and true on success. may _not_ sleep. + * @unlock: release the lock. always succeed. may _not_ sleep. + * @relax: optional, platform-specific relax handler, called by hwspinlock + * core while spinning on a lock, between two successive + * invocations of @trylock. may _not_ sleep. + */ +struct hwspinlock_ops { + int (*trylock)(struct hwspinlock *lock); + void (*unlock)(struct hwspinlock *lock); + void (*relax)(struct hwspinlock *lock); +}; + +/** + * struct hwspinlock - this struct represents a single hwspinlock instance + * @bank: the hwspinlock_device structure which owns this lock + * @lock: initialized and used by hwspinlock core + * @priv: private data, owned by the underlying platform-specific hwspinlock drv + */ +struct hwspinlock { + struct hwspinlock_device *bank; + spinlock_t lock; + void *priv; +}; + +/** + * struct hwspinlock_device - a device which usually spans numerous hwspinlocks + * @dev: underlying device, will be used to invoke runtime PM api + * @ops: platform-specific hwspinlock handlers + * @base_id: id index of the first lock in this device + * @num_locks: number of locks in this device + * @lock: dynamically allocated array of 'struct hwspinlock' + */ +struct hwspinlock_device { + struct device *dev; + const struct hwspinlock_ops *ops; + int base_id; + int num_locks; + struct hwspinlock lock[0]; +}; + +static inline int hwlock_to_id(struct hwspinlock *hwlock) +{ + int local_id = hwlock - &hwlock->bank->lock[0]; + + return hwlock->bank->base_id + local_id; +} + +#endif /* __HWSPINLOCK_HWSPINLOCK_H */ diff --git a/drivers/hwspinlock/omap_hwspinlock.c b/drivers/hwspinlock/omap_hwspinlock.c new file mode 100644 index 00000000..292869cc --- /dev/null +++ b/drivers/hwspinlock/omap_hwspinlock.c @@ -0,0 +1,190 @@ +/* + * OMAP hardware spinlock driver + * + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com + * + * Contact: Simon Que <sque@ti.com> + * Hari Kanigeri <h-kanigeri2@ti.com> + * Ohad Ben-Cohen <ohad@wizery.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/hwspinlock.h> +#include <linux/platform_device.h> + +#include "hwspinlock_internal.h" + +/* Spinlock register offsets */ +#define SYSSTATUS_OFFSET 0x0014 +#define LOCK_BASE_OFFSET 0x0800 + +#define SPINLOCK_NUMLOCKS_BIT_OFFSET (24) + +/* Possible values of SPINLOCK_LOCK_REG */ +#define SPINLOCK_NOTTAKEN (0) /* free */ +#define SPINLOCK_TAKEN (1) /* locked */ + +static int omap_hwspinlock_trylock(struct hwspinlock *lock) +{ + void __iomem *lock_addr = lock->priv; + + /* attempt to acquire the lock by reading its value */ + return (SPINLOCK_NOTTAKEN == readl(lock_addr)); +} + +static void omap_hwspinlock_unlock(struct hwspinlock *lock) +{ + void __iomem *lock_addr = lock->priv; + + /* release the lock by writing 0 to it */ + writel(SPINLOCK_NOTTAKEN, lock_addr); +} + +/* + * relax the OMAP interconnect while spinning on it. + * + * The specs recommended that the retry delay time will be + * just over half of the time that a requester would be + * expected to hold the lock. + * + * The number below is taken from an hardware specs example, + * obviously it is somewhat arbitrary. + */ +static void omap_hwspinlock_relax(struct hwspinlock *lock) +{ + ndelay(50); +} + +static const struct hwspinlock_ops omap_hwspinlock_ops = { + .trylock = omap_hwspinlock_trylock, + .unlock = omap_hwspinlock_unlock, + .relax = omap_hwspinlock_relax, +}; + +static int omap_hwspinlock_probe(struct platform_device *pdev) +{ + struct hwspinlock_pdata *pdata = pdev->dev.platform_data; + struct hwspinlock_device *bank; + struct hwspinlock *hwlock; + struct resource *res; + void __iomem *io_base; + int num_locks, i, ret; + + if (!pdata) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + io_base = ioremap(res->start, resource_size(res)); + if (!io_base) + return -ENOMEM; + + /* Determine number of locks */ + i = readl(io_base + SYSSTATUS_OFFSET); + i >>= SPINLOCK_NUMLOCKS_BIT_OFFSET; + + /* one of the four lsb's must be set, and nothing else */ + if (hweight_long(i & 0xf) != 1 || i > 8) { + ret = -EINVAL; + goto iounmap_base; + } + + num_locks = i * 32; /* actual number of locks in this device */ + + bank = kzalloc(sizeof(*bank) + num_locks * sizeof(*hwlock), GFP_KERNEL); + if (!bank) { + ret = -ENOMEM; + goto iounmap_base; + } + + platform_set_drvdata(pdev, bank); + + for (i = 0, hwlock = &bank->lock[0]; i < num_locks; i++, hwlock++) + hwlock->priv = io_base + LOCK_BASE_OFFSET + sizeof(u32) * i; + + /* + * runtime PM will make sure the clock of this module is + * enabled iff at least one lock is requested + */ + pm_runtime_enable(&pdev->dev); + + ret = hwspin_lock_register(bank, &pdev->dev, &omap_hwspinlock_ops, + pdata->base_id, num_locks); + if (ret) + goto reg_fail; + + return 0; + +reg_fail: + pm_runtime_disable(&pdev->dev); + kfree(bank); +iounmap_base: + iounmap(io_base); + return ret; +} + +static int omap_hwspinlock_remove(struct platform_device *pdev) +{ + struct hwspinlock_device *bank = platform_get_drvdata(pdev); + void __iomem *io_base = bank->lock[0].priv - LOCK_BASE_OFFSET; + int ret; + + ret = hwspin_lock_unregister(bank); + if (ret) { + dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret); + return ret; + } + + pm_runtime_disable(&pdev->dev); + iounmap(io_base); + kfree(bank); + + return 0; +} + +static struct platform_driver omap_hwspinlock_driver = { + .probe = omap_hwspinlock_probe, + .remove = omap_hwspinlock_remove, + .driver = { + .name = "omap_hwspinlock", + .owner = THIS_MODULE, + }, +}; + +static int __init omap_hwspinlock_init(void) +{ + return platform_driver_register(&omap_hwspinlock_driver); +} +/* board init code might need to reserve hwspinlocks for predefined purposes */ +postcore_initcall(omap_hwspinlock_init); + +static void __exit omap_hwspinlock_exit(void) +{ + platform_driver_unregister(&omap_hwspinlock_driver); +} +module_exit(omap_hwspinlock_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Hardware spinlock driver for OMAP"); +MODULE_AUTHOR("Simon Que <sque@ti.com>"); +MODULE_AUTHOR("Hari Kanigeri <h-kanigeri2@ti.com>"); +MODULE_AUTHOR("Ohad Ben-Cohen <ohad@wizery.com>"); diff --git a/drivers/hwspinlock/sci_hwspinlock.c b/drivers/hwspinlock/sci_hwspinlock.c new file mode 100644 index 00000000..9873aa25 --- /dev/null +++ b/drivers/hwspinlock/sci_hwspinlock.c @@ -0,0 +1,265 @@ +/* + * SCI hardware spinlock driver + * + * Copyright (C) 2012 Spreadtrum - http://www.spreadtrum.com + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com + * + * Contact: steve.zhan <steve.zhan@spreadtrum.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/hwspinlock.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <soc/sprd/sci.h> +#include <soc/sprd/sci_glb_regs.h> +#include <soc/sprd/arch_lock.h> + +#include "hwspinlock_internal.h" + + +#define HWSPINLOCK_ENABLE_CLEAR (0x454e434c) + +#define HWSPINLOCK_CLEAR (0x0) +#define HWSPINLOCK_TTLSTS (0x4) +#define HWSPINLOCK_DTLSTL (0x8) +#define HWSPINLOCK_CLEAREN (0xc) + +#define HWSPINLOCK_TOKEN_V0(_X_) (0x80 + 0x4*(_X_)) +#define HWSPINLOCK_TOKEN_V1(_X_) (0x800 + 0x4*(_X_)) + +#define THIS_PROCESSOR_KEY (HWSPINLOCK_WRITE_KEY) /*first processor */ +static void __iomem *hwspinlock_base; +static int hwspinlock_exist_cnt = 0; + +#define SPINLOCKS_BUSY() (readl(hwspinlock_base + HWSPINLOCK_TTLSTS)) +#define SPINLOCKS_DETAIL_STATUS(_X_) (_X_ = readl(hwspinlock_base + HWSPINLOCK_DTLSTL)) +#define SPINLOCKS_ENABLE_CLEAR() writel(HWSPINLOCK_ENABLE_CLEAR,hwspinlock_base + HWSPINLOCK_CLEAREN) +#define SPINLOCKS_DISABLE_CLEAR() writel(~HWSPINLOCK_ENABLE_CLEAR, hwspinlock_base + HWSPINLOCK_CLEAREN) + +__used static int hwspinlock_isbusy(unsigned int lockid) +{ + unsigned int status = 0; + SPINLOCKS_DETAIL_STATUS(status); + return ((status & (1 << lockid)) ? 1 : 0); +} + +__used static int hwspinlocks_isbusy(void) +{ + return ((SPINLOCKS_BUSY())? 1 : 0); +} + +__used static void hwspinlock_clear(unsigned int lockid) +{ + /*setting the abnormal clear bit to 1 makes the corresponding + *lock to Not Taken state + */ + SPINLOCKS_ENABLE_CLEAR(); + writel(1 << lockid, hwspinlock_base + HWSPINLOCK_CLEAR); + SPINLOCKS_DISABLE_CLEAR(); +} + +__used static void hwspinlock_clear_all(void) +{ + /*clear all the locks + */ + unsigned int lockid = 0; + SPINLOCKS_ENABLE_CLEAR(); + do { + writel(1 << lockid, hwspinlock_base + HWSPINLOCK_CLEAR); + } while (lockid++ < HWSPINLOCK_ID_TOTAL_NUMS); + SPINLOCKS_DISABLE_CLEAR(); +} + +static unsigned int do_lock_key(struct hwspinlock *lock) +{ + unsigned int key = THIS_PROCESSOR_KEY; + return key; +} + +static int __hwspinlock_trylock(struct hwspinlock *lock) +{ + void __iomem *addr = lock->priv; + + if (hwspinlock_vid == 0x100 || hwspinlock_vid == 0x200) { + if (!readl(addr)) + goto __locked; + } else { + unsigned int key = do_lock_key(lock); + if (!(key ^ HWSPINLOCK_NOTTAKEN_V0)) + BUG_ON(1); + if (HWSPINLOCK_NOTTAKEN_V0 == readl(addr)) { + writel(key, addr); + if (key == readl(addr)) + goto __locked; + } + } + return 0; + +__locked: + RECORD_HWLOCKS_STATUS_LOCK(hwlock_to_id(lock)); + return 1; +} + +static void __hwspinlock_unlock(struct hwspinlock *lock) +{ + void __iomem *lock_addr = lock->priv; + int unlock_key = 0; + + if (hwspinlock_vid == 0x100 || hwspinlock_vid == 0x200) { + unlock_key = HWSPINLOCK_NOTTAKEN_V1; + } else { + unlock_key = HWSPINLOCK_NOTTAKEN_V0; + if (!(readl(lock_addr) ^ unlock_key)) + BUG_ON(1); + } + RECORD_HWLOCKS_STATUS_UNLOCK(hwlock_to_id(lock)); + writel(unlock_key, lock_addr); +} + +/* + * relax the interconnect while spinning on it. + * + * The specs recommended that the retry delay time will be + * just over half of the time that a requester would be + * expected to hold the lock. + * + * The number below is taken from an hardware specs example, + * obviously it is somewhat arbitrary. + */ +static void __hwspinlock_relax(struct hwspinlock *lock) +{ + ndelay(10); +} + +static const struct hwspinlock_ops sci_hwspinlock_ops = { + .trylock = __hwspinlock_trylock, + .unlock = __hwspinlock_unlock, + .relax = __hwspinlock_relax, +}; + +static int sci_hwspinlock_probe(struct platform_device *pdev) +{ + struct hwspinlock_device *bank; + struct hwspinlock *hwlock; + int i, ret, num_locks; + + pdev->id = of_alias_get_id(pdev->dev.of_node, "hwspinlock"); + if(pdev->id == 0) + return 0; + __hwspinlock_init(); + + /*each group have 32 locks*/ + num_locks = 32; + hwspinlock_base = (void __iomem *)(pdev->id == 0 ? SPRD_HWSPINLOCK0_BASE : SPRD_HWSPINLOCK1_BASE); + hwspinlock_vid = __raw_readl(hwspinlock_base + 0xffc); + + bank = kzalloc(sizeof(*bank) + num_locks * sizeof(*hwlock), GFP_KERNEL); + if (!bank) { + ret = -ENOMEM; + goto exit; + } + + platform_set_drvdata(pdev, bank); + + for (i = 0, hwlock = &bank->lock[0]; i < num_locks; i++, hwlock++) { + if (hwspinlock_vid == 0x100 || hwspinlock_vid == 0x200) + hwlock->priv = (void __iomem *)(hwspinlock_base + HWSPINLOCK_TOKEN_V1(i)); + else + hwlock->priv = (void __iomem *)(hwspinlock_base + HWSPINLOCK_TOKEN_V0(i)); + } + /* + * runtime PM will make sure the clock of this module is + * enabled if at least one lock is requested + */ + pm_runtime_enable(&pdev->dev); + + ret = hwspin_lock_register(bank, &pdev->dev, &sci_hwspinlock_ops, + hwspinlock_exist_cnt, num_locks); + + if (ret) + goto reg_fail; + + hwspinlock_exist_cnt += num_locks; + + printk("sci_hwspinlock_probe ok: hwspinlock name = %s, hwspinlock_vid = 0x%x, add num_locks = %d\n", + pdev->name,hwspinlock_vid,num_locks); + + return 0; + +reg_fail: + pm_runtime_disable(&pdev->dev); + kfree(bank); +exit: + return ret; +} + +static int sci_hwspinlock_remove(struct platform_device *pdev) +{ + struct hwspinlock_device *bank = platform_get_drvdata(pdev); + int ret; + + ret = hwspin_lock_unregister(bank); + if (ret) { + dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret); + return ret; + } + + pm_runtime_disable(&pdev->dev); + kfree(bank); + + return 0; +} + +static const struct of_device_id sprd_hwspinlock_of_match[] = { + { .compatible = "sprd,sprd-hwspinlock", }, + { /* sentinel */ } +}; + +static struct platform_driver sci_hwspinlock_driver = { + .probe = sci_hwspinlock_probe, + .remove = sci_hwspinlock_remove, + .driver = { + .name = "sci_hwspinlock", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sprd_hwspinlock_of_match), + }, +}; + +static int __init sci_hwspinlock_init(void) +{ + return platform_driver_register(&sci_hwspinlock_driver); +} + +/* board init code might need to reserve hwspinlocks for predefined purposes */ +postcore_initcall(sci_hwspinlock_init); + +static void __exit sci_hwspinlock_exit(void) +{ + platform_driver_unregister(&sci_hwspinlock_driver); +} + +module_exit(sci_hwspinlock_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Hardware spinlock driver for Spreadtrum"); +MODULE_AUTHOR("steve.zhan <steve.zhan@spreadtrum.com>"); diff --git a/drivers/hwspinlock/sprd_hwspinlock.c b/drivers/hwspinlock/sprd_hwspinlock.c new file mode 100644 index 00000000..b8b95906 --- /dev/null +++ b/drivers/hwspinlock/sprd_hwspinlock.c @@ -0,0 +1,525 @@ +/* + * SCI hardware spinlock driver + * + * Copyright (C) 2012 Spreadtrum - http://www.spreadtrum.com + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com + * + * Contact: steve.zhan <steve.zhan@spreadtrum.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/hwspinlock.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <soc/sprd/sci.h> +#include <soc/sprd/sci_glb_regs.h> +#include <soc/sprd/arch_lock.h> +#include "hwspinlock_internal.h" + +/* hwspinlock reg */ +#define HWSPINLOCK_CLEAR (0x0) +#define HWSPINLOCK_RECCTRL (0x4) +#define HWSPINLOCK_TTLSTS (0x8) +#define HWSPINLOCK_CLEAREN (0xc) +#define HWSPINLOCK_FLAG0 (0x10) +#define HWSPINLOCK_FLAG1 (0x14) +#define HWSPINLOCK_FLAG2 (0x18) +#define HWSPINLOCK_FLAG3 (0x1c) +#define HWSPINLOCK_MASTERID(_X_) (0x80 + 0x4*(_X_)) +#define HWSPINLOCK_TOKEN_V0(_X_) (0x80 + 0x4*(_X_)) +#define HWSPINLOCK_TOKEN_V1(_X_) (0x800 + 0x4*(_X_)) +#define HWSPINLOCK_VERID (0xFFC) + +/* RECCTRL */ +#define HWSPINLOCK_ID BIT(0) +#define HWSPINLOCK_USER_BITS BIT(1) + +/* first processor */ +#define THIS_PROCESSOR_KEY (HWSPINLOCK_WRITE_KEY) + +/* spinlock enable */ +#define HWSPINLOCK_ENABLE_CLEAR (0x454e434c) + +/* hwspinlock controller */ +#define AON_HWSPLOCK (1) +#define AP_HWSPLOCK (0) + +struct sprd_hwspinlock_dev { + unsigned int hwspinlock_vid; + unsigned long hwspinlock_base; + unsigned int hwspinlock_cnt; + struct hwspinlock_device bank; +}; + +/* array[0] stands for lock0 */ +unsigned char hwlocks_implemented[HWSPINLOCK_ID_TOTAL_NUMS]; +unsigned char local_hwlocks_status[HWSPINLOCK_ID_TOTAL_NUMS]; +struct hwspinlock *hwlocks[HWSPINLOCK_ID_TOTAL_NUMS]; +int hwspinlock_vid_early; + +static struct sprd_hwspinlock_dev *hwlock_to_sprdlock_dev(struct hwspinlock *hwlock) +{ + struct hwspinlock_device *hwbank; + int lock_id; + + if(!hwlock) + return NULL; + + lock_id = hwlock_to_id(hwlock); + hwbank = container_of(hwlock,struct hwspinlock_device,lock[lock_id]); + if(hwbank) + return container_of(hwbank,struct sprd_hwspinlock_dev,bank); + else + return NULL; +} + +#ifdef CONFIG_ARCH_WHALE +int sprd_hwlock_init(void) +{ + return 0; +} +#else +int sprd_hwlock_init(void) +{ + writel_relaxed(BIT_SPINLOCK_EB, (void __iomem *)(REG_GLB_SET(REG_AP_AHB_AHB_EB))); + writel_relaxed(BIT_SPLK_EB, (void __iomem *)(REG_GLB_SET(REG_AON_APB_APB_EB0))); + return 0; +} +#endif + +static void inline sprd_hwlocks_implemented(int hwlock_id) +{ + if (hwlock_id == AON_HWSPLOCK){ + hwlocks_implemented[HWLOCK_ADI] = 1; + hwlocks_implemented[HWLOCK_GLB] = 1; + hwlocks_implemented[HWLOCK_AGPIO] = 1; + hwlocks_implemented[HWLOCK_AEIC] = 1; + hwlocks_implemented[HWLOCK_ADC] = 1; + hwlocks_implemented[HWLOCK_EFUSE] = 1; + }else if (hwlock_id == AP_HWSPLOCK){ + /* Caution: ap hwspinlock id need add 31 */ + /* hwlocks_implemented[HWLOCK_ADI+31] = 1; */ + } +} + +int sprd_record_hwlock_sts(unsigned int lock_id, unsigned int sts) +{ + if(lock_id >= HWSPINLOCK_ID_TOTAL_NUMS){ + printk(KERN_ERR "Hwspinlock id is wrong!\n"); + return -ENXIO; + } + + local_hwlocks_status[lock_id] = sts; + return sts; +} + +int sprd_hwspinlock_isbusy(unsigned int lock_id) +{ + unsigned int status = 0; + struct sprd_hwspinlock_dev *sprd_hwlock; + + if (!hwlocks[lock_id]){ + printk(KERN_ERR "This hwspinlock is not register!\n"); + return -ENODEV; + } + + sprd_hwlock = hwlock_to_sprdlock_dev(hwlocks[lock_id]); + if (!sprd_hwlock){ + printk(KERN_ERR "Can't find hwspinlock device!\n"); + return -ENODEV; + } + + status = readl_relaxed((void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_TTLSTS)); + if (status < 0) + return -EIO; + return ((status & (1 << lock_id)) ? 1 : 0); +} + +int sprd_hwspinlocks_isbusy(void) +{ + int id; + + for(id = 0; id < HWSPINLOCK_ID_TOTAL_NUMS; id++){ + if(sprd_hwspinlock_isbusy(id) > 0) + return 1; + } + return 0; +} + +int sprd_hwspinlock_master_id(unsigned int lock_id) +{ + unsigned int master_id = 0; + struct sprd_hwspinlock_dev *sprd_hwlock; + + if(!hwlocks[lock_id]){ + printk(KERN_ERR "This hwspinlock is not register!\n"); + return -ENODEV; + } + + sprd_hwlock = hwlock_to_sprdlock_dev(hwlocks[lock_id]); + if (!sprd_hwlock){ + printk(KERN_ERR "Can't find hwspinlock device!\n"); + return -ENODEV; + } + + master_id = readl_relaxed((void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_MASTERID(lock_id))); + return master_id; +} + +void hwspinlock_clear(unsigned int lock_id) +{ + struct sprd_hwspinlock_dev *sprd_hwlock; + + if(!hwlocks[lock_id]){ + printk(KERN_ERR "This hwspinlock is not register!\n"); + return; + } + + sprd_hwlock = hwlock_to_sprdlock_dev(hwlocks[lock_id]); + if (!sprd_hwlock){ + printk(KERN_ERR "Can't find hwspinlock device!\n"); + return; + } + /*setting the abnormal clear bit to 1 makes the corresponding + *lock to Not Taken state + */ + writel_relaxed(HWSPINLOCK_ENABLE_CLEAR,(void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_CLEAREN)); + writel_relaxed((1 << lock_id), (void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_CLEAR)); + writel_relaxed(~HWSPINLOCK_ENABLE_CLEAR, (void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_CLEAREN)); +} + +void hwspinlock_clear_all(void) +{ + unsigned int lock_id = 0; + do { + hwspinlock_clear(lock_id); + } while (lock_id++ < HWSPINLOCK_ID_TOTAL_NUMS); +} + +static int sprd_check_hwspinlock_vid(struct hwspinlock *lock) +{ + struct sprd_hwspinlock_dev *sprd_hwlock; + + sprd_hwlock = hwlock_to_sprdlock_dev(lock); + if (!sprd_hwlock){ + printk(KERN_ERR "%s:Can't find hwspinlock device!\n",__func__); + return -ENODEV; + } + + /* for sharkl/sharkl64/whale project */ + if (sprd_hwlock->hwspinlock_vid == 0x100 || sprd_hwlock->hwspinlock_vid == 0x200 + || sprd_hwlock->hwspinlock_vid == 0x300){ + return 1; + } else { + printk(KERN_ERR "Warning:hwlock version id is 0x%x!\n",sprd_hwlock->hwspinlock_vid); + return 0; + } +} + +static unsigned long sprd_hwspinlock_addr(unsigned int lock_id) +{ + unsigned long addr; + struct hwspinlock *lock; + + if(lock_id >= HWSPINLOCK_ID_TOTAL_NUMS){ + printk(KERN_ERR "Hwspinlock id is wrong!\n"); + return 0; + } + + lock = hwlocks[lock_id]; + if (!lock){ + printk(KERN_ERR "Call %s: can't find hwspinlock!\n",__func__); + return 0; + } + else + addr = (unsigned long)lock->priv; + + return addr; +} + +static int sprd_init_hwlocks(int hwlock_id) +{ + struct hwspinlock **plock; + int i; + + sprd_hwlocks_implemented(hwlock_id); + + if (hwlock_id == AON_HWSPLOCK) + i = 0; + else if (hwlock_id == AP_HWSPLOCK) + i = 31; + else + i = HWSPINLOCK_ID_TOTAL_NUMS; + + for (; i < HWSPINLOCK_ID_TOTAL_NUMS; ++i) { + if (hwlocks_implemented[i]) { + plock = &hwlocks[i]; + *plock = hwspin_lock_request_specific(i); + if (WARN_ON(IS_ERR_OR_NULL(*plock))) + *plock = NULL; + else + pr_info("early alloc hwspinlock id %d\n", + hwspin_lock_get_id(*plock)); + } + } + return 0; +} + +static unsigned int do_lock_key(struct hwspinlock *lock) +{ + unsigned int key = THIS_PROCESSOR_KEY; + return key; +} + +static int __hwspinlock_trylock(struct hwspinlock *lock) +{ + void __iomem *addr = lock->priv; + + if (sprd_check_hwspinlock_vid(lock)) { + if (!readl_relaxed(addr)) + goto __locked; + } else { + unsigned int key = do_lock_key(lock); + if (!(key ^ HWSPINLOCK_NOTTAKEN_V0)) + BUG_ON(1); + if (HWSPINLOCK_NOTTAKEN_V0 == readl_relaxed(addr)) { + writel_relaxed(key, addr); + if (key == readl_relaxed(addr)) + goto __locked; + } + } + + printk(KERN_ERR "Hwspinlock [%d] lock failed!\n",hwlock_to_id(lock)); + return 0; + +__locked: + sprd_record_hwlock_sts(hwlock_to_id(lock), 1); + return 1; +} + +static void __hwspinlock_unlock(struct hwspinlock *lock) +{ + void __iomem *lock_addr = lock->priv; + int unlock_key; + + if (sprd_check_hwspinlock_vid(lock)) { + unlock_key = HWSPINLOCK_NOTTAKEN_V1; + } else { + unlock_key = HWSPINLOCK_NOTTAKEN_V0; + if (!(readl_relaxed(lock_addr) ^ unlock_key)) + BUG_ON(1); + } + + writel_relaxed(unlock_key, lock_addr); + sprd_record_hwlock_sts(hwlock_to_id(lock), 0); +} + +/* + * relax the interconnect while spinning on it. + * + * The specs recommended that the retry delay time will be + * just over half of the time that a requester would be + * expected to hold the lock. + * + * The number below is taken from an hardware specs example, + * obviously it is somewhat arbitrary. + */ +static void __hwspinlock_relax(struct hwspinlock *lock) +{ + ndelay(10); +} + +static const struct hwspinlock_ops sprd_hwspinlock_ops = { + .trylock = __hwspinlock_trylock, + .unlock = __hwspinlock_unlock, + .relax = __hwspinlock_relax, +}; + +static int sprd_hwspinlock_probe(struct platform_device *pdev) +{ + struct hwspinlock *hwlock; + struct sprd_hwspinlock_dev *sprd_hwlock; + static int hwspinlock_exist_cnt = 0; + u32 num_locks = 0; + int i, ret, hwlock_id; + + ret = of_alias_get_id(pdev->dev.of_node, "hwspinlock"); + if (IS_ERR_VALUE(ret)) { + printk(KERN_ERR "Hwspinlock get alias failed!\n"); + return ret; + } + hwlock_id = ret; + + ret = of_property_read_u32(pdev->dev.of_node,"hwspinlock_cnt",&num_locks); + if(ret){ + printk(KERN_ERR "Hwspinlock get count failed!\n"); + num_locks = 32; + } + + sprd_hwlock = devm_kzalloc(&pdev->dev, sizeof(struct sprd_hwspinlock_dev) + + num_locks * sizeof(*hwlock), GFP_KERNEL); + if (!sprd_hwlock) { + printk(KERN_ERR "Hwspinlock device alloc memory failed!\n"); + return -ENOMEM; + } + + /* intial the hwspinlock */ + sprd_hwlock_init(); + sprd_hwlock->hwspinlock_cnt = num_locks; + if (hwlock_id == 1) + sprd_hwlock->hwspinlock_base = SPRD_HWSPINLOCK1_BASE; + else if (hwlock_id == 0) + sprd_hwlock->hwspinlock_base = SPRD_HWSPINLOCK0_BASE; + else { + printk(KERN_ERR "Hwspinlock id is wrong!\n"); + return -ENODEV; + } + sprd_hwlock->hwspinlock_vid = readl_relaxed((void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_VERID)); + + for (i = 0, hwlock = &sprd_hwlock->bank.lock[0]; i < num_locks; i++, hwlock++) { + if (sprd_hwlock->hwspinlock_vid == 0x100 || sprd_hwlock->hwspinlock_vid == 0x200 + || sprd_hwlock->hwspinlock_vid == 0x300) + hwlock->priv = (void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_TOKEN_V1(i)); + else + hwlock->priv = (void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_TOKEN_V0(i)); + } + + /* + * runtime PM will make sure the clock of this module is + * enabled if at least one lock is requested + */ + platform_set_drvdata(pdev, sprd_hwlock); + pm_runtime_enable(&pdev->dev); + + ret = hwspin_lock_register(&sprd_hwlock->bank, &pdev->dev, &sprd_hwspinlock_ops, + hwspinlock_exist_cnt, num_locks); + if (ret){ + printk(KERN_ERR "Hwspinlock register failed!\n"); + goto reg_fail; + } + + hwspinlock_exist_cnt += num_locks; + /* inital the hwlocks */ + sprd_init_hwlocks(hwlock_id); +#ifdef CONFIG_ARCH_WHALE + /* record master user bits */ + writel_relaxed(HWSPINLOCK_USER_BITS, (void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_RECCTRL)); +#endif + + printk("sprd hwspinlock probe ok: hwspinlock name = %s, hwspinlock_vid = 0x%x," + "hwspinlock_exist_cnt = %d\n", pdev->name,sprd_hwlock->hwspinlock_vid,hwspinlock_exist_cnt); + + return 0; + +reg_fail: + pm_runtime_disable(&pdev->dev); + return ret; +} + +static int sprd_hwspinlock_remove(struct platform_device *pdev) +{ + struct sprd_hwspinlock_dev *sprd_hwlock = platform_get_drvdata(pdev); + int ret; + + ret = hwspin_lock_unregister(&sprd_hwlock->bank); + if (ret) { + dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret); + return ret; + } + + pm_runtime_disable(&pdev->dev); + return 0; +} + +static const struct of_device_id sprd_hwspinlock_of_match[] = { + { .compatible = "sprd,sprd-hwspinlock", }, + { /* sentinel */ } +}; + +static struct platform_driver sprd_hwspinlock_driver = { + .probe = sprd_hwspinlock_probe, + .remove = sprd_hwspinlock_remove, + .driver = { + .name = "sprd_hwspinlock", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sprd_hwspinlock_of_match), + }, +}; + +static int __init sprd_hwspinlock_init(void) +{ + return platform_driver_register(&sprd_hwspinlock_driver); +} + +/* board init code might need to reserve hwspinlocks for predefined purposes */ +postcore_initcall(sprd_hwspinlock_init); + +static void __exit sprd_hwspinlock_exit(void) +{ + platform_driver_unregister(&sprd_hwspinlock_driver); +} + +module_exit(sprd_hwspinlock_exit); + +static int remove_node(struct device_node *np) +{ + struct property *ps; + + ps = kzalloc(sizeof(*ps), GFP_KERNEL); + if (!ps) + return -1; + + ps->value = kstrdup("no", GFP_KERNEL); + ps->length = strlen(ps->value); + ps->name = kstrdup("status", GFP_KERNEL); + of_update_property(np, ps); + printk("After register,hwspinlock1 node available=%d\n",of_device_is_available(np)); + + return 0; +} + +int __init early_hwspinlocks_init(void) +{ + struct device_node *np; + struct platform_device *pdev; + int ret; + + np = of_find_node_by_name(NULL, "hwspinlock1"); + if (!np) { + pr_warn("Can't get the hwspinlock1 node!\n"); + return -ENODEV; + } + + pdev = of_platform_device_create(np, 0, NULL); + if (!pdev) { + pr_warn("register hwspinlock1 failed!\n"); + return -ENODEV; + } + + pr_info("SPRD register hwspinlock1 ok,hwspinlock1's name is %s!\n",pdev->name); + ret = remove_node(np); + return ret; +} + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Hardware spinlock driver for Spreadtrum"); +MODULE_AUTHOR("baolin.wang <baolin.wang@spreadtrum.com>"); diff --git a/drivers/hwspinlock/u8500_hsem.c b/drivers/hwspinlock/u8500_hsem.c new file mode 100644 index 00000000..401c33bc --- /dev/null +++ b/drivers/hwspinlock/u8500_hsem.c @@ -0,0 +1,197 @@ +/* + * u8500 HWSEM driver + * + * Copyright (C) 2010-2011 ST-Ericsson + * + * Implements u8500 semaphore handling for protocol 1, no interrupts. + * + * Author: Mathieu Poirier <mathieu.poirier@linaro.org> + * Heavily borrowed from the work of : + * Simon Que <sque@ti.com> + * Hari Kanigeri <h-kanigeri2@ti.com> + * Ohad Ben-Cohen <ohad@wizery.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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/hwspinlock.h> +#include <linux/platform_device.h> + +#include "hwspinlock_internal.h" + +/* + * Implementation of STE's HSem protocol 1 without interrutps. + * The only masterID we allow is '0x01' to force people to use + * HSems for synchronisation between processors rather than processes + * on the ARM core. + */ + +#define U8500_MAX_SEMAPHORE 32 /* a total of 32 semaphore */ +#define RESET_SEMAPHORE (0) /* free */ + +/* + * CPU ID for master running u8500 kernel. + * Hswpinlocks should only be used to synchonise operations + * between the Cortex A9 core and the other CPUs. Hence + * forcing the masterID to a preset value. + */ +#define HSEM_MASTER_ID 0x01 + +#define HSEM_REGISTER_OFFSET 0x08 + +#define HSEM_CTRL_REG 0x00 +#define HSEM_ICRALL 0x90 +#define HSEM_PROTOCOL_1 0x01 + +static int u8500_hsem_trylock(struct hwspinlock *lock) +{ + void __iomem *lock_addr = lock->priv; + + writel(HSEM_MASTER_ID, lock_addr); + + /* get only first 4 bit and compare to masterID. + * if equal, we have the semaphore, otherwise + * someone else has it. + */ + return (HSEM_MASTER_ID == (0x0F & readl(lock_addr))); +} + +static void u8500_hsem_unlock(struct hwspinlock *lock) +{ + void __iomem *lock_addr = lock->priv; + + /* release the lock by writing 0 to it */ + writel(RESET_SEMAPHORE, lock_addr); +} + +/* + * u8500: what value is recommended here ? + */ +static void u8500_hsem_relax(struct hwspinlock *lock) +{ + ndelay(50); +} + +static const struct hwspinlock_ops u8500_hwspinlock_ops = { + .trylock = u8500_hsem_trylock, + .unlock = u8500_hsem_unlock, + .relax = u8500_hsem_relax, +}; + +static int u8500_hsem_probe(struct platform_device *pdev) +{ + struct hwspinlock_pdata *pdata = pdev->dev.platform_data; + struct hwspinlock_device *bank; + struct hwspinlock *hwlock; + struct resource *res; + void __iomem *io_base; + int i, ret, num_locks = U8500_MAX_SEMAPHORE; + ulong val; + + if (!pdata) + return -ENODEV; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + io_base = ioremap(res->start, resource_size(res)); + if (!io_base) + return -ENOMEM; + + /* make sure protocol 1 is selected */ + val = readl(io_base + HSEM_CTRL_REG); + writel((val & ~HSEM_PROTOCOL_1), io_base + HSEM_CTRL_REG); + + /* clear all interrupts */ + writel(0xFFFF, io_base + HSEM_ICRALL); + + bank = kzalloc(sizeof(*bank) + num_locks * sizeof(*hwlock), GFP_KERNEL); + if (!bank) { + ret = -ENOMEM; + goto iounmap_base; + } + + platform_set_drvdata(pdev, bank); + + for (i = 0, hwlock = &bank->lock[0]; i < num_locks; i++, hwlock++) + hwlock->priv = io_base + HSEM_REGISTER_OFFSET + sizeof(u32) * i; + + /* no pm needed for HSem but required to comply with hwspilock core */ + pm_runtime_enable(&pdev->dev); + + ret = hwspin_lock_register(bank, &pdev->dev, &u8500_hwspinlock_ops, + pdata->base_id, num_locks); + if (ret) + goto reg_fail; + + return 0; + +reg_fail: + pm_runtime_disable(&pdev->dev); + kfree(bank); +iounmap_base: + iounmap(io_base); + return ret; +} + +static int u8500_hsem_remove(struct platform_device *pdev) +{ + struct hwspinlock_device *bank = platform_get_drvdata(pdev); + void __iomem *io_base = bank->lock[0].priv - HSEM_REGISTER_OFFSET; + int ret; + + /* clear all interrupts */ + writel(0xFFFF, io_base + HSEM_ICRALL); + + ret = hwspin_lock_unregister(bank); + if (ret) { + dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret); + return ret; + } + + pm_runtime_disable(&pdev->dev); + iounmap(io_base); + kfree(bank); + + return 0; +} + +static struct platform_driver u8500_hsem_driver = { + .probe = u8500_hsem_probe, + .remove = u8500_hsem_remove, + .driver = { + .name = "u8500_hsem", + .owner = THIS_MODULE, + }, +}; + +static int __init u8500_hsem_init(void) +{ + return platform_driver_register(&u8500_hsem_driver); +} +/* board init code might need to reserve hwspinlocks for predefined purposes */ +postcore_initcall(u8500_hsem_init); + +static void __exit u8500_hsem_exit(void) +{ + platform_driver_unregister(&u8500_hsem_driver); +} +module_exit(u8500_hsem_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Hardware Spinlock driver for u8500"); +MODULE_AUTHOR("Mathieu Poirier <mathieu.poirier@linaro.org>"); |
