diff options
Diffstat (limited to 'drivers/hwspinlock')
| -rw-r--r-- | drivers/hwspinlock/Kconfig | 40 | ||||
| -rw-r--r-- | drivers/hwspinlock/Makefile | 2 | ||||
| -rw-r--r-- | drivers/hwspinlock/hwspinlock_core.c | 236 | ||||
| -rw-r--r-- | drivers/hwspinlock/hwspinlock_internal.h | 5 | ||||
| -rw-r--r-- | drivers/hwspinlock/omap_hwspinlock.c | 89 | ||||
| -rw-r--r-- | drivers/hwspinlock/qcom_hwspinlock.c | 177 | ||||
| -rw-r--r-- | drivers/hwspinlock/sirf_hwspinlock.c | 137 | ||||
| -rw-r--r-- | drivers/hwspinlock/sprd_hwspinlock.c | 68 | ||||
| -rw-r--r-- | drivers/hwspinlock/stm32_hwspinlock.c | 71 | ||||
| -rw-r--r-- | drivers/hwspinlock/sun6i_hwspinlock.c | 210 | ||||
| -rw-r--r-- | drivers/hwspinlock/u8500_hsem.c | 57 |
11 files changed, 568 insertions, 524 deletions
diff --git a/drivers/hwspinlock/Kconfig b/drivers/hwspinlock/Kconfig index 7869c67e5b6b..3874d15b0e9b 100644 --- a/drivers/hwspinlock/Kconfig +++ b/drivers/hwspinlock/Kconfig @@ -6,10 +6,11 @@ menuconfig HWSPINLOCK bool "Hardware Spinlock drivers" +if HWSPINLOCK + config HWSPINLOCK_OMAP tristate "OMAP Hardware Spinlock device" - depends on HWSPINLOCK - depends on ARCH_OMAP4 || SOC_OMAP5 || SOC_DRA7XX || SOC_AM33XX || SOC_AM43XX + depends on ARCH_OMAP4 || SOC_OMAP5 || SOC_DRA7XX || SOC_AM33XX || SOC_AM43XX || ARCH_K3 || COMPILE_TEST help Say y here to support the OMAP Hardware Spinlock device (firstly introduced in OMAP4). @@ -18,8 +19,7 @@ config HWSPINLOCK_OMAP config HWSPINLOCK_QCOM tristate "Qualcomm Hardware Spinlock device" - depends on HWSPINLOCK - depends on ARCH_QCOM + depends on ARCH_QCOM || COMPILE_TEST select MFD_SYSCON help Say y here to support the Qualcomm Hardware Mutex functionality, which @@ -28,22 +28,9 @@ config HWSPINLOCK_QCOM If unsure, say N. -config HWSPINLOCK_SIRF - tristate "SIRF Hardware Spinlock device" - depends on HWSPINLOCK - depends on ARCH_SIRF - help - Say y here to support the SIRF Hardware Spinlock device, which - provides a synchronisation mechanism for the various processors - on the SoC. - - It's safe to say n here if you're not interested in SIRF hardware - spinlock or just want a bare minimum kernel. - config HWSPINLOCK_SPRD tristate "SPRD Hardware Spinlock device" - depends on ARCH_SPRD - depends on HWSPINLOCK + depends on ARCH_SPRD || COMPILE_TEST help Say y here to support the SPRD Hardware Spinlock device. @@ -51,20 +38,29 @@ config HWSPINLOCK_SPRD config HWSPINLOCK_STM32 tristate "STM32 Hardware Spinlock device" - depends on MACH_STM32MP157 - depends on HWSPINLOCK + depends on MACH_STM32MP157 || COMPILE_TEST help Say y here to support the STM32 Hardware Spinlock device. If unsure, say N. +config HWSPINLOCK_SUN6I + tristate "SUN6I Hardware Spinlock device" + depends on ARCH_SUNXI || COMPILE_TEST + help + Say y here to support the SUN6I Hardware Spinlock device which can be + found in most of the sun6i compatible Allwinner SoCs. + + If unsure, say N. + config HSEM_U8500 tristate "STE Hardware Semaphore functionality" - depends on HWSPINLOCK - depends on ARCH_U8500 + depends on ARCH_U8500 || COMPILE_TEST 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. + +endif # HWSPINLOCK diff --git a/drivers/hwspinlock/Makefile b/drivers/hwspinlock/Makefile index ed053e3f02be..a0f16c9aaa82 100644 --- a/drivers/hwspinlock/Makefile +++ b/drivers/hwspinlock/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_HWSPINLOCK) += hwspinlock_core.o obj-$(CONFIG_HWSPINLOCK_OMAP) += omap_hwspinlock.o obj-$(CONFIG_HWSPINLOCK_QCOM) += qcom_hwspinlock.o -obj-$(CONFIG_HWSPINLOCK_SIRF) += sirf_hwspinlock.o obj-$(CONFIG_HWSPINLOCK_SPRD) += sprd_hwspinlock.o obj-$(CONFIG_HWSPINLOCK_STM32) += stm32_hwspinlock.o +obj-$(CONFIG_HWSPINLOCK_SUN6I) += sun6i_hwspinlock.o obj-$(CONFIG_HSEM_U8500) += u8500_hsem.o diff --git a/drivers/hwspinlock/hwspinlock_core.c b/drivers/hwspinlock/hwspinlock_core.c index 2bad40d42210..cc8e952a6772 100644 --- a/drivers/hwspinlock/hwspinlock_core.c +++ b/drivers/hwspinlock/hwspinlock_core.c @@ -9,6 +9,7 @@ #define pr_fmt(fmt) "%s: " fmt, __func__ +#include <linux/delay.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/spinlock.h> @@ -23,6 +24,9 @@ #include "hwspinlock_internal.h" +/* retry delay used in atomic context */ +#define HWSPINLOCK_RETRY_DELAY_US 100 + /* radix tree tags */ #define HWSPINLOCK_UNUSED (0) /* tags an hwspinlock as unused */ @@ -68,11 +72,11 @@ static DEFINE_MUTEX(hwspinlock_tree_lock); * user need some time-consuming or sleepable operations under the hardware * lock, they need one sleepable lock (like mutex) to protect the operations. * - * If the mode is not HWLOCK_RAW, 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. + * If the mode is neither HWLOCK_IN_ATOMIC nor HWLOCK_RAW, 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 @@ -80,16 +84,17 @@ static DEFINE_MUTEX(hwspinlock_tree_lock); * should decide between spin_trylock, spin_trylock_irq and * spin_trylock_irqsave. * - * Returns 0 if we successfully locked the hwspinlock or -EBUSY if + * 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); + if (WARN_ON(!hwlock || (!flags && mode == HWLOCK_IRQSTATE))) + return -EINVAL; /* * This spin_lock{_irq, _irqsave} serves three purposes: @@ -112,6 +117,7 @@ int __hwspin_trylock(struct hwspinlock *hwlock, int mode, unsigned long *flags) ret = spin_trylock_irq(&hwlock->lock); break; case HWLOCK_RAW: + case HWLOCK_IN_ATOMIC: ret = 1; break; default: @@ -136,6 +142,7 @@ int __hwspin_trylock(struct hwspinlock *hwlock, int mode, unsigned long *flags) spin_unlock_irq(&hwlock->lock); break; case HWLOCK_RAW: + case HWLOCK_IN_ATOMIC: /* Nothing to do */ break; default: @@ -165,7 +172,7 @@ 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 + * @to: 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) @@ -179,26 +186,31 @@ EXPORT_SYMBOL_GPL(__hwspin_trylock); * user need some time-consuming or sleepable operations under the hardware * lock, they need one sleepable lock (like mutex) to protect the operations. * - * If the mode is not HWLOCK_RAW, 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. + * If the mode is HWLOCK_IN_ATOMIC (called from an atomic context) the timeout + * is handled with busy-waiting delays, hence shall not exceed few msecs. + * + * If the mode is neither HWLOCK_IN_ATOMIC nor HWLOCK_RAW, 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_lock, spin_lock_irq and spin_lock_irqsave. * - * Returns 0 when the @hwlock was successfully taken, and an appropriate + * 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. + * 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; + unsigned long expire, atomic_delay = 0; expire = msecs_to_jiffies(to) + jiffies; @@ -212,8 +224,15 @@ int __hwspin_lock_timeout(struct hwspinlock *hwlock, unsigned int to, * 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; + if (mode == HWLOCK_IN_ATOMIC) { + udelay(HWSPINLOCK_RETRY_DELAY_US); + atomic_delay += HWSPINLOCK_RETRY_DELAY_US; + if (atomic_delay > to * 1000) + return -ETIMEDOUT; + } else { + if (time_is_before_eq_jiffies(expire)) + return -ETIMEDOUT; + } /* * Allow platform-specific relax handlers to prevent @@ -248,8 +267,8 @@ EXPORT_SYMBOL_GPL(__hwspin_lock_timeout); */ void __hwspin_unlock(struct hwspinlock *hwlock, int mode, unsigned long *flags) { - BUG_ON(!hwlock); - BUG_ON(!flags && mode == HWLOCK_IRQSTATE); + if (WARN_ON(!hwlock || (!flags && mode == HWLOCK_IRQSTATE))) + return; /* * We must make sure that memory operations (both reads and writes), @@ -276,6 +295,7 @@ void __hwspin_unlock(struct hwspinlock *hwlock, int mode, unsigned long *flags) spin_unlock_irq(&hwlock->lock); break; case HWLOCK_RAW: + case HWLOCK_IN_ATOMIC: /* Nothing to do */ break; default: @@ -286,14 +306,41 @@ void __hwspin_unlock(struct hwspinlock *hwlock, int mode, unsigned long *flags) EXPORT_SYMBOL_GPL(__hwspin_unlock); /** + * hwspin_lock_bust() - bust a specific hwspinlock + * @hwlock: a previously-acquired hwspinlock which we want to bust + * @id: identifier of the remote lock holder, if applicable + * + * This function will bust a hwspinlock that was previously acquired as + * long as the current owner of the lock matches the id given by the caller. + * + * Context: Process context. + * + * Returns: 0 on success, or -EINVAL if the hwspinlock does not exist, or + * the bust operation fails, and -EOPNOTSUPP if the bust operation is not + * defined for the hwspinlock. + */ +int hwspin_lock_bust(struct hwspinlock *hwlock, unsigned int id) +{ + if (WARN_ON(!hwlock)) + return -EINVAL; + + if (!hwlock->bank->ops->bust) { + pr_err("bust operation not defined\n"); + return -EOPNOTSUPP; + } + + return hwlock->bank->ops->bust(hwlock, id); +} +EXPORT_SYMBOL_GPL(hwspin_lock_bust); + +/** * of_hwspin_lock_simple_xlate - translate hwlock_spec to return a lock id - * @bank: the hwspinlock device bank * @hwlock_spec: hwlock specifier as found in the device tree * * This is a simple translation function, suitable for hwspinlock platform * drivers that only has a lock specifier length of 1. * - * Returns a relative index of the lock within a specified bank on success, + * Returns: a relative index of the lock within a specified bank on success, * or -EINVAL on invalid specifier cell count. */ static inline int @@ -315,9 +362,10 @@ of_hwspin_lock_simple_xlate(const struct of_phandle_args *hwlock_spec) * hwspinlock device, so that it can be requested using the normal * hwspin_lock_request_specific() API. * - * Returns the global lock id number on success, -EPROBE_DEFER if the hwspinlock - * device is not yet registered, -EINVAL on invalid args specifier value or an - * appropriate error as returned from the OF parsing of the DT client node. + * Returns: the global lock id number on success, -EPROBE_DEFER if the + * hwspinlock device is not yet registered, -EINVAL on invalid args + * specifier value or an appropriate error as returned from the OF parsing + * of the DT client node. */ int of_hwspin_lock_get_id(struct device_node *np, int index) { @@ -333,6 +381,11 @@ int of_hwspin_lock_get_id(struct device_node *np, int index) if (ret) return ret; + if (!of_device_is_available(args.np)) { + ret = -ENOENT; + goto out; + } + /* Find the hwspinlock device: we need its base_id */ ret = -EPROBE_DEFER; rcu_read_lock(); @@ -345,7 +398,7 @@ int of_hwspin_lock_get_id(struct device_node *np, int index) continue; } - if (hwlock->bank->dev->of_node == args.np) { + if (device_match_of_node(hwlock->bank->dev, args.np)) { ret = 0; break; } @@ -377,9 +430,10 @@ EXPORT_SYMBOL_GPL(of_hwspin_lock_get_id); * the hwspinlock device, so that it can be requested using the normal * hwspin_lock_request_specific() API. * - * Returns the global lock id number on success, -EPROBE_DEFER if the hwspinlock - * device is not yet registered, -EINVAL on invalid args specifier value or an - * appropriate error as returned from the OF parsing of the DT client node. + * Returns: the global lock id number on success, -EPROBE_DEFER if the + * hwspinlock device is not yet registered, -EINVAL on invalid args + * specifier value or an appropriate error as returned from the OF parsing + * of the DT client node. */ int of_hwspin_lock_get_id_byname(struct device_node *np, const char *name) { @@ -459,7 +513,7 @@ out: * * Should be called from a process context (might sleep) * - * Returns 0 on success, or an appropriate error code on failure + * 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) @@ -507,7 +561,7 @@ EXPORT_SYMBOL_GPL(hwspin_lock_register); * * Should be called from a process context (might sleep) * - * Returns 0 on success, or an appropriate error code on failure + * Returns: %0 on success, or an appropriate error code on failure */ int hwspin_lock_unregister(struct hwspinlock_device *bank) { @@ -556,7 +610,7 @@ static int devm_hwspin_lock_device_match(struct device *dev, void *res, * * Should be called from a process context (might sleep) * - * Returns 0 on success, or an appropriate error code on failure + * Returns: %0 on success, or an appropriate error code on failure */ int devm_hwspin_lock_unregister(struct device *dev, struct hwspinlock_device *bank) @@ -585,7 +639,7 @@ EXPORT_SYMBOL_GPL(devm_hwspin_lock_unregister); * * Should be called from a process context (might sleep) * - * Returns 0 on success, or an appropriate error code on failure + * Returns: %0 on success, or an appropriate error code on failure */ int devm_hwspin_lock_register(struct device *dev, struct hwspinlock_device *bank, @@ -613,12 +667,13 @@ EXPORT_SYMBOL_GPL(devm_hwspin_lock_register); /** * __hwspin_lock_request() - tag an hwspinlock as used and power it up + * @hwlock: the target hwspinlock * * 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 + * 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) @@ -635,13 +690,15 @@ static int __hwspin_lock_request(struct hwspinlock *hwlock) /* notify PM core that power is now needed */ ret = pm_runtime_get_sync(dev); - if (ret < 0) { + if (ret < 0 && ret != -EACCES) { dev_err(dev, "%s: can't power on device\n", __func__); pm_runtime_put_noidle(dev); module_put(dev->driver->owner); return ret; } + ret = 0; + /* mark hwspinlock as used, should not fail */ tmp = radix_tree_tag_clear(&hwspinlock_tree, hwlock_to_id(hwlock), HWSPINLOCK_UNUSED); @@ -653,66 +710,6 @@ static int __hwspin_lock_request(struct hwspinlock *hwlock) } /** - * 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 * @@ -723,7 +720,7 @@ EXPORT_SYMBOL_GPL(hwspin_lock_request); * * Should be called from a process context (might sleep) * - * Returns the address of the assigned hwspinlock, or NULL on error + * Returns: the address of the assigned hwspinlock, or %NULL on error */ struct hwspinlock *hwspin_lock_request_specific(unsigned int id) { @@ -771,7 +768,7 @@ EXPORT_SYMBOL_GPL(hwspin_lock_request_specific); * * Should be called from a process context (might sleep) * - * Returns 0 on success, or an appropriate error code on failure + * Returns: %0 on success, or an appropriate error code on failure */ int hwspin_lock_free(struct hwspinlock *hwlock) { @@ -798,9 +795,7 @@ int hwspin_lock_free(struct hwspinlock *hwlock) } /* notify the underlying device that power is not needed */ - ret = pm_runtime_put(dev); - if (ret < 0) - goto out; + pm_runtime_put(dev); /* mark this hwspinlock as available */ tmp = radix_tree_tag_set(&hwspinlock_tree, hwlock_to_id(hwlock), @@ -843,7 +838,7 @@ static void devm_hwspin_lock_release(struct device *dev, void *res) * * Should be called from a process context (might sleep) * - * Returns 0 on success, or an appropriate error code on failure + * Returns: %0 on success, or an appropriate error code on failure */ int devm_hwspin_lock_free(struct device *dev, struct hwspinlock *hwlock) { @@ -858,40 +853,6 @@ int devm_hwspin_lock_free(struct device *dev, struct hwspinlock *hwlock) EXPORT_SYMBOL_GPL(devm_hwspin_lock_free); /** - * devm_hwspin_lock_request() - request an hwspinlock for a managed device - * @dev: the device to 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 *devm_hwspin_lock_request(struct device *dev) -{ - struct hwspinlock **ptr, *hwlock; - - ptr = devres_alloc(devm_hwspin_lock_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return NULL; - - hwlock = hwspin_lock_request(); - if (hwlock) { - *ptr = hwlock; - devres_add(dev, ptr); - } else { - devres_free(ptr); - } - - return hwlock; -} -EXPORT_SYMBOL_GPL(devm_hwspin_lock_request); - -/** * devm_hwspin_lock_request_specific() - request for a specific hwspinlock for * a managed device * @dev: the device to request the specific hwspinlock @@ -904,7 +865,7 @@ EXPORT_SYMBOL_GPL(devm_hwspin_lock_request); * * Should be called from a process context (might sleep) * - * Returns the address of the assigned hwspinlock, or NULL on error + * Returns: the address of the assigned hwspinlock, or %NULL on error */ struct hwspinlock *devm_hwspin_lock_request_specific(struct device *dev, unsigned int id) @@ -927,6 +888,5 @@ struct hwspinlock *devm_hwspin_lock_request_specific(struct device *dev, } EXPORT_SYMBOL_GPL(devm_hwspin_lock_request_specific); -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 index 9eb6bd020dc7..f298fc0ee5ad 100644 --- a/drivers/hwspinlock/hwspinlock_internal.h +++ b/drivers/hwspinlock/hwspinlock_internal.h @@ -21,6 +21,8 @@ struct hwspinlock_device; * @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. + * @bust: optional, platform-specific bust handler, called by hwspinlock + * core to bust a specific lock. * @relax: optional, platform-specific relax handler, called by hwspinlock * core while spinning on a lock, between two successive * invocations of @trylock. may _not_ sleep. @@ -28,6 +30,7 @@ struct hwspinlock_device; struct hwspinlock_ops { int (*trylock)(struct hwspinlock *lock); void (*unlock)(struct hwspinlock *lock); + int (*bust)(struct hwspinlock *lock, unsigned int id); void (*relax)(struct hwspinlock *lock); }; @@ -56,7 +59,7 @@ struct hwspinlock_device { const struct hwspinlock_ops *ops; int base_id; int num_locks; - struct hwspinlock lock[0]; + struct hwspinlock lock[]; }; static inline int hwlock_to_id(struct hwspinlock *hwlock) diff --git a/drivers/hwspinlock/omap_hwspinlock.c b/drivers/hwspinlock/omap_hwspinlock.c index 625844e0abef..27b47b8623c0 100644 --- a/drivers/hwspinlock/omap_hwspinlock.c +++ b/drivers/hwspinlock/omap_hwspinlock.c @@ -2,11 +2,12 @@ /* * OMAP hardware spinlock driver * - * Copyright (C) 2010-2015 Texas Instruments Incorporated - http://www.ti.com + * Copyright (C) 2010-2021 Texas Instruments Incorporated - https://www.ti.com * * Contact: Simon Que <sque@ti.com> * Hari Kanigeri <h-kanigeri2@ti.com> * Ohad Ben-Cohen <ohad@wizery.com> + * Suman Anna <s-anna@ti.com> */ #include <linux/kernel.h> @@ -73,36 +74,24 @@ static const struct hwspinlock_ops omap_hwspinlock_ops = { static int omap_hwspinlock_probe(struct platform_device *pdev) { - struct device_node *node = pdev->dev.of_node; struct hwspinlock_device *bank; - struct hwspinlock *hwlock; - struct resource *res; void __iomem *io_base; int num_locks, i, ret; /* Only a single hwspinlock block device is supported */ int base_id = 0; - if (!node) - 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; + io_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); /* * make sure the module is enabled and clocked before reading * the module SYSSTATUS register */ - pm_runtime_enable(&pdev->dev); - ret = pm_runtime_get_sync(&pdev->dev); - if (ret < 0) { - pm_runtime_put_noidle(&pdev->dev); - goto iounmap_base; - } + devm_pm_runtime_enable(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret < 0) + return ret; /* Determine number of locks */ i = readl(io_base + SYSSTATUS_OFFSET); @@ -114,73 +103,39 @@ static int omap_hwspinlock_probe(struct platform_device *pdev) */ ret = pm_runtime_put(&pdev->dev); if (ret < 0) - goto iounmap_base; + return ret; /* 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; - } + if (hweight_long(i & 0xf) != 1 || i > 8) + return -EINVAL; num_locks = i * 32; /* actual number of locks in this device */ - bank = kzalloc(struct_size(bank, lock, num_locks), GFP_KERNEL); - if (!bank) { - ret = -ENOMEM; - goto iounmap_base; - } - - platform_set_drvdata(pdev, bank); + bank = devm_kzalloc(&pdev->dev, struct_size(bank, lock, num_locks), + GFP_KERNEL); + if (!bank) + return -ENOMEM; - for (i = 0, hwlock = &bank->lock[0]; i < num_locks; i++, hwlock++) - hwlock->priv = io_base + LOCK_BASE_OFFSET + sizeof(u32) * i; + for (i = 0; i < num_locks; i++) + bank->lock[i].priv = io_base + LOCK_BASE_OFFSET + sizeof(u32) * i; - ret = hwspin_lock_register(bank, &pdev->dev, &omap_hwspinlock_ops, + return devm_hwspin_lock_register(&pdev->dev, bank, &omap_hwspinlock_ops, base_id, num_locks); - if (ret) - goto reg_fail; - - return 0; - -reg_fail: - kfree(bank); -iounmap_base: - pm_runtime_disable(&pdev->dev); - 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 const struct of_device_id omap_hwspinlock_of_match[] = { { .compatible = "ti,omap4-hwspinlock", }, + { .compatible = "ti,am64-hwspinlock", }, + { .compatible = "ti,am654-hwspinlock", }, { /* end */ }, }; MODULE_DEVICE_TABLE(of, omap_hwspinlock_of_match); static struct platform_driver omap_hwspinlock_driver = { .probe = omap_hwspinlock_probe, - .remove = omap_hwspinlock_remove, .driver = { .name = "omap_hwspinlock", - .of_match_table = of_match_ptr(omap_hwspinlock_of_match), + .of_match_table = omap_hwspinlock_of_match, }, }; diff --git a/drivers/hwspinlock/qcom_hwspinlock.c b/drivers/hwspinlock/qcom_hwspinlock.c index 6da7447d277d..0390979fd765 100644 --- a/drivers/hwspinlock/qcom_hwspinlock.c +++ b/drivers/hwspinlock/qcom_hwspinlock.c @@ -12,7 +12,6 @@ #include <linux/of.h> #include <linux/of_device.h> #include <linux/platform_device.h> -#include <linux/pm_runtime.h> #include <linux/regmap.h> #include "hwspinlock_internal.h" @@ -20,6 +19,12 @@ #define QCOM_MUTEX_APPS_PROC_ID 1 #define QCOM_MUTEX_NUM_LOCKS 32 +struct qcom_hwspinlock_of_data { + u32 offset; + u32 stride; + const struct regmap_config *regmap_config; +}; + static int qcom_hwspinlock_trylock(struct hwspinlock *lock) { struct regmap_field *field = lock->priv; @@ -59,53 +64,158 @@ static void qcom_hwspinlock_unlock(struct hwspinlock *lock) pr_err("%s: failed to unlock spinlock\n", __func__); } +static int qcom_hwspinlock_bust(struct hwspinlock *lock, unsigned int id) +{ + struct regmap_field *field = lock->priv; + u32 owner; + int ret; + + ret = regmap_field_read(field, &owner); + if (ret) { + dev_err(lock->bank->dev, "unable to query spinlock owner\n"); + return ret; + } + + if (owner != id) + return 0; + + ret = regmap_field_write(field, 0); + if (ret) { + dev_err(lock->bank->dev, "failed to bust spinlock\n"); + return ret; + } + + return 0; +} + static const struct hwspinlock_ops qcom_hwspinlock_ops = { .trylock = qcom_hwspinlock_trylock, .unlock = qcom_hwspinlock_unlock, + .bust = qcom_hwspinlock_bust, +}; + +static const struct regmap_config sfpb_mutex_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x100, + .fast_io = true, +}; + +static const struct qcom_hwspinlock_of_data of_sfpb_mutex = { + .offset = 0x4, + .stride = 0x4, + .regmap_config = &sfpb_mutex_config, +}; + +static const struct regmap_config tcsr_msm8226_mutex_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x1000, + .fast_io = true, +}; + +static const struct qcom_hwspinlock_of_data of_msm8226_tcsr_mutex = { + .offset = 0, + .stride = 0x80, + .regmap_config = &tcsr_msm8226_mutex_config, +}; + +static const struct regmap_config tcsr_mutex_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x20000, + .fast_io = true, +}; + +static const struct qcom_hwspinlock_of_data of_tcsr_mutex = { + .offset = 0, + .stride = 0x1000, + .regmap_config = &tcsr_mutex_config, }; static const struct of_device_id qcom_hwspinlock_of_match[] = { - { .compatible = "qcom,sfpb-mutex" }, - { .compatible = "qcom,tcsr-mutex" }, + { .compatible = "qcom,sfpb-mutex", .data = &of_sfpb_mutex }, + { .compatible = "qcom,tcsr-mutex", .data = &of_tcsr_mutex }, + { .compatible = "qcom,apq8084-tcsr-mutex", .data = &of_msm8226_tcsr_mutex }, + { .compatible = "qcom,msm8226-tcsr-mutex", .data = &of_msm8226_tcsr_mutex }, + { .compatible = "qcom,msm8974-tcsr-mutex", .data = &of_msm8226_tcsr_mutex }, + { .compatible = "qcom,msm8994-tcsr-mutex", .data = &of_msm8226_tcsr_mutex }, { } }; MODULE_DEVICE_TABLE(of, qcom_hwspinlock_of_match); -static int qcom_hwspinlock_probe(struct platform_device *pdev) +static struct regmap *qcom_hwspinlock_probe_syscon(struct platform_device *pdev, + u32 *base, u32 *stride) { - struct hwspinlock_device *bank; struct device_node *syscon; - struct reg_field field; struct regmap *regmap; - size_t array_size; - u32 stride; - u32 base; int ret; - int i; syscon = of_parse_phandle(pdev->dev.of_node, "syscon", 0); - if (!syscon) { - dev_err(&pdev->dev, "no syscon property\n"); - return -ENODEV; - } + if (!syscon) + return ERR_PTR(-ENODEV); regmap = syscon_node_to_regmap(syscon); of_node_put(syscon); if (IS_ERR(regmap)) - return PTR_ERR(regmap); + return regmap; - ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 1, &base); + ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 1, base); if (ret < 0) { dev_err(&pdev->dev, "no offset in syscon\n"); - return -EINVAL; + return ERR_PTR(-EINVAL); } - ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 2, &stride); + ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 2, stride); if (ret < 0) { dev_err(&pdev->dev, "no stride syscon\n"); - return -EINVAL; + return ERR_PTR(-EINVAL); } + return regmap; +} + +static struct regmap *qcom_hwspinlock_probe_mmio(struct platform_device *pdev, + u32 *offset, u32 *stride) +{ + const struct qcom_hwspinlock_of_data *data; + struct device *dev = &pdev->dev; + void __iomem *base; + + data = of_device_get_match_data(dev); + if (!data->regmap_config) + return ERR_PTR(-EINVAL); + + *offset = data->offset; + *stride = data->stride; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return ERR_CAST(base); + + return devm_regmap_init_mmio(dev, base, data->regmap_config); +} + +static int qcom_hwspinlock_probe(struct platform_device *pdev) +{ + struct hwspinlock_device *bank; + struct reg_field field; + struct regmap *regmap; + size_t array_size; + u32 stride; + u32 base; + int i; + + regmap = qcom_hwspinlock_probe_syscon(pdev, &base, &stride); + if (IS_ERR(regmap) && PTR_ERR(regmap) == -ENODEV) + regmap = qcom_hwspinlock_probe_mmio(pdev, &base, &stride); + + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + array_size = QCOM_MUTEX_NUM_LOCKS * sizeof(struct hwspinlock); bank = devm_kzalloc(&pdev->dev, sizeof(*bank) + array_size, GFP_KERNEL); if (!bank) @@ -120,37 +230,16 @@ static int qcom_hwspinlock_probe(struct platform_device *pdev) bank->lock[i].priv = devm_regmap_field_alloc(&pdev->dev, regmap, field); + if (IS_ERR(bank->lock[i].priv)) + return PTR_ERR(bank->lock[i].priv); } - pm_runtime_enable(&pdev->dev); - - ret = hwspin_lock_register(bank, &pdev->dev, &qcom_hwspinlock_ops, - 0, QCOM_MUTEX_NUM_LOCKS); - if (ret) - pm_runtime_disable(&pdev->dev); - - return ret; -} - -static int qcom_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); - - return 0; + return devm_hwspin_lock_register(&pdev->dev, bank, &qcom_hwspinlock_ops, + 0, QCOM_MUTEX_NUM_LOCKS); } static struct platform_driver qcom_hwspinlock_driver = { .probe = qcom_hwspinlock_probe, - .remove = qcom_hwspinlock_remove, .driver = { .name = "qcom_hwspinlock", .of_match_table = qcom_hwspinlock_of_match, diff --git a/drivers/hwspinlock/sirf_hwspinlock.c b/drivers/hwspinlock/sirf_hwspinlock.c deleted file mode 100644 index 1f625cd68c50..000000000000 --- a/drivers/hwspinlock/sirf_hwspinlock.c +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * SIRF hardware spinlock driver - * - * Copyright (c) 2015 Cambridge Silicon Radio Limited, a CSR plc group company. - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/device.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 <linux/of.h> -#include <linux/of_address.h> - -#include "hwspinlock_internal.h" - -struct sirf_hwspinlock { - void __iomem *io_base; - struct hwspinlock_device bank; -}; - -/* Number of Hardware Spinlocks*/ -#define HW_SPINLOCK_NUMBER 30 - -/* Hardware spinlock register offsets */ -#define HW_SPINLOCK_BASE 0x404 -#define HW_SPINLOCK_OFFSET(x) (HW_SPINLOCK_BASE + 0x4 * (x)) - -static int sirf_hwspinlock_trylock(struct hwspinlock *lock) -{ - void __iomem *lock_addr = lock->priv; - - /* attempt to acquire the lock by reading value == 1 from it */ - return !!readl(lock_addr); -} - -static void sirf_hwspinlock_unlock(struct hwspinlock *lock) -{ - void __iomem *lock_addr = lock->priv; - - /* release the lock by writing 0 to it */ - writel(0, lock_addr); -} - -static const struct hwspinlock_ops sirf_hwspinlock_ops = { - .trylock = sirf_hwspinlock_trylock, - .unlock = sirf_hwspinlock_unlock, -}; - -static int sirf_hwspinlock_probe(struct platform_device *pdev) -{ - struct sirf_hwspinlock *hwspin; - struct hwspinlock *hwlock; - int idx, ret; - - if (!pdev->dev.of_node) - return -ENODEV; - - hwspin = devm_kzalloc(&pdev->dev, - struct_size(hwspin, bank.lock, - HW_SPINLOCK_NUMBER), - GFP_KERNEL); - if (!hwspin) - return -ENOMEM; - - /* retrieve io base */ - hwspin->io_base = of_iomap(pdev->dev.of_node, 0); - if (!hwspin->io_base) - return -ENOMEM; - - for (idx = 0; idx < HW_SPINLOCK_NUMBER; idx++) { - hwlock = &hwspin->bank.lock[idx]; - hwlock->priv = hwspin->io_base + HW_SPINLOCK_OFFSET(idx); - } - - platform_set_drvdata(pdev, hwspin); - - pm_runtime_enable(&pdev->dev); - - ret = hwspin_lock_register(&hwspin->bank, &pdev->dev, - &sirf_hwspinlock_ops, 0, - HW_SPINLOCK_NUMBER); - if (ret) - goto reg_failed; - - return 0; - -reg_failed: - pm_runtime_disable(&pdev->dev); - iounmap(hwspin->io_base); - - return ret; -} - -static int sirf_hwspinlock_remove(struct platform_device *pdev) -{ - struct sirf_hwspinlock *hwspin = platform_get_drvdata(pdev); - int ret; - - ret = hwspin_lock_unregister(&hwspin->bank); - if (ret) { - dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret); - return ret; - } - - pm_runtime_disable(&pdev->dev); - - iounmap(hwspin->io_base); - - return 0; -} - -static const struct of_device_id sirf_hwpinlock_ids[] = { - { .compatible = "sirf,hwspinlock", }, - {}, -}; -MODULE_DEVICE_TABLE(of, sirf_hwpinlock_ids); - -static struct platform_driver sirf_hwspinlock_driver = { - .probe = sirf_hwspinlock_probe, - .remove = sirf_hwspinlock_remove, - .driver = { - .name = "atlas7_hwspinlock", - .of_match_table = of_match_ptr(sirf_hwpinlock_ids), - }, -}; - -module_platform_driver(sirf_hwspinlock_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("SIRF Hardware spinlock driver"); -MODULE_AUTHOR("Wei Chen <wei.chen@csr.com>"); diff --git a/drivers/hwspinlock/sprd_hwspinlock.c b/drivers/hwspinlock/sprd_hwspinlock.c index dc42bf51f3e6..22e2ffb91743 100644 --- a/drivers/hwspinlock/sprd_hwspinlock.c +++ b/drivers/hwspinlock/sprd_hwspinlock.c @@ -4,7 +4,6 @@ * Copyright (C) 2017 Spreadtrum - http://www.spreadtrum.com */ -#include <linux/bitops.h> #include <linux/clk.h> #include <linux/delay.h> #include <linux/device.h> @@ -15,8 +14,6 @@ #include <linux/of.h> #include <linux/of_device.h> #include <linux/platform_device.h> -#include <linux/pm_runtime.h> -#include <linux/slab.h> #include "hwspinlock_internal.h" @@ -79,25 +76,29 @@ static const struct hwspinlock_ops sprd_hwspinlock_ops = { .relax = sprd_hwspinlock_relax, }; +static void sprd_hwspinlock_disable(void *data) +{ + struct sprd_hwspinlock_dev *sprd_hwlock = data; + + clk_disable_unprepare(sprd_hwlock->clk); +} + static int sprd_hwspinlock_probe(struct platform_device *pdev) { struct sprd_hwspinlock_dev *sprd_hwlock; struct hwspinlock *lock; - struct resource *res; int i, ret; if (!pdev->dev.of_node) return -ENODEV; sprd_hwlock = devm_kzalloc(&pdev->dev, - sizeof(struct sprd_hwspinlock_dev) + - SPRD_HWLOCKS_NUM * sizeof(*lock), + struct_size(sprd_hwlock, bank.lock, SPRD_HWLOCKS_NUM), GFP_KERNEL); if (!sprd_hwlock) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - sprd_hwlock->base = devm_ioremap_resource(&pdev->dev, res); + sprd_hwlock->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(sprd_hwlock->base)) return PTR_ERR(sprd_hwlock->base); @@ -107,7 +108,17 @@ static int sprd_hwspinlock_probe(struct platform_device *pdev) return PTR_ERR(sprd_hwlock->clk); } - clk_prepare_enable(sprd_hwlock->clk); + ret = clk_prepare_enable(sprd_hwlock->clk); + if (ret) + return ret; + + ret = devm_add_action_or_reset(&pdev->dev, sprd_hwspinlock_disable, + sprd_hwlock); + if (ret) { + dev_err(&pdev->dev, + "Failed to add hwspinlock disable action\n"); + return ret; + } /* set the hwspinlock to record user id to identify subsystems */ writel(HWSPINLOCK_USER_BITS, sprd_hwlock->base + HWSPINLOCK_RECCTRL); @@ -118,27 +129,10 @@ static int sprd_hwspinlock_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, sprd_hwlock); - pm_runtime_enable(&pdev->dev); - - ret = hwspin_lock_register(&sprd_hwlock->bank, &pdev->dev, - &sprd_hwspinlock_ops, 0, SPRD_HWLOCKS_NUM); - if (ret) { - pm_runtime_disable(&pdev->dev); - clk_disable_unprepare(sprd_hwlock->clk); - return ret; - } - return 0; -} - -static int sprd_hwspinlock_remove(struct platform_device *pdev) -{ - struct sprd_hwspinlock_dev *sprd_hwlock = platform_get_drvdata(pdev); - - hwspin_lock_unregister(&sprd_hwlock->bank); - pm_runtime_disable(&pdev->dev); - clk_disable_unprepare(sprd_hwlock->clk); - return 0; + return devm_hwspin_lock_register(&pdev->dev, &sprd_hwlock->bank, + &sprd_hwspinlock_ops, 0, + SPRD_HWLOCKS_NUM); } static const struct of_device_id sprd_hwspinlock_of_match[] = { @@ -149,24 +143,12 @@ MODULE_DEVICE_TABLE(of, sprd_hwspinlock_of_match); static struct platform_driver sprd_hwspinlock_driver = { .probe = sprd_hwspinlock_probe, - .remove = sprd_hwspinlock_remove, .driver = { .name = "sprd_hwspinlock", - .of_match_table = of_match_ptr(sprd_hwspinlock_of_match), + .of_match_table = sprd_hwspinlock_of_match, }, }; - -static int __init sprd_hwspinlock_init(void) -{ - return platform_driver_register(&sprd_hwspinlock_driver); -} -postcore_initcall(sprd_hwspinlock_init); - -static void __exit sprd_hwspinlock_exit(void) -{ - platform_driver_unregister(&sprd_hwspinlock_driver); -} -module_exit(sprd_hwspinlock_exit); +module_platform_driver(sprd_hwspinlock_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Hardware spinlock driver for Spreadtrum"); diff --git a/drivers/hwspinlock/stm32_hwspinlock.c b/drivers/hwspinlock/stm32_hwspinlock.c index 441839288893..bb5c7e5f7a80 100644 --- a/drivers/hwspinlock/stm32_hwspinlock.c +++ b/drivers/hwspinlock/stm32_hwspinlock.c @@ -5,6 +5,7 @@ */ #include <linux/clk.h> +#include <linux/delay.h> #include <linux/hwspinlock.h> #include <linux/io.h> #include <linux/kernel.h> @@ -42,60 +43,79 @@ static void stm32_hwspinlock_unlock(struct hwspinlock *lock) writel(STM32_MUTEX_COREID, lock_addr); } +static void stm32_hwspinlock_relax(struct hwspinlock *lock) +{ + ndelay(50); +} + static const struct hwspinlock_ops stm32_hwspinlock_ops = { .trylock = stm32_hwspinlock_trylock, .unlock = stm32_hwspinlock_unlock, + .relax = stm32_hwspinlock_relax, }; +static void stm32_hwspinlock_disable_clk(void *data) +{ + struct platform_device *pdev = data; + struct stm32_hwspinlock *hw = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + pm_runtime_get_sync(dev); + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + pm_runtime_put_noidle(dev); + + clk_disable_unprepare(hw->clk); +} + static int stm32_hwspinlock_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct stm32_hwspinlock *hw; void __iomem *io_base; - struct resource *res; - size_t array_size; int i, ret; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - io_base = devm_ioremap_resource(&pdev->dev, res); + io_base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(io_base)) return PTR_ERR(io_base); - array_size = STM32_MUTEX_NUM_LOCKS * sizeof(struct hwspinlock); - hw = devm_kzalloc(&pdev->dev, sizeof(*hw) + array_size, GFP_KERNEL); + hw = devm_kzalloc(dev, struct_size(hw, bank.lock, STM32_MUTEX_NUM_LOCKS), GFP_KERNEL); if (!hw) return -ENOMEM; - hw->clk = devm_clk_get(&pdev->dev, "hsem"); + hw->clk = devm_clk_get(dev, "hsem"); if (IS_ERR(hw->clk)) return PTR_ERR(hw->clk); - for (i = 0; i < STM32_MUTEX_NUM_LOCKS; i++) - hw->bank.lock[i].priv = io_base + i * sizeof(u32); + ret = clk_prepare_enable(hw->clk); + if (ret) { + dev_err(dev, "Failed to prepare_enable clock\n"); + return ret; + } platform_set_drvdata(pdev, hw); - pm_runtime_enable(&pdev->dev); - ret = hwspin_lock_register(&hw->bank, &pdev->dev, &stm32_hwspinlock_ops, - 0, STM32_MUTEX_NUM_LOCKS); + pm_runtime_get_noresume(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_put(dev); - if (ret) - pm_runtime_disable(&pdev->dev); + ret = devm_add_action_or_reset(dev, stm32_hwspinlock_disable_clk, pdev); + if (ret) { + dev_err(dev, "Failed to register action\n"); + return ret; + } - return ret; -} + for (i = 0; i < STM32_MUTEX_NUM_LOCKS; i++) + hw->bank.lock[i].priv = io_base + i * sizeof(u32); -static int stm32_hwspinlock_remove(struct platform_device *pdev) -{ - struct stm32_hwspinlock *hw = platform_get_drvdata(pdev); - int ret; + ret = devm_hwspin_lock_register(dev, &hw->bank, &stm32_hwspinlock_ops, + 0, STM32_MUTEX_NUM_LOCKS); - ret = hwspin_lock_unregister(&hw->bank); if (ret) - dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret); - - pm_runtime_disable(&pdev->dev); + dev_err(dev, "Failed to register hwspinlock\n"); - return 0; + return ret; } static int __maybe_unused stm32_hwspinlock_runtime_suspend(struct device *dev) @@ -130,7 +150,6 @@ MODULE_DEVICE_TABLE(of, stm32_hwpinlock_ids); static struct platform_driver stm32_hwspinlock_driver = { .probe = stm32_hwspinlock_probe, - .remove = stm32_hwspinlock_remove, .driver = { .name = "stm32_hwspinlock", .of_match_table = stm32_hwpinlock_ids, diff --git a/drivers/hwspinlock/sun6i_hwspinlock.c b/drivers/hwspinlock/sun6i_hwspinlock.c new file mode 100644 index 000000000000..c2d314588046 --- /dev/null +++ b/drivers/hwspinlock/sun6i_hwspinlock.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * sun6i_hwspinlock.c - hardware spinlock driver for sun6i compatible Allwinner SoCs + * Copyright (C) 2020 Wilken Gottwalt <wilken.gottwalt@posteo.net> + */ + +#include <linux/clk.h> +#include <linux/debugfs.h> +#include <linux/errno.h> +#include <linux/hwspinlock.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#include "hwspinlock_internal.h" + +#define DRIVER_NAME "sun6i_hwspinlock" + +#define SPINLOCK_BASE_ID 0 /* there is only one hwspinlock device per SoC */ +#define SPINLOCK_SYSSTATUS_REG 0x0000 +#define SPINLOCK_LOCK_REGN 0x0100 +#define SPINLOCK_NOTTAKEN 0 + +struct sun6i_hwspinlock_data { + struct hwspinlock_device *bank; + struct reset_control *reset; + struct clk *ahb_clk; + struct dentry *debugfs; + int nlocks; +}; + +#ifdef CONFIG_DEBUG_FS + +static int hwlocks_supported_show(struct seq_file *seqf, void *unused) +{ + struct sun6i_hwspinlock_data *priv = seqf->private; + + seq_printf(seqf, "%d\n", priv->nlocks); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(hwlocks_supported); + +static void sun6i_hwspinlock_debugfs_init(struct sun6i_hwspinlock_data *priv) +{ + priv->debugfs = debugfs_create_dir(DRIVER_NAME, NULL); + debugfs_create_file("supported", 0444, priv->debugfs, priv, &hwlocks_supported_fops); +} + +#else + +static void sun6i_hwspinlock_debugfs_init(struct sun6i_hwspinlock_data *priv) +{ +} + +#endif + +static int sun6i_hwspinlock_trylock(struct hwspinlock *lock) +{ + void __iomem *lock_addr = lock->priv; + + return (readl(lock_addr) == SPINLOCK_NOTTAKEN); +} + +static void sun6i_hwspinlock_unlock(struct hwspinlock *lock) +{ + void __iomem *lock_addr = lock->priv; + + writel(SPINLOCK_NOTTAKEN, lock_addr); +} + +static const struct hwspinlock_ops sun6i_hwspinlock_ops = { + .trylock = sun6i_hwspinlock_trylock, + .unlock = sun6i_hwspinlock_unlock, +}; + +static void sun6i_hwspinlock_disable(void *data) +{ + struct sun6i_hwspinlock_data *priv = data; + + debugfs_remove_recursive(priv->debugfs); + clk_disable_unprepare(priv->ahb_clk); + reset_control_assert(priv->reset); +} + +static int sun6i_hwspinlock_probe(struct platform_device *pdev) +{ + struct sun6i_hwspinlock_data *priv; + struct hwspinlock *hwlock; + void __iomem *io_base; + u32 num_banks; + int err, i; + + io_base = devm_platform_ioremap_resource(pdev, SPINLOCK_BASE_ID); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->ahb_clk = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(priv->ahb_clk)) { + err = PTR_ERR(priv->ahb_clk); + dev_err(&pdev->dev, "unable to get AHB clock (%d)\n", err); + return err; + } + + priv->reset = devm_reset_control_get(&pdev->dev, "ahb"); + if (IS_ERR(priv->reset)) + return dev_err_probe(&pdev->dev, PTR_ERR(priv->reset), + "unable to get reset control\n"); + + err = reset_control_deassert(priv->reset); + if (err) { + dev_err(&pdev->dev, "deassert reset control failure (%d)\n", err); + return err; + } + + err = clk_prepare_enable(priv->ahb_clk); + if (err) { + dev_err(&pdev->dev, "unable to prepare AHB clk (%d)\n", err); + goto clk_fail; + } + + /* + * bit 28 and 29 represents the hwspinlock setup + * + * every datasheet (A64, A80, A83T, H3, H5, H6 ...) says the default value is 0x1 and 0x1 + * to 0x4 represent 32, 64, 128 and 256 locks + * but later datasheets (H5, H6) say 00, 01, 10, 11 represent 32, 64, 128 and 256 locks, + * but that would mean H5 and H6 have 64 locks, while their datasheets talk about 32 locks + * all the time, not a single mentioning of 64 locks + * the 0x4 value is also not representable by 2 bits alone, so some datasheets are not + * correct + * one thing have all in common, default value of the sysstatus register is 0x10000000, + * which results in bit 28 being set + * this is the reason 0x1 is considered being 32 locks and bit 30 is taken into account + * verified on H2+ (datasheet 0x1 = 32 locks) and H5 (datasheet 01 = 64 locks) + */ + num_banks = readl(io_base + SPINLOCK_SYSSTATUS_REG) >> 28; + switch (num_banks) { + case 1 ... 4: + priv->nlocks = 1 << (4 + num_banks); + break; + default: + err = -EINVAL; + dev_err(&pdev->dev, "unsupported hwspinlock setup (%d)\n", num_banks); + goto bank_fail; + } + + priv->bank = devm_kzalloc(&pdev->dev, struct_size(priv->bank, lock, priv->nlocks), + GFP_KERNEL); + if (!priv->bank) { + err = -ENOMEM; + goto bank_fail; + } + + for (i = 0; i < priv->nlocks; ++i) { + hwlock = &priv->bank->lock[i]; + hwlock->priv = io_base + SPINLOCK_LOCK_REGN + sizeof(u32) * i; + } + + /* failure of debugfs is considered non-fatal */ + sun6i_hwspinlock_debugfs_init(priv); + if (IS_ERR(priv->debugfs)) + priv->debugfs = NULL; + + err = devm_add_action_or_reset(&pdev->dev, sun6i_hwspinlock_disable, priv); + if (err) { + dev_err(&pdev->dev, "failed to add hwspinlock disable action\n"); + goto bank_fail; + } + + platform_set_drvdata(pdev, priv); + + return devm_hwspin_lock_register(&pdev->dev, priv->bank, &sun6i_hwspinlock_ops, + SPINLOCK_BASE_ID, priv->nlocks); + +bank_fail: + clk_disable_unprepare(priv->ahb_clk); +clk_fail: + reset_control_assert(priv->reset); + + return err; +} + +static const struct of_device_id sun6i_hwspinlock_ids[] = { + { .compatible = "allwinner,sun6i-a31-hwspinlock", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sun6i_hwspinlock_ids); + +static struct platform_driver sun6i_hwspinlock_driver = { + .probe = sun6i_hwspinlock_probe, + .driver = { + .name = DRIVER_NAME, + .of_match_table = sun6i_hwspinlock_ids, + }, +}; +module_platform_driver(sun6i_hwspinlock_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("SUN6I hardware spinlock driver"); +MODULE_AUTHOR("Wilken Gottwalt <wilken.gottwalt@posteo.net>"); diff --git a/drivers/hwspinlock/u8500_hsem.c b/drivers/hwspinlock/u8500_hsem.c index 572ca79d77e8..5a2d8c3e0d80 100644 --- a/drivers/hwspinlock/u8500_hsem.c +++ b/drivers/hwspinlock/u8500_hsem.c @@ -16,7 +16,6 @@ #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> @@ -88,21 +87,16 @@ 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; + int i, 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; + io_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); /* make sure protocol 1 is selected */ val = readl(io_base + HSEM_CTRL_REG); @@ -111,55 +105,28 @@ static int u8500_hsem_probe(struct platform_device *pdev) /* clear all interrupts */ writel(0xFFFF, io_base + HSEM_ICRALL); - bank = kzalloc(struct_size(bank, lock, num_locks), GFP_KERNEL); - if (!bank) { - ret = -ENOMEM; - goto iounmap_base; - } + bank = devm_kzalloc(&pdev->dev, struct_size(bank, lock, num_locks), + GFP_KERNEL); + if (!bank) + return -ENOMEM; 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; + return devm_hwspin_lock_register(&pdev->dev, bank, + &u8500_hwspinlock_ops, + pdata->base_id, num_locks); } -static int u8500_hsem_remove(struct platform_device *pdev) +static void 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 = { |
