2 * SCI hardware spinlock driver
4 * Copyright (C) 2012 Spreadtrum - http://www.spreadtrum.com
5 * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com
7 * Contact: steve.zhan <steve.zhan@spreadtrum.com>
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * version 2 as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
19 #include <linux/kernel.h>
20 #include <linux/module.h>
21 #include <linux/device.h>
22 #include <linux/delay.h>
24 #include <linux/bitops.h>
25 #include <linux/pm_runtime.h>
26 #include <linux/slab.h>
27 #include <linux/spinlock.h>
28 #include <linux/hwspinlock.h>
29 #include <linux/platform_device.h>
31 #include <linux/of_device.h>
33 #include <soc/sprd/sci.h>
34 #include <soc/sprd/sci_glb_regs.h>
35 #include <soc/sprd/arch_lock.h>
37 #include "hwspinlock_internal.h"
40 #define HWSPINLOCK_ENABLE_CLEAR (0x454e434c)
42 #define HWSPINLOCK_CLEAR (0x0)
43 #define HWSPINLOCK_TTLSTS (0x4)
44 #define HWSPINLOCK_DTLSTL (0x8)
45 #define HWSPINLOCK_CLEAREN (0xc)
47 #define HWSPINLOCK_TOKEN_V0(_X_) (0x80 + 0x4*(_X_))
48 #define HWSPINLOCK_TOKEN_V1(_X_) (0x800 + 0x4*(_X_))
50 #define THIS_PROCESSOR_KEY (HWSPINLOCK_WRITE_KEY) /*first processor */
51 static void __iomem *hwspinlock_base;
52 static int hwspinlock_exist_cnt = 0;
54 #define SPINLOCKS_BUSY() (readl(hwspinlock_base + HWSPINLOCK_TTLSTS))
55 #define SPINLOCKS_DETAIL_STATUS(_X_) (_X_ = readl(hwspinlock_base + HWSPINLOCK_DTLSTL))
56 #define SPINLOCKS_ENABLE_CLEAR() writel(HWSPINLOCK_ENABLE_CLEAR,hwspinlock_base + HWSPINLOCK_CLEAREN)
57 #define SPINLOCKS_DISABLE_CLEAR() writel(~HWSPINLOCK_ENABLE_CLEAR, hwspinlock_base + HWSPINLOCK_CLEAREN)
59 __used static int hwspinlock_isbusy(unsigned int lockid)
61 unsigned int status = 0;
62 SPINLOCKS_DETAIL_STATUS(status);
63 return ((status & (1 << lockid)) ? 1 : 0);
66 __used static int hwspinlocks_isbusy(void)
68 return ((SPINLOCKS_BUSY())? 1 : 0);
71 __used static void hwspinlock_clear(unsigned int lockid)
73 /*setting the abnormal clear bit to 1 makes the corresponding
74 *lock to Not Taken state
76 SPINLOCKS_ENABLE_CLEAR();
77 writel(1 << lockid, hwspinlock_base + HWSPINLOCK_CLEAR);
78 SPINLOCKS_DISABLE_CLEAR();
81 __used static void hwspinlock_clear_all(void)
85 unsigned int lockid = 0;
86 SPINLOCKS_ENABLE_CLEAR();
88 writel(1 << lockid, hwspinlock_base + HWSPINLOCK_CLEAR);
89 } while (lockid++ < HWSPINLOCK_ID_TOTAL_NUMS);
90 SPINLOCKS_DISABLE_CLEAR();
93 static unsigned int do_lock_key(struct hwspinlock *lock)
95 unsigned int key = THIS_PROCESSOR_KEY;
99 static int __hwspinlock_trylock(struct hwspinlock *lock)
101 void __iomem *addr = lock->priv;
103 if (hwspinlock_vid == 0x100 || hwspinlock_vid == 0x200) {
107 unsigned int key = do_lock_key(lock);
108 if (!(key ^ HWSPINLOCK_NOTTAKEN_V0))
110 if (HWSPINLOCK_NOTTAKEN_V0 == readl(addr)) {
112 if (key == readl(addr))
119 RECORD_HWLOCKS_STATUS_LOCK(hwlock_to_id(lock));
123 static void __hwspinlock_unlock(struct hwspinlock *lock)
125 void __iomem *lock_addr = lock->priv;
128 if (hwspinlock_vid == 0x100 || hwspinlock_vid == 0x200) {
129 unlock_key = HWSPINLOCK_NOTTAKEN_V1;
131 unlock_key = HWSPINLOCK_NOTTAKEN_V0;
132 if (!(readl(lock_addr) ^ unlock_key))
135 RECORD_HWLOCKS_STATUS_UNLOCK(hwlock_to_id(lock));
136 writel(unlock_key, lock_addr);
140 * relax the interconnect while spinning on it.
142 * The specs recommended that the retry delay time will be
143 * just over half of the time that a requester would be
144 * expected to hold the lock.
146 * The number below is taken from an hardware specs example,
147 * obviously it is somewhat arbitrary.
149 static void __hwspinlock_relax(struct hwspinlock *lock)
154 static const struct hwspinlock_ops sci_hwspinlock_ops = {
155 .trylock = __hwspinlock_trylock,
156 .unlock = __hwspinlock_unlock,
157 .relax = __hwspinlock_relax,
160 static int sci_hwspinlock_probe(struct platform_device *pdev)
162 struct hwspinlock_device *bank;
163 struct hwspinlock *hwlock;
164 int i, ret, num_locks;
166 pdev->id = of_alias_get_id(pdev->dev.of_node, "hwspinlock");
171 /*each group have 32 locks*/
173 hwspinlock_base = (void __iomem *)(pdev->id == 0 ? SPRD_HWSPINLOCK0_BASE : SPRD_HWSPINLOCK1_BASE);
174 hwspinlock_vid = __raw_readl(hwspinlock_base + 0xffc);
176 bank = kzalloc(sizeof(*bank) + num_locks * sizeof(*hwlock), GFP_KERNEL);
182 platform_set_drvdata(pdev, bank);
184 for (i = 0, hwlock = &bank->lock[0]; i < num_locks; i++, hwlock++) {
185 if (hwspinlock_vid == 0x100 || hwspinlock_vid == 0x200)
186 hwlock->priv = (void __iomem *)(hwspinlock_base + HWSPINLOCK_TOKEN_V1(i));
188 hwlock->priv = (void __iomem *)(hwspinlock_base + HWSPINLOCK_TOKEN_V0(i));
191 * runtime PM will make sure the clock of this module is
192 * enabled if at least one lock is requested
194 pm_runtime_enable(&pdev->dev);
196 ret = hwspin_lock_register(bank, &pdev->dev, &sci_hwspinlock_ops,
197 hwspinlock_exist_cnt, num_locks);
202 hwspinlock_exist_cnt += num_locks;
204 printk("sci_hwspinlock_probe ok: hwspinlock name = %s, hwspinlock_vid = 0x%x, add num_locks = %d\n",
205 pdev->name,hwspinlock_vid,num_locks);
210 pm_runtime_disable(&pdev->dev);
216 static int sci_hwspinlock_remove(struct platform_device *pdev)
218 struct hwspinlock_device *bank = platform_get_drvdata(pdev);
221 ret = hwspin_lock_unregister(bank);
223 dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret);
227 pm_runtime_disable(&pdev->dev);
233 static const struct of_device_id sprd_hwspinlock_of_match[] = {
234 { .compatible = "sprd,sprd-hwspinlock", },
238 static struct platform_driver sci_hwspinlock_driver = {
239 .probe = sci_hwspinlock_probe,
240 .remove = sci_hwspinlock_remove,
242 .name = "sci_hwspinlock",
243 .owner = THIS_MODULE,
244 .of_match_table = of_match_ptr(sprd_hwspinlock_of_match),
248 static int __init sci_hwspinlock_init(void)
250 return platform_driver_register(&sci_hwspinlock_driver);
253 /* board init code might need to reserve hwspinlocks for predefined purposes */
254 postcore_initcall(sci_hwspinlock_init);
256 static void __exit sci_hwspinlock_exit(void)
258 platform_driver_unregister(&sci_hwspinlock_driver);
261 module_exit(sci_hwspinlock_exit);
263 MODULE_LICENSE("GPL v2");
264 MODULE_DESCRIPTION("Hardware spinlock driver for Spreadtrum");
265 MODULE_AUTHOR("steve.zhan <steve.zhan@spreadtrum.com>");