lkml.org 
[lkml]   [2010]   [Oct]   [18]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 1/3] drivers: misc: add omap_hwspinlock driver
    Date
    From: Simon Que <sque@ti.com>

    Add driver for OMAP's Hardware Spinlock module.

    The OMAP Hardware Spinlock module, initially introduced in OMAP4,
    provides hardware assistance for synchronization between the
    multiple processors in the device (Cortex-A9, Cortex-M3 and
    C64x+ DSP).

    Signed-off-by: Simon Que <sque@ti.com>
    Signed-off-by: Hari Kanigeri <h-kanigeri2@ti.com>
    Signed-off-by: Krishnamoorthy, Balaji T <balajitk@ti.com>
    [ohad@wizery.com: disable interrupts/preemption to prevent hw abuse]
    [ohad@wizery.com: add memory barriers to prevent memory reordering issues]
    [ohad@wizery.com: relax omap interconnect between subsequent lock attempts]
    [ohad@wizery.com: timeout param to use jiffies instead of number of attempts]
    [ohad@wizery.com: remove code duplication in lock, trylock, lock_timeout]
    [ohad@wizery.com: runtime pm usage count to reflect num of requested locks]
    [ohad@wizery.com: move to drivers/misc, general cleanups, document]
    Signed-off-by: Ohad Ben-Cohen <ohad@wizery.com>
    Cc: Benoit Cousson <b-cousson@ti.com>
    Cc: Tony Lindgren <tony@atomide.com>
    ---
    Documentation/misc-devices/omap_hwspinlock.txt | 199 +++++++++
    drivers/misc/Kconfig | 10 +
    drivers/misc/Makefile | 1 +
    drivers/misc/omap_hwspinlock.c | 555 ++++++++++++++++++++++++
    include/linux/omap_hwspinlock.h | 108 +++++
    5 files changed, 873 insertions(+), 0 deletions(-)
    create mode 100644 Documentation/misc-devices/omap_hwspinlock.txt
    create mode 100644 drivers/misc/omap_hwspinlock.c
    create mode 100644 include/linux/omap_hwspinlock.h

    diff --git a/Documentation/misc-devices/omap_hwspinlock.txt b/Documentation/misc-devices/omap_hwspinlock.txt
    new file mode 100644
    index 0000000..b093347
    --- /dev/null
    +++ b/Documentation/misc-devices/omap_hwspinlock.txt
    @@ -0,0 +1,199 @@
    +OMAP Hardware Spinlocks
    +
    +1. Introduction
    +
    +Hardware spinlock modules provide hardware assistance for synchronization
    +and mutual exclusion between heterogeneous processors and those not operating
    +under a single, shared operating system.
    +
    +For example, OMAP4 has dual Cortex-A9, dual Cortex-M3 and a C64x+ DSP,
    +each of which is running a different Operating System (the master, A9,
    +is usually running Linux and the slave processors, the M3 and the DSP,
    +are running some flavor of RTOS).
    +
    +A hwspinlock driver allows kernel code to access data structures (or hardware
    +resources) that are shared with any of the existing remote processors, with
    +which there is no alternative mechanism to accomplish synchronization and
    +mutual exclusion operations.
    +
    +This is necessary, for example, for Inter-processor communications:
    +on OMAP4, cpu-intensive multimedia tasks are offloaded by the host to the
    +remote M3 and/or C64x+ slave processors (by an IPC subsystem called Syslink).
    +
    +To achieve fast message-based communications, a minimal kernel support
    +is needed to deliver messages arriving from a remote processor to the
    +appropriate user process.
    +
    +This communication is based on simple data structures that are shared between
    +the remote processors, and access to them is synchronized using the hwspinlock
    +module (remote processor directly places new messages in this shared data
    +structure).
    +
    +2. User API
    +
    + struct omap_hwspinlock *omap_hwspinlock_request(void);
    + - dynamically assign an hwspinlock and return its address, or
    + ERR_PTR(-EBUSY) if an unused hwspinlock isn't available. Users of this
    + API will usually want to communicate the lock's id to the remote core
    + before it can be used to achieve synchronization (to get the id of the
    + lock, use omap_hwspinlock_get_id()).
    + Can be called from an atomic context (this function will not sleep) but
    + not from within interrupt context.
    +
    + struct omap_hwspinlock *omap_hwspinlock_request_specific(unsigned int id);
    + - assign a specific hwspinlock id and return its address, or
    + ERR_PTR(-EBUSY) if that hwspinlock is already in use. Usually board code
    + will be calling this function in order to reserve specific hwspinlock
    + ids for predefined purposes.
    + Can be called from an atomic context (this function will not sleep) but
    + not from within interrupt context.
    +
    + int omap_hwspinlock_free(struct omap_hwspinlock *hwlock);
    + - free a previously-assigned hwspinlock; returns 0 on success, or an
    + appropriate error code on failure (e.g. -EINVAL if the hwspinlock
    + was not assigned).
    + Can be called from an atomic context (this function will not sleep) but
    + not from within interrupt context.
    +
    + int omap_hwspin_lock(struct omap_hwspinlock *hwlock, unsigned long *flags);
    + - lock a previously assigned hwspinlock. If the hwspinlock is already
    + taken, the function will busy loop waiting for it to be released.
    + Note: if a faulty remote core never releases this lock, this function
    + will deadlock.
    + This function will fail if hwlock is invalid, but otherwise it will
    + always succeed (or deadlock; see above) and will never sleep. It is safe
    + to call it from any context.
    + Upon a successful return from this function, interrupts and preemption
    + are disabled so the caller must not sleep, and is advised to release the
    + hwspinlock as soon as possible, in order to minimize remote cores polling
    + on the hardware interconnect.
    + The flags parameter is a pointer to where the interrupts state of the
    + caller will be saved at.
    +
    + int omap_hwspin_lock_timeout(struct omap_hwspinlock *hwlock,
    + unsigned long timeout, unsigned long *flags);
    + - lock a previously-assigned hwspinlock with a timeout limit (specified in
    + jiffies). If the hwspinlock is already taken, the function will busy loop
    + waiting for it to be released, but give up when the timeout meets jiffies.
    + If timeout is 0, the function will never give up (therefore if a faulty
    + remote core never releases the hwspinlock, it will deadlock).
    + Upon a successful return from this function, interrupts and preemption
    + are disabled so the caller must not sleep, and is advised to release the
    + hwspinlock as soon as possible, in order to minimize remote cores polling
    + on the hardware interconnect.
    + This function can be called from any context (it will never sleep). It
    + returns 0 when successful and an appropriate error code otherwise (most
    + notably -ETIMEDOUT if the hwspinlock is still busy after timeout meets
    + jiffies).
    + The flags parameter is a pointer to where the interrupts state of the
    + caller will be saved at.
    +
    + int
    + omap_hwspin_trylock(struct omap_hwspinlock *hwlock, unsigned long *flags);
    + - attempt to lock a previously-assigned hwspinlock, but immediately fail if
    + it is already taken.
    + Upon a successful return from this function, interrupts and preemption
    + are disabled so the caller must not sleep, and is advised to release the
    + hwspinlock as soon as possible, in order to minimize remote cores polling
    + on the hardware interconnect.
    + This function can be called from any context (it will never sleep). It
    + returns 0 when successful and an appropriate error code otherwise (most
    + notably -EBUSY if the hwspinlock was already taken when it was called).
    + The flags parameter is a pointer to where the interrupts state of the
    + caller will be saved at.
    +
    + int
    + omap_hwspin_unlock(struct omap_hwspinlock *hwlock, unsigned long *flags);
    + - unlock a previously-locked hwspinlock. Always succeed, and can be called
    + from any context (the function never sleeps). Note: code should _never_
    + unlock an hwspinlock which is already unlocked (there is no protection
    + against this).
    + Upon a successful return from this function, the interrupts state of the
    + caller is restored and preemption is reenabled.
    + This function can be called from any context (it will never sleep). It
    + returns 0 when successful and -EINVAL if hwlock is invalid.
    + The flags parameter points to the caller saved interrupts state.
    +
    + int omap_hwspinlock_get_id(struct omap_hwspinlock *hwlock);
    + - returns the id of hwlock, or -EINVAL if hwlock is invalid.
    +
    +3. Typical usage
    +
    +#include <linux/omap_hwspinlock.h>
    +#include <linux/err.h>
    +
    +int hwspinlock_example1(void)
    +{
    + struct omap_hwspinlock *lock;
    + unsigned long flags;
    + int ret;
    +
    + /* dynamically assign a hwspinlock */
    + lock = omap_hwspinlock_request();
    + if (IS_ERR(lock))
    + ... handle error ...
    +
    + /*
    + * probably need to communicate the id of the hwspinlock to the
    + * remote processor which we intend to achieve synchronization
    + * with. The id of the lock can be obtained by calling
    + * omap_hwspinlock_get_id(lock)
    + */
    +
    + /* take the lock, spin if it's already taken */
    + ret = omap_hwspin_lock(hwlock, &flags);
    + if (ret)
    + ... handle error ...
    +
    + /*
    + * we took the lock, do our thing asap, and do NOT sleep
    + */
    +
    + /* release the lock */
    + ret = omap_hwspin_unlock(hwlock, &flags);
    + if (ret)
    + ... handle error ...
    +
    + /* free the lock */
    + ret = omap_hwspinlock_free(hwlock);
    + if (ret)
    + ... handle error ...
    +
    + return ret;
    +}
    +
    +int hwspinlock_example2(void)
    +{
    + struct omap_hwspinlock *lock;
    + unsigned long flags;
    + int ret;
    +
    + /*
    + * request a specific hwspinlock id - this should be called by early
    + * board init code.
    + */
    + lock = omap_hwspinlock_request_specific(PREDEFINED_LOCK_ID);
    + if (IS_ERR(lock))
    + ... handle error ...
    +
    + /* try to take it, but don't spin on it */
    + ret = omap_hwspin_trylock(hwlock);
    + if (!ret)
    + ... handle error ... /* lock is busy */
    +
    + /*
    + * we took the lock, do our thing asap, but do NOT sleep
    + */
    +
    + /* release the lock */
    + ret = omap_hwspin_unlock(hwlock, &flags);
    + if (ret)
    + ... handle error ...
    +
    + /* free the lock */
    + ret = omap_hwspinlock_free(hwlock);
    + if (ret)
    + ... handle error ...
    +
    + return ret;
    +}
    diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
    index b743312..ce4b7a6 100644
    --- a/drivers/misc/Kconfig
    +++ b/drivers/misc/Kconfig
    @@ -390,6 +390,16 @@ config BMP085
    To compile this driver as a module, choose M here: the
    module will be called bmp085.

    +config OMAP_HWSPINLOCK
    + bool "OMAP Hardware Spinlock module"
    + help
    + Say Y here to enable OMAP's hardware spinlock module (first
    + introduced in OMAP4). This module is needed to achieve
    + synchronization and mutual exclusion between the several
    + remote processors on the system.
    +
    + If unsure, say N.
    +
    source "drivers/misc/c2port/Kconfig"
    source "drivers/misc/eeprom/Kconfig"
    source "drivers/misc/cb710/Kconfig"
    diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
    index 42eab95..a4427ad 100644
    --- a/drivers/misc/Makefile
    +++ b/drivers/misc/Makefile
    @@ -35,3 +35,4 @@ obj-y += eeprom/
    obj-y += cb710/
    obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o
    obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
    +obj-$(CONFIG_OMAP_HWSPINLOCK) += omap_hwspinlock.o
    diff --git a/drivers/misc/omap_hwspinlock.c b/drivers/misc/omap_hwspinlock.c
    new file mode 100644
    index 0000000..63dc5a2
    --- /dev/null
    +++ b/drivers/misc/omap_hwspinlock.c
    @@ -0,0 +1,555 @@
    +/*
    + * hardware spinlock driver for OMAP
    + *
    + * Copyright (C) 2010 Texas Instruments, Inc.
    + *
    + * Authors: Simon Que <sque@ti.com>
    + * Hari Kanigeri <h-kanigeri2@ti.com>
    + * Ohad Ben-Cohen <ohad@wizery.com>
    + *
    + * This program is free software; you can redistribute it and/or modify it
    + * under the terms of the GNU General Public License version 2 as published
    + * by the Free Software Foundation.
    + *
    + * This program is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    + * GNU General Public License for more details.
    + */
    +
    +#define pr_fmt(fmt) "%s: " fmt, __func__
    +
    +#include <linux/kernel.h>
    +#include <linux/module.h>
    +#include <linux/spinlock.h>
    +#include <linux/init.h>
    +#include <linux/types.h>
    +#include <linux/err.h>
    +#include <linux/jiffies.h>
    +#include <linux/omap_hwspinlock.h>
    +#include <linux/pm_runtime.h>
    +#include <linux/device.h>
    +#include <linux/list.h>
    +#include <linux/delay.h>
    +#include <linux/io.h>
    +#include <linux/log2.h>
    +#include <linux/slab.h>
    +#include <linux/spinlock.h>
    +#include <linux/platform_device.h>
    +
    +/* Spinlock register offsets */
    +#define SPINLOCK_SYSSTATUS_OFFSET 0x0014
    +#define SPINLOCK_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 */
    +
    +/**
    + * struct omap_hwspinlock - represents a single hardware spinlock
    + *
    + * @node: linked list member
    + * @id: unique and system-wide index number of this lock
    + * @lock: regular spinlock; we have one for every hwspinlock instance
    + * @addr: mapped address of the register which contains this lock's hw state
    + * @dev: underlying device, will be used to invoke runtime PM api
    + */
    +struct omap_hwspinlock {
    + struct list_head node;
    + int id;
    + spinlock_t lock;
    + void __iomem *addr;
    + struct device *dev;
    +};
    +
    +/**
    + * struct omap_hwspinlock_state - represents state of the underlying device
    + *
    + * @io_base: mapped base address of the hwspinlock device
    + * @hwlocks: array of omap_hwspinlocks that belong to this device
    + * @num_locks: number of hwspinlocks provided by this device
    + */
    +struct omap_hwspinlock_state {
    + void __iomem *io_base;
    + struct omap_hwspinlock *hwlocks;
    + int num_locks;
    +};
    +
    +/* List for keeping track of free locks */
    +static LIST_HEAD(free_hwlocks);
    +
    +/* Access to the list of free locks is protected by this spinlock */
    +static DEFINE_SPINLOCK(free_hwlocks_lock);
    +
    +/*
    + * "Multiple hwspinlock devices" is still an imaginary scenario,
    + * so maintaining a global state of our device is just fine for now.
    + */
    +static struct omap_hwspinlock_state hwspinlock_state;
    +
    +/**
    + * omap_hwspin_trylock() - attempt to lock a specific hwspinlock
    + * @hwlock: a hwspinlock which we want to trylock
    + * @flags: a pointer to where the caller's interrupt state will be saved at
    + *
    + * This function attempt to lock the underlying hwspinlock. Unlike
    + * hwspinlock_lock, this function will immediately fail if the hwspinlock
    + * is already taken.
    + *
    + * Upon a successful return from this function, preemption and interrupts
    + * are 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.
    + *
    + * This function can be called from any context.
    + *
    + * Returns 0 if we successfully locked the hwspinlock, -EBUSY if
    + * the hwspinlock was already taken, and -EINVAL if @hwlock is invalid.
    + */
    +int omap_hwspin_trylock(struct omap_hwspinlock *hwlock, unsigned long *flags)
    +{
    + u32 ret;
    +
    + if (IS_ERR_OR_NULL(hwlock)) {
    + pr_err("invalid hwlock\n");
    + return -EINVAL;
    + }
    +
    + /*
    + * This spin_trylock_irqsave serves two purposes:
    + *
    + * 1. Disable local interrupts and preemption, in order to
    + * minimize the period of time in which the hwspinlock
    + * is taken (so caller will not preempted). This is
    + * important in order to minimize the possible polling on
    + * the hardware interconnect by a remote user of this lock.
    + *
    + * 2. Make this hwspinlock primitive SMP-safe (so we can try to
    + * take it from additional contexts on the local cpu)
    + */
    + if (!spin_trylock_irqsave(&hwlock->lock, *flags))
    + return -EBUSY;
    +
    + /* attempt to acquire the lock by reading its value */
    + ret = readl(hwlock->addr);
    +
    + /* lock is already taken */
    + if (ret == SPINLOCK_TAKEN) {
    + spin_unlock_irqrestore(&hwlock->lock, *flags);
    + return -EBUSY;
    + }
    +
    + /*
    + * We can be sure the other core's memory operations
    + * are observable to us only _after_ we successfully take
    + * the hwspinlock, so we must make sure that subsequent memory
    + * operations 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(omap_hwspin_trylock);
    +
    +/**
    + * omap_hwspin_lock_timeout() - lock a specific hwspinlock with timeout limit
    + * @hwlock: the hwspinlock to be locked
    + * @to: timeout, in jiffies
    + * @flags: a pointer to where the caller's interrupts state will be saved at
    + *
    + * This function locks the underlying @hwlock. If the @hwlock
    + * is already taken, the function will busy loop waiting for it to
    + * be released, but give up when @timeout meets jiffies. If @timeout
    + * is 0, the function will never give up (therefore if a
    + * faulty remote core never releases the @hwlock, it will deadlock).
    + *
    + * Upon a successful return from this function, preemption and interrupts
    + * are 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.
    + *
    + * This function can be called from any context.
    + *
    + * Returns 0 when the @hwlock was successfully taken, and an appropriate
    + * error code otherwise (most notably an -ETIMEDOUT if the @hwlock is still
    + * busy after @timeout meets jiffies). The function will never sleep.
    + */
    +int omap_hwspin_lock_timeout(struct omap_hwspinlock *hwlock, unsigned long to,
    + unsigned long *flags)
    +{
    + int ret;
    +
    + for (;;) {
    + /* Try to take the hwspinlock */
    + ret = omap_hwspin_trylock(hwlock, flags);
    + if (ret != -EBUSY)
    + break;
    +
    + /*
    + * The lock is already taken, let's check if the user wants
    + * us to try again
    + */
    + if (to && time_is_before_eq_jiffies(to))
    + return -ETIMEDOUT;
    +
    + /*
    + * Do not hog the omap interconnect.
    + *
    + * It is 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.
    + */
    + ndelay(50);
    + }
    +
    + return ret;
    +}
    +EXPORT_SYMBOL_GPL(omap_hwspin_lock_timeout);
    +
    +/**
    + * omap_hwspinlock_unlock() - unlock a specific hwspinlock
    + * @hwlock: a previously-acquired hwspinlock which we want to unlock
    + * @flags: a pointer to the caller's saved interrupts state
    + *
    + * This function will unlock a specific hwspinlock, enable preemption and
    + * restore the interrupts state. @hwlock must be taken (by us!) before
    + * calling this function: it is a bug to call unlock on a @hwlock that was
    + * not taken by us, i.e. using one of omap_hwspin_{lock trylock, lock_timeout}.
    + *
    + * This function can be called from any context.
    + *
    + * Returns 0 when the @hwlock on success, or -EINVAL if @hwlock is invalid.
    + */
    +int omap_hwspin_unlock(struct omap_hwspinlock *hwlock, unsigned long *flags)
    +{
    + if (IS_ERR_OR_NULL(hwlock)) {
    + pr_err("invalid hwlock\n");
    + return -EINVAL;
    + }
    +
    + /*
    + * We must make sure that memory operations, done before unlocking
    + * the hwspinlock, will not be reordered after the lock is released.
    + * 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
    + * were already observable.
    + */
    + mb();
    +
    + /* release the lock by writing 0 to it (NOTTAKEN) */
    + writel(SPINLOCK_NOTTAKEN, hwlock->addr);
    +
    + /* undo the spin_trylock_irqsave called in the locking function */
    + spin_unlock_irqrestore(&hwlock->lock, *flags);
    +
    + return 0;
    +}
    +EXPORT_SYMBOL_GPL(omap_hwspin_unlock);
    +
    +/**
    + * __omap_hwspinlock_assign() - prepare hwspinlock for assignment
    + * @hwlock: a hwspinlock which we want to assign to the user
    + *
    + * This is an internal function that prepares a specific hwspinlock
    + * for assignment.
    + *
    + * Returns 0 or 1 for success, or an appropriate error code on a failue
    + */
    +static int __omap_hwspinlock_assign(struct omap_hwspinlock *hwlock)
    +{
    + int ret;
    +
    + /* notify the underlying device that power is now needed */
    + ret = pm_runtime_get_sync(hwlock->dev);
    + if (ret < 0) {
    + dev_err(hwlock->dev, "%s: can't power on device\n", __func__);
    + goto out;
    + }
    +
    + /* remove the lock from the list of free locks */
    + list_del(&hwlock->node);
    +
    + /*
    + * mark this hwlock as used; needed in case someone will try to request
    + * this hwlock specifically (using omap_hwspinlock_request_specific)
    + * while it is already being used
    + */
    + INIT_LIST_HEAD(&hwlock->node);
    +
    +out:
    + return ret;
    +}
    +
    +/**
    + * omap_hwspinlock_request() - request a hw spinlock
    + *
    + * This function should be called by users of the hwspinlock module,
    + * 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 to synchronize (to get the id
    + * of a given hwlock, use omap_hwspinlock_get_id()).
    + *
    + * Can be called from an atomic context (will not sleep) but not from
    + * within interrupt context (simply because there is no use case for
    + * that yet).
    + *
    + * Returns the address of the assigned hwspinlock, or an appropriate error
    + * code on a failue (such as ERR_PTR(-EBUSY) if an unused hwspinlock wasn't
    + * found)
    + */
    +struct omap_hwspinlock *omap_hwspinlock_request(void)
    +{
    + struct omap_hwspinlock *hwlock;
    + int ret;
    +
    + spin_lock(&free_hwlocks_lock);
    +
    + if (list_empty(&free_hwlocks)) {
    + pr_warn("a free hwspinlock is not available\n");
    + hwlock = ERR_PTR(-EBUSY);
    + goto out;
    + }
    +
    + hwlock = list_first_entry(&free_hwlocks, struct omap_hwspinlock, node);
    +
    + ret = __omap_hwspinlock_assign(hwlock);
    + if (ret < 0)
    + hwlock = ERR_PTR(ret);
    +
    +out:
    + spin_unlock(&free_hwlocks_lock);
    + return hwlock;
    +}
    +EXPORT_SYMBOL_GPL(omap_hwspinlock_request);
    +
    +/**
    + * omap_hwspinlock_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 board code will be calling this function in order to
    + * reserve specific hwspinlock ids for predefined purposes.
    + *
    + * Can be called from an atomic context (will not sleep) but not from
    + * within interrupt context (simply because there is no use case for
    + * that yet).
    + *
    + * Returns the address of the assigned lock on success, or appropriate
    + * error codes on failures (should be tested with IS_ERR)
    + */
    +struct omap_hwspinlock *omap_hwspinlock_request_specific(unsigned int id)
    +{
    + struct omap_hwspinlock *hwlock;
    + int ret;
    +
    + if (id >= hwspinlock_state.num_locks) {
    + pr_warn("invalid id requested\n");
    + return ERR_PTR(-EINVAL);
    + }
    +
    + spin_lock(&free_hwlocks_lock);
    +
    + hwlock = &hwspinlock_state.hwlocks[id];
    + if (list_empty(&hwlock->node)) {
    + pr_warn("hwspinlock %d was already assigned\n", id);
    + hwlock = ERR_PTR(-EBUSY);
    + goto out;
    + }
    +
    + ret = __omap_hwspinlock_assign(hwlock);
    + if (ret < 0)
    + hwlock = ERR_PTR(ret);
    +
    +out:
    + spin_unlock(&free_hwlocks_lock);
    + return hwlock;
    +}
    +EXPORT_SYMBOL_GPL(omap_hwspinlock_request_specific);
    +
    +/**
    + * omap_hwspinlock_free() - free a specific hwspinlock
    + * @hwlock: the specific hwspinlock to free
    + *
    + * This function returns @hwlock to the list of free hwspinlocks.
    + * Should only be called with an @hwlock that was retrieved from
    + * an earlier call to omap_hwspinlock_request{_specific}.
    + *
    + * Can be called from an atomic context (will not sleep) but not from
    + * within interrupt context (simply because there is no use case for
    + * that yet).
    + *
    + * Returns 0 on success, or an appropriate error code on failure
    + */
    +int omap_hwspinlock_free(struct omap_hwspinlock *hwlock)
    +{
    + int ret;
    +
    + if (IS_ERR_OR_NULL(hwlock)) {
    + pr_err("invalid hwlock\n");
    + return -EINVAL;
    + }
    +
    + spin_lock(&free_hwlocks_lock);
    +
    + /* make sure hwlock is marked as used */
    + if (!list_empty(&hwlock->node)) {
    + pr_err("hwlock doesn't seem to be used\n");
    + ret = -EINVAL;
    + goto out;
    + }
    +
    + list_add(&hwlock->node, &free_hwlocks);
    +
    + /* notify the underlying device that power is not needed */
    + ret = pm_runtime_put(hwlock->dev);
    + if (ret < 0)
    + dev_err(hwlock->dev, "%s: pm_runtime_put failed\n", __func__);
    +
    +out:
    + spin_unlock(&free_hwlocks_lock);
    + return ret;
    +}
    +EXPORT_SYMBOL_GPL(omap_hwspinlock_free);
    +
    +/**
    + * omap_hwspinlock_get_id() - retrieves id number of a given hwspinlock
    + * @hwlock: a valid hwspinlock instance
    + *
    + * Returns id number of a given @hwlock, or -EINVAL if @hwlock is invalid.
    + */
    +int omap_hwspinlock_get_id(struct omap_hwspinlock *hwlock)
    +{
    + if (IS_ERR_OR_NULL(hwlock)) {
    + pr_err("invalid hwlock\n");
    + return -EINVAL;
    + }
    +
    + return hwlock->id;
    +}
    +EXPORT_SYMBOL_GPL(omap_hwspinlock_get_id);
    +
    +static int __devinit omap_hwspinlock_probe(struct platform_device *pdev)
    +{
    + struct omap_hwspinlock *hwlock, *hwlocks;
    + struct resource *res;
    + void __iomem *io_base;
    + int i, ret, num_locks;
    +
    + /* Multiple hwspinlock devices have no meaning, yet */
    + if (hwspinlock_state.hwlocks) {
    + dev_err(&pdev->dev, "unexpected spinlock device\n");
    + return -EBUSY;
    + }
    +
    + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    + if (!res)
    + return -ENODEV;
    +
    + io_base = ioremap(res->start, resource_size(res));
    + if (!io_base)
    + return -ENOMEM;
    +
    + /* Determine number of locks */
    + i = readl(io_base + SPINLOCK_SYSSTATUS_OFFSET);
    + i >>= SPINLOCK_NUMLOCKS_BIT_OFFSET;
    +
    + /* exactly one of the four least significant bits must be 1 */
    + if (!i || !is_power_of_2(i) || i > 8) {
    + ret = -EINVAL;
    + goto iounmap_base;
    + }
    +
    + num_locks = i * 32;
    +
    + hwlocks = kzalloc(sizeof(*hwlock) * num_locks, GFP_KERNEL);
    + if (!hwlocks) {
    + ret = -ENOMEM;
    + goto iounmap_base;
    + }
    +
    + for (i = 0; i < num_locks; i++) {
    + hwlock = &hwlocks[i];
    +
    + spin_lock_init(&hwlock->lock);
    +
    + hwlock->id = i;
    + hwlock->dev = &pdev->dev;
    + hwlock->addr = io_base + SPINLOCK_BASE_OFFSET +
    + sizeof(u32) * i;
    +
    + /* Add hwlock to the list of free locks */
    + list_add(&hwlock->node, &free_hwlocks);
    + }
    +
    + hwspinlock_state.io_base = io_base;
    + hwspinlock_state.hwlocks = hwlocks;
    + hwspinlock_state.num_locks = num_locks;
    +
    + /*
    + * runtime PM will make sure the clock of this module is
    + * enabled iff at least one lock is requested
    + */
    + pm_runtime_enable(&pdev->dev);
    +
    + pr_info("registered %d hwspinlocks\n", num_locks);
    +
    + return 0;
    +
    +iounmap_base:
    + iounmap(hwspinlock_state.io_base);
    + return ret;
    +}
    +
    +static int omap_hwspinlock_remove(struct platform_device *pdev)
    +{
    + pm_runtime_disable(&pdev->dev);
    +
    + hwspinlock_state.num_locks = 0;
    + hwspinlock_state.hwlocks = NULL;
    +
    + /* clear list of free locks */
    + INIT_LIST_HEAD(&free_hwlocks);
    +
    + kfree(hwspinlock_state.hwlocks);
    +
    + iounmap(hwspinlock_state.io_base);
    +
    + return 0;
    +}
    +
    +static struct platform_driver omap_hwspinlock_driver = {
    + .probe = omap_hwspinlock_probe,
    + .remove = omap_hwspinlock_remove,
    + .driver = {
    + .name = "omap_hwspinlock",
    + },
    +};
    +
    +static int __init omap_hwspinlock_init(void)
    +{
    + return platform_driver_register(&omap_hwspinlock_driver);
    +}
    +/* early board code might need to reserve specific hwspinlocks */
    +postcore_initcall(omap_hwspinlock_init);
    +
    +static void __exit omap_hwspinlock_exit(void)
    +{
    + platform_driver_unregister(&omap_hwspinlock_driver);
    +}
    +module_exit(omap_hwspinlock_exit);
    +
    +MODULE_LICENSE("GPL v2");
    +MODULE_DESCRIPTION("Hardware spinlock driver for OMAP");
    +MODULE_AUTHOR("Simon Que <sque@ti.com>");
    +MODULE_AUTHOR("Hari Kanigeri <h-kanigeri2@ti.com>");
    +MODULE_AUTHOR("Ohad Ben-Cohen <ohad@wizery.com>");
    diff --git a/include/linux/omap_hwspinlock.h b/include/linux/omap_hwspinlock.h
    new file mode 100644
    index 0000000..8faabb8
    --- /dev/null
    +++ b/include/linux/omap_hwspinlock.h
    @@ -0,0 +1,108 @@
    +/*
    + * Copyright (C) 2010 Texas Instruments, Inc.
    + *
    + * Author: Ohad Ben-Cohen <ohad@wizery.com>
    + *
    + * This program is free software; you can redistribute it and/or modify it
    + * under the terms of the GNU General Public License version 2 as published
    + * by the Free Software Foundation.
    + *
    + * This program is distributed in the hope that it will be useful,
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    + * GNU General Public License for more details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with this program; if not, write to the Free Software
    + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    + */
    +
    +#ifndef __LINUX_OMAP_HWSPINLOCK_H
    +#define __LINUX_OMAP_HWSPINLOCK_H
    +
    +#include <linux/err.h>
    +
    +struct omap_hwspinlock;
    +
    +#ifdef CONFIG_OMAP_HWSPINLOCK
    +
    +struct omap_hwspinlock *omap_hwspinlock_request(void);
    +struct omap_hwspinlock *omap_hwspinlock_request_specific(unsigned int id);
    +int omap_hwspinlock_free(struct omap_hwspinlock *hwlock);
    +int omap_hwspinlock_get_id(struct omap_hwspinlock *hwlock);
    +int omap_hwspin_lock_timeout(struct omap_hwspinlock *hwlock, unsigned long to,
    + unsigned long *flags);
    +int omap_hwspin_trylock(struct omap_hwspinlock *hwlock, unsigned long *flags);
    +int omap_hwspin_unlock(struct omap_hwspinlock *hwlock, unsigned long *flags);
    +
    +#else /* !CONFIG_OMAP_HWSPINLOCK */
    +
    +static inline struct omap_hwspinlock *omap_hwspinlock_request(void)
    +{
    + return ERR_PTR(-ENOSYS);
    +}
    +
    +static inline
    +struct omap_hwspinlock *omap_hwspinlock_request_specific(unsigned int id)
    +{
    + return ERR_PTR(-ENOSYS);
    +}
    +
    +static inline int omap_hwspinlock_free(struct omap_hwspinlock *hwlock)
    +{
    + return -ENOSYS;
    +}
    +
    +static inline int omap_hwspin_lock_timeout(struct omap_hwspinlock *hwlock,
    + unsigned long timeout)
    +{
    + return -ENOSYS;
    +}
    +
    +static inline int omap_hwspin_trylock(struct omap_hwspinlock *hwlock)
    +{
    + return -ENOSYS;
    +}
    +
    +static inline int omap_hwspin_unlock(struct omap_hwspinlock *hwlock)
    +{
    + return -ENOSYS;
    +}
    +
    +static inline int omap_hwspinlock_get_id(struct omap_hwspinlock *hwlock)
    +{
    + return -ENOSYS;
    +}
    +
    +#endif /* !CONFIG_OMAP_HWSPINLOCK */
    +
    +/**
    + * omap_hwspin_lock - take a specific hwspinlock, with no time limits
    + * @hwlock: the hwspinlock to be locked
    + * @flags: a pointer to where the caller's interrups state will be saved at
    + *
    + * This function locks the underlying @hwlock. If the @hwlock
    + * is already taken, the function will busy loop waiting for it to
    + * be released. Note: if a faulty remote core never releases the
    + * @hwlock, this function will deadlock.
    + *
    + * Upon a successful return from this function, preemption and interrupts
    + * are 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.
    + *
    + * Calling this function with a @hwlock which is already taken will have the
    + * same effects as calling spin_lock_irqsave on a taken spinlock.
    + *
    + * This function can be called from any context.
    + *
    + * Returns 0 when the @hwlock was successfully taken, and an appropriate
    + * error code otherwise (can only fail if @hwlock is invalid).
    + */
    +static inline
    +int omap_hwspin_lock(struct omap_hwspinlock *hwlock, unsigned long *flags)
    +{
    + return omap_hwspin_lock_timeout(hwlock, 0, flags);
    +}
    +
    +#endif /* __LINUX_OMAP_HWSPINLOCK_H */
    --
    1.7.0.4


    \
     
     \ /
      Last update: 2010-10-18 09:53    [W:2.567 / U:1.284 seconds]
    ©2003-2017 Jasper Spaans. hosted at Digital OceanAdvertise on this site