Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
# SPDX-License-Identifier: GPL-2.0
#
# Generic HWSPINLOCK framework
#
menuconfig HWSPINLOCK
bool "Hardware Spinlock drivers"
if HWSPINLOCK
config HWSPINLOCK_OMAP
tristate "OMAP Hardware Spinlock device"
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).
If unsure, say N.
config HWSPINLOCK_QCOM
tristate "Qualcomm Hardware Spinlock device"
depends on ARCH_QCOM || COMPILE_TEST
select MFD_SYSCON
help
Say y here to support the Qualcomm Hardware Mutex functionality, which
provides a synchronisation mechanism for the various processors on
the SoC.
If unsure, say N.
config HWSPINLOCK_SPRD
tristate "SPRD Hardware Spinlock device"
depends on ARCH_SPRD || COMPILE_TEST
help
Say y here to support the SPRD Hardware Spinlock device.
If unsure, say N.
config HWSPINLOCK_STM32
tristate "STM32 Hardware Spinlock device"
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.
endif # HWSPINLOCK
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
#
# Generic Hardware Spinlock framework
#
obj-$(CONFIG_HWSPINLOCK) += hwspinlock_core.o
obj-$(CONFIG_HWSPINLOCK_OMAP) += omap_hwspinlock.o
obj-$(CONFIG_HWSPINLOCK_QCOM) += qcom_hwspinlock.o
obj-$(CONFIG_HWSPINLOCK_SPRD) += sprd_hwspinlock.o
obj-$(CONFIG_HWSPINLOCK_STM32) += stm32_hwspinlock.o
obj-$(CONFIG_HWSPINLOCK_SUN6I) += sun6i_hwspinlock.o
@@ -0,0 +1,892 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Hardware spinlock framework
*
* Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com
*
* Contact: Ohad Ben-Cohen <ohad@wizery.com>
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/delay.h>
#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 <linux/of.h>
#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 */
/*
* 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.
*
* Caution: If the mode is HWLOCK_RAW, that means user must protect the routine
* of getting hardware lock with mutex or spinlock. Since in some scenarios,
* 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 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_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;
if (WARN_ON(!hwlock || (!flags && mode == HWLOCK_IRQSTATE)))
return -EINVAL;
/*
* 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.)
*/
switch (mode) {
case HWLOCK_IRQSTATE:
ret = spin_trylock_irqsave(&hwlock->lock, *flags);
break;
case HWLOCK_IRQ:
ret = spin_trylock_irq(&hwlock->lock);
break;
case HWLOCK_RAW:
case HWLOCK_IN_ATOMIC:
ret = 1;
break;
default:
ret = spin_trylock(&hwlock->lock);
break;
}
/* 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) {
switch (mode) {
case HWLOCK_IRQSTATE:
spin_unlock_irqrestore(&hwlock->lock, *flags);
break;
case HWLOCK_IRQ:
spin_unlock_irq(&hwlock->lock);
break;
case HWLOCK_RAW:
case HWLOCK_IN_ATOMIC:
/* Nothing to do */
break;
default:
spin_unlock(&hwlock->lock);
break;
}
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
* @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)
*
* 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.
*
* Caution: If the mode is HWLOCK_RAW, that means user must protect the routine
* of getting hardware lock with mutex or spinlock. Since in some scenarios,
* 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 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
* 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, atomic_delay = 0;
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 (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
* 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)
{
if (WARN_ON(!hwlock || (!flags && mode == HWLOCK_IRQSTATE)))
return;
/*
* 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 */
switch (mode) {
case HWLOCK_IRQSTATE:
spin_unlock_irqrestore(&hwlock->lock, *flags);
break;
case HWLOCK_IRQ:
spin_unlock_irq(&hwlock->lock);
break;
case HWLOCK_RAW:
case HWLOCK_IN_ATOMIC:
/* Nothing to do */
break;
default:
spin_unlock(&hwlock->lock);
break;
}
}
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
* @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,
* or -EINVAL on invalid specifier cell count.
*/
static inline int
of_hwspin_lock_simple_xlate(const struct of_phandle_args *hwlock_spec)
{
if (WARN_ON(hwlock_spec->args_count != 1))
return -EINVAL;
return hwlock_spec->args[0];
}
/**
* of_hwspin_lock_get_id() - get lock id for an OF phandle-based specific lock
* @np: device node from which to request the specific hwlock
* @index: index of the hwlock in the list of values
*
* This function provides a means for DT users of the hwspinlock module to
* get the global lock id of a specific hwspinlock using the phandle of 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.
*/
int of_hwspin_lock_get_id(struct device_node *np, int index)
{
struct of_phandle_args args;
struct hwspinlock *hwlock;
struct radix_tree_iter iter;
void **slot;
int id;
int ret;
ret = of_parse_phandle_with_args(np, "hwlocks", "#hwlock-cells", index,
&args);
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();
radix_tree_for_each_slot(slot, &hwspinlock_tree, &iter, 0) {
hwlock = radix_tree_deref_slot(slot);
if (unlikely(!hwlock))
continue;
if (radix_tree_deref_retry(hwlock)) {
slot = radix_tree_iter_retry(&iter);
continue;
}
if (device_match_of_node(hwlock->bank->dev, args.np)) {
ret = 0;
break;
}
}
rcu_read_unlock();
if (ret < 0)
goto out;
id = of_hwspin_lock_simple_xlate(&args);
if (id < 0 || id >= hwlock->bank->num_locks) {
ret = -EINVAL;
goto out;
}
id += hwlock->bank->base_id;
out:
of_node_put(args.np);
return ret ? ret : id;
}
EXPORT_SYMBOL_GPL(of_hwspin_lock_get_id);
/**
* of_hwspin_lock_get_id_byname() - get lock id for an specified hwlock name
* @np: device node from which to request the specific hwlock
* @name: hwlock name
*
* This function provides a means for DT users of the hwspinlock module to
* get the global lock id of a specific hwspinlock using the specified name of
* 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.
*/
int of_hwspin_lock_get_id_byname(struct device_node *np, const char *name)
{
int index;
if (!name)
return -EINVAL;
index = of_property_match_string(np, "hwlock-names", name);
if (index < 0)
return index;
return of_hwspin_lock_get_id(np, index);
}
EXPORT_SYMBOL_GPL(of_hwspin_lock_get_id_byname);
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);
static void devm_hwspin_lock_unreg(struct device *dev, void *res)
{
hwspin_lock_unregister(*(struct hwspinlock_device **)res);
}
static int devm_hwspin_lock_device_match(struct device *dev, void *res,
void *data)
{
struct hwspinlock_device **bank = res;
if (WARN_ON(!bank || !*bank))
return 0;
return *bank == data;
}
/**
* devm_hwspin_lock_unregister() - unregister an hw spinlock device for
* a managed device
* @dev: the backing 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 devm_hwspin_lock_unregister(struct device *dev,
struct hwspinlock_device *bank)
{
int ret;
ret = devres_release(dev, devm_hwspin_lock_unreg,
devm_hwspin_lock_device_match, bank);
WARN_ON(ret);
return ret;
}
EXPORT_SYMBOL_GPL(devm_hwspin_lock_unregister);
/**
* devm_hwspin_lock_register() - register a new hw spinlock device for
* a managed device
* @dev: the backing device
* @bank: the hwspinlock device, which usually provides numerous hw locks
* @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 devm_hwspin_lock_register(struct device *dev,
struct hwspinlock_device *bank,
const struct hwspinlock_ops *ops,
int base_id, int num_locks)
{
struct hwspinlock_device **ptr;
int ret;
ptr = devres_alloc(devm_hwspin_lock_unreg, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;
ret = hwspin_lock_register(bank, dev, ops, base_id, num_locks);
if (!ret) {
*ptr = bank;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return ret;
}
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
* 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 && 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);
/* self-sanity check that should never fail */
WARN_ON(tmp != hwlock);
return ret;
}
/**
* 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 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) {
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 */
pm_runtime_put(dev);
/* 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);
static int devm_hwspin_lock_match(struct device *dev, void *res, void *data)
{
struct hwspinlock **hwlock = res;
if (WARN_ON(!hwlock || !*hwlock))
return 0;
return *hwlock == data;
}
static void devm_hwspin_lock_release(struct device *dev, void *res)
{
hwspin_lock_free(*(struct hwspinlock **)res);
}
/**
* devm_hwspin_lock_free() - free a specific hwspinlock for a managed device
* @dev: the device to free the 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 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 devm_hwspin_lock_free(struct device *dev, struct hwspinlock *hwlock)
{
int ret;
ret = devres_release(dev, devm_hwspin_lock_release,
devm_hwspin_lock_match, hwlock);
WARN_ON(ret);
return ret;
}
EXPORT_SYMBOL_GPL(devm_hwspin_lock_free);
/**
* devm_hwspin_lock_request_specific() - request for a specific hwspinlock for
* a managed device
* @dev: the device to request the 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 *devm_hwspin_lock_request_specific(struct device *dev,
unsigned int id)
{
struct hwspinlock **ptr, *hwlock;
ptr = devres_alloc(devm_hwspin_lock_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return NULL;
hwlock = hwspin_lock_request_specific(id);
if (hwlock) {
*ptr = hwlock;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return hwlock;
}
EXPORT_SYMBOL_GPL(devm_hwspin_lock_request_specific);
MODULE_DESCRIPTION("Hardware spinlock interface");
MODULE_AUTHOR("Ohad Ben-Cohen <ohad@wizery.com>");
@@ -0,0 +1,72 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Hardware spinlocks internal header
*
* Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com
*
* Contact: Ohad Ben-Cohen <ohad@wizery.com>
*/
#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.
* @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.
*/
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);
};
/**
* 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[];
};
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 */
@@ -0,0 +1,159 @@
// SPDX-License-Identifier: GPL-2.0
/*
* OMAP hardware spinlock driver
*
* 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>
#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/of.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_device *bank;
void __iomem *io_base;
int num_locks, i, ret;
/* Only a single hwspinlock block device is supported */
int base_id = 0;
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
*/
ret = devm_pm_runtime_enable(&pdev->dev);
if (ret)
return ret;
ret = pm_runtime_resume_and_get(&pdev->dev);
if (ret < 0)
return ret;
/* Determine number of locks */
i = readl(io_base + SYSSTATUS_OFFSET);
i >>= SPINLOCK_NUMLOCKS_BIT_OFFSET;
/*
* runtime PM will make sure the clock of this module is
* enabled again iff at least one lock is requested
*/
pm_runtime_put(&pdev->dev);
/* one of the four lsb's must be set, and nothing else */
if (hweight_long(i & 0xf) != 1 || i > 8)
return -EINVAL;
num_locks = i * 32; /* actual number of locks in this device */
bank = devm_kzalloc(&pdev->dev, struct_size(bank, lock, num_locks),
GFP_KERNEL);
if (!bank)
return -ENOMEM;
for (i = 0; i < num_locks; i++)
bank->lock[i].priv = io_base + LOCK_BASE_OFFSET + sizeof(u32) * i;
return devm_hwspin_lock_register(&pdev->dev, bank, &omap_hwspinlock_ops,
base_id, num_locks);
}
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,
.driver = {
.name = "omap_hwspinlock",
.of_match_table = omap_hwspinlock_of_match,
},
};
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>");
@@ -0,0 +1,260 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2013, The Linux Foundation. All rights reserved.
* Copyright (c) 2015, Sony Mobile Communications AB
*/
#include <linux/hwspinlock.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include "hwspinlock_internal.h"
#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;
u32 lock_owner;
int ret;
ret = regmap_field_write(field, QCOM_MUTEX_APPS_PROC_ID);
if (ret)
return ret;
ret = regmap_field_read(field, &lock_owner);
if (ret)
return ret;
return lock_owner == QCOM_MUTEX_APPS_PROC_ID;
}
static void qcom_hwspinlock_unlock(struct hwspinlock *lock)
{
struct regmap_field *field = lock->priv;
u32 lock_owner;
int ret;
ret = regmap_field_read(field, &lock_owner);
if (ret) {
pr_err("%s: unable to query spinlock owner\n", __func__);
return;
}
if (lock_owner != QCOM_MUTEX_APPS_PROC_ID) {
pr_err("%s: spinlock not owned by us (actual owner is %d)\n",
__func__, lock_owner);
}
ret = regmap_field_write(field, 0);
if (ret)
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", .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 struct regmap *qcom_hwspinlock_probe_syscon(struct platform_device *pdev,
u32 *base, u32 *stride)
{
struct device_node *syscon;
struct regmap *regmap;
int ret;
syscon = of_parse_phandle(pdev->dev.of_node, "syscon", 0);
if (!syscon)
return ERR_PTR(-ENODEV);
regmap = syscon_node_to_regmap(syscon);
of_node_put(syscon);
if (IS_ERR(regmap))
return regmap;
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 ERR_PTR(-EINVAL);
}
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 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 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)
return -ENOMEM;
platform_set_drvdata(pdev, bank);
for (i = 0; i < QCOM_MUTEX_NUM_LOCKS; i++) {
struct reg_field field = REG_FIELD(base + i * stride, 0, 31);
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);
}
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,
.driver = {
.name = "qcom_hwspinlock",
.of_match_table = qcom_hwspinlock_of_match,
},
};
static int __init qcom_hwspinlock_init(void)
{
return platform_driver_register(&qcom_hwspinlock_driver);
}
/* board init code might need to reserve hwspinlocks for predefined purposes */
postcore_initcall(qcom_hwspinlock_init);
static void __exit qcom_hwspinlock_exit(void)
{
platform_driver_unregister(&qcom_hwspinlock_driver);
}
module_exit(qcom_hwspinlock_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Hardware spinlock driver for Qualcomm SoCs");
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Spreadtrum hardware spinlock driver
* Copyright (C) 2017 Spreadtrum - http://www.spreadtrum.com
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/hwspinlock.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include "hwspinlock_internal.h"
/* hwspinlock registers definition */
#define HWSPINLOCK_RECCTRL 0x4
#define HWSPINLOCK_MASTERID(_X_) (0x80 + 0x4 * (_X_))
#define HWSPINLOCK_TOKEN(_X_) (0x800 + 0x4 * (_X_))
/* unlocked value */
#define HWSPINLOCK_NOTTAKEN 0x55aa10c5
/* bits definition of RECCTRL reg */
#define HWSPINLOCK_USER_BITS 0x1
/* hwspinlock number */
#define SPRD_HWLOCKS_NUM 32
struct sprd_hwspinlock_dev {
void __iomem *base;
struct clk *clk;
struct hwspinlock_device bank;
};
/* try to lock the hardware spinlock */
static int sprd_hwspinlock_trylock(struct hwspinlock *lock)
{
struct sprd_hwspinlock_dev *sprd_hwlock =
dev_get_drvdata(lock->bank->dev);
void __iomem *addr = lock->priv;
int user_id, lock_id;
if (!readl(addr))
return 1;
lock_id = hwlock_to_id(lock);
/* get the hardware spinlock master/user id */
user_id = readl(sprd_hwlock->base + HWSPINLOCK_MASTERID(lock_id));
dev_warn(sprd_hwlock->bank.dev,
"hwspinlock [%d] lock failed and master/user id = %d!\n",
lock_id, user_id);
return 0;
}
/* unlock the hardware spinlock */
static void sprd_hwspinlock_unlock(struct hwspinlock *lock)
{
void __iomem *lock_addr = lock->priv;
writel(HWSPINLOCK_NOTTAKEN, lock_addr);
}
/* The specs recommended below number as the retry delay time */
static void sprd_hwspinlock_relax(struct hwspinlock *lock)
{
ndelay(10);
}
static const struct hwspinlock_ops sprd_hwspinlock_ops = {
.trylock = sprd_hwspinlock_trylock,
.unlock = sprd_hwspinlock_unlock,
.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;
int i, ret;
if (!pdev->dev.of_node)
return -ENODEV;
sprd_hwlock = devm_kzalloc(&pdev->dev,
struct_size(sprd_hwlock, bank.lock, SPRD_HWLOCKS_NUM),
GFP_KERNEL);
if (!sprd_hwlock)
return -ENOMEM;
sprd_hwlock->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(sprd_hwlock->base))
return PTR_ERR(sprd_hwlock->base);
sprd_hwlock->clk = devm_clk_get(&pdev->dev, "enable");
if (IS_ERR(sprd_hwlock->clk)) {
dev_err(&pdev->dev, "get hwspinlock clock failed!\n");
return PTR_ERR(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);
for (i = 0; i < SPRD_HWLOCKS_NUM; i++) {
lock = &sprd_hwlock->bank.lock[i];
lock->priv = sprd_hwlock->base + HWSPINLOCK_TOKEN(i);
}
platform_set_drvdata(pdev, sprd_hwlock);
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[] = {
{ .compatible = "sprd,hwspinlock-r3p0", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sprd_hwspinlock_of_match);
static struct platform_driver sprd_hwspinlock_driver = {
.probe = sprd_hwspinlock_probe,
.driver = {
.name = "sprd_hwspinlock",
.of_match_table = sprd_hwspinlock_of_match,
},
};
module_platform_driver(sprd_hwspinlock_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Hardware spinlock driver for Spreadtrum");
MODULE_AUTHOR("Baolin Wang <baolin.wang@spreadtrum.com>");
MODULE_AUTHOR("Lanqing Liu <lanqing.liu@spreadtrum.com>");
MODULE_AUTHOR("Long Cheng <aiden.cheng@spreadtrum.com>");
@@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) STMicroelectronics SA 2018
* Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/hwspinlock.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include "hwspinlock_internal.h"
#define STM32_MUTEX_COREID BIT(8)
#define STM32_MUTEX_LOCK_BIT BIT(31)
#define STM32_MUTEX_NUM_LOCKS 32
struct stm32_hwspinlock {
struct clk *clk;
struct hwspinlock_device bank;
};
static int stm32_hwspinlock_trylock(struct hwspinlock *lock)
{
void __iomem *lock_addr = lock->priv;
u32 status;
writel(STM32_MUTEX_LOCK_BIT | STM32_MUTEX_COREID, lock_addr);
status = readl(lock_addr);
return status == (STM32_MUTEX_LOCK_BIT | STM32_MUTEX_COREID);
}
static void stm32_hwspinlock_unlock(struct hwspinlock *lock)
{
void __iomem *lock_addr = lock->priv;
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;
int i, ret;
io_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(io_base))
return PTR_ERR(io_base);
hw = devm_kzalloc(dev, struct_size(hw, bank.lock, STM32_MUTEX_NUM_LOCKS), GFP_KERNEL);
if (!hw)
return -ENOMEM;
hw->clk = devm_clk_get(dev, "hsem");
if (IS_ERR(hw->clk))
return PTR_ERR(hw->clk);
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_get_noresume(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_put(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;
}
for (i = 0; i < STM32_MUTEX_NUM_LOCKS; i++)
hw->bank.lock[i].priv = io_base + i * sizeof(u32);
ret = devm_hwspin_lock_register(dev, &hw->bank, &stm32_hwspinlock_ops,
0, STM32_MUTEX_NUM_LOCKS);
if (ret)
dev_err(dev, "Failed to register hwspinlock\n");
return ret;
}
static int __maybe_unused stm32_hwspinlock_runtime_suspend(struct device *dev)
{
struct stm32_hwspinlock *hw = dev_get_drvdata(dev);
clk_disable_unprepare(hw->clk);
return 0;
}
static int __maybe_unused stm32_hwspinlock_runtime_resume(struct device *dev)
{
struct stm32_hwspinlock *hw = dev_get_drvdata(dev);
clk_prepare_enable(hw->clk);
return 0;
}
static const struct dev_pm_ops stm32_hwspinlock_pm_ops = {
SET_RUNTIME_PM_OPS(stm32_hwspinlock_runtime_suspend,
stm32_hwspinlock_runtime_resume,
NULL)
};
static const struct of_device_id stm32_hwpinlock_ids[] = {
{ .compatible = "st,stm32-hwspinlock", },
{},
};
MODULE_DEVICE_TABLE(of, stm32_hwpinlock_ids);
static struct platform_driver stm32_hwspinlock_driver = {
.probe = stm32_hwspinlock_probe,
.driver = {
.name = "stm32_hwspinlock",
.of_match_table = stm32_hwpinlock_ids,
.pm = &stm32_hwspinlock_pm_ops,
},
};
static int __init stm32_hwspinlock_init(void)
{
return platform_driver_register(&stm32_hwspinlock_driver);
}
/* board init code might need to reserve hwspinlocks for predefined purposes */
postcore_initcall(stm32_hwspinlock_init);
static void __exit stm32_hwspinlock_exit(void)
{
platform_driver_unregister(&stm32_hwspinlock_driver);
}
module_exit(stm32_hwspinlock_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Hardware spinlock driver for STM32 SoCs");
MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>");
@@ -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>");