| /* |
| * SIRF hardware spinlock driver |
| * |
| * Copyright (c) 2015 Cambridge Silicon Radio Limited, a CSR plc group company. |
| * |
| * Licensed under GPLv2. |
| */ |
| |
| #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, sizeof(*hwspin) + |
| sizeof(*hwlock) * 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>"); |