summaryrefslogtreecommitdiff
path: root/drivers/hwspinlock/sprd_hwspinlock.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/hwspinlock/sprd_hwspinlock.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'drivers/hwspinlock/sprd_hwspinlock.c')
-rw-r--r--drivers/hwspinlock/sprd_hwspinlock.c525
1 files changed, 525 insertions, 0 deletions
diff --git a/drivers/hwspinlock/sprd_hwspinlock.c b/drivers/hwspinlock/sprd_hwspinlock.c
new file mode 100644
index 00000000..b8b95906
--- /dev/null
+++ b/drivers/hwspinlock/sprd_hwspinlock.c
@@ -0,0 +1,525 @@
+/*
+ * SCI hardware spinlock driver
+ *
+ * Copyright (C) 2012 Spreadtrum - http://www.spreadtrum.com
+ * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com
+ *
+ * Contact: steve.zhan <steve.zhan@spreadtrum.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.
+ */
+
+#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/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include <soc/sprd/sci.h>
+#include <soc/sprd/sci_glb_regs.h>
+#include <soc/sprd/arch_lock.h>
+#include "hwspinlock_internal.h"
+
+/* hwspinlock reg */
+#define HWSPINLOCK_CLEAR (0x0)
+#define HWSPINLOCK_RECCTRL (0x4)
+#define HWSPINLOCK_TTLSTS (0x8)
+#define HWSPINLOCK_CLEAREN (0xc)
+#define HWSPINLOCK_FLAG0 (0x10)
+#define HWSPINLOCK_FLAG1 (0x14)
+#define HWSPINLOCK_FLAG2 (0x18)
+#define HWSPINLOCK_FLAG3 (0x1c)
+#define HWSPINLOCK_MASTERID(_X_) (0x80 + 0x4*(_X_))
+#define HWSPINLOCK_TOKEN_V0(_X_) (0x80 + 0x4*(_X_))
+#define HWSPINLOCK_TOKEN_V1(_X_) (0x800 + 0x4*(_X_))
+#define HWSPINLOCK_VERID (0xFFC)
+
+/* RECCTRL */
+#define HWSPINLOCK_ID BIT(0)
+#define HWSPINLOCK_USER_BITS BIT(1)
+
+/* first processor */
+#define THIS_PROCESSOR_KEY (HWSPINLOCK_WRITE_KEY)
+
+/* spinlock enable */
+#define HWSPINLOCK_ENABLE_CLEAR (0x454e434c)
+
+/* hwspinlock controller */
+#define AON_HWSPLOCK (1)
+#define AP_HWSPLOCK (0)
+
+struct sprd_hwspinlock_dev {
+ unsigned int hwspinlock_vid;
+ unsigned long hwspinlock_base;
+ unsigned int hwspinlock_cnt;
+ struct hwspinlock_device bank;
+};
+
+/* array[0] stands for lock0 */
+unsigned char hwlocks_implemented[HWSPINLOCK_ID_TOTAL_NUMS];
+unsigned char local_hwlocks_status[HWSPINLOCK_ID_TOTAL_NUMS];
+struct hwspinlock *hwlocks[HWSPINLOCK_ID_TOTAL_NUMS];
+int hwspinlock_vid_early;
+
+static struct sprd_hwspinlock_dev *hwlock_to_sprdlock_dev(struct hwspinlock *hwlock)
+{
+ struct hwspinlock_device *hwbank;
+ int lock_id;
+
+ if(!hwlock)
+ return NULL;
+
+ lock_id = hwlock_to_id(hwlock);
+ hwbank = container_of(hwlock,struct hwspinlock_device,lock[lock_id]);
+ if(hwbank)
+ return container_of(hwbank,struct sprd_hwspinlock_dev,bank);
+ else
+ return NULL;
+}
+
+#ifdef CONFIG_ARCH_WHALE
+int sprd_hwlock_init(void)
+{
+ return 0;
+}
+#else
+int sprd_hwlock_init(void)
+{
+ writel_relaxed(BIT_SPINLOCK_EB, (void __iomem *)(REG_GLB_SET(REG_AP_AHB_AHB_EB)));
+ writel_relaxed(BIT_SPLK_EB, (void __iomem *)(REG_GLB_SET(REG_AON_APB_APB_EB0)));
+ return 0;
+}
+#endif
+
+static void inline sprd_hwlocks_implemented(int hwlock_id)
+{
+ if (hwlock_id == AON_HWSPLOCK){
+ hwlocks_implemented[HWLOCK_ADI] = 1;
+ hwlocks_implemented[HWLOCK_GLB] = 1;
+ hwlocks_implemented[HWLOCK_AGPIO] = 1;
+ hwlocks_implemented[HWLOCK_AEIC] = 1;
+ hwlocks_implemented[HWLOCK_ADC] = 1;
+ hwlocks_implemented[HWLOCK_EFUSE] = 1;
+ }else if (hwlock_id == AP_HWSPLOCK){
+ /* Caution: ap hwspinlock id need add 31 */
+ /* hwlocks_implemented[HWLOCK_ADI+31] = 1; */
+ }
+}
+
+int sprd_record_hwlock_sts(unsigned int lock_id, unsigned int sts)
+{
+ if(lock_id >= HWSPINLOCK_ID_TOTAL_NUMS){
+ printk(KERN_ERR "Hwspinlock id is wrong!\n");
+ return -ENXIO;
+ }
+
+ local_hwlocks_status[lock_id] = sts;
+ return sts;
+}
+
+int sprd_hwspinlock_isbusy(unsigned int lock_id)
+{
+ unsigned int status = 0;
+ struct sprd_hwspinlock_dev *sprd_hwlock;
+
+ if (!hwlocks[lock_id]){
+ printk(KERN_ERR "This hwspinlock is not register!\n");
+ return -ENODEV;
+ }
+
+ sprd_hwlock = hwlock_to_sprdlock_dev(hwlocks[lock_id]);
+ if (!sprd_hwlock){
+ printk(KERN_ERR "Can't find hwspinlock device!\n");
+ return -ENODEV;
+ }
+
+ status = readl_relaxed((void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_TTLSTS));
+ if (status < 0)
+ return -EIO;
+ return ((status & (1 << lock_id)) ? 1 : 0);
+}
+
+int sprd_hwspinlocks_isbusy(void)
+{
+ int id;
+
+ for(id = 0; id < HWSPINLOCK_ID_TOTAL_NUMS; id++){
+ if(sprd_hwspinlock_isbusy(id) > 0)
+ return 1;
+ }
+ return 0;
+}
+
+int sprd_hwspinlock_master_id(unsigned int lock_id)
+{
+ unsigned int master_id = 0;
+ struct sprd_hwspinlock_dev *sprd_hwlock;
+
+ if(!hwlocks[lock_id]){
+ printk(KERN_ERR "This hwspinlock is not register!\n");
+ return -ENODEV;
+ }
+
+ sprd_hwlock = hwlock_to_sprdlock_dev(hwlocks[lock_id]);
+ if (!sprd_hwlock){
+ printk(KERN_ERR "Can't find hwspinlock device!\n");
+ return -ENODEV;
+ }
+
+ master_id = readl_relaxed((void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_MASTERID(lock_id)));
+ return master_id;
+}
+
+void hwspinlock_clear(unsigned int lock_id)
+{
+ struct sprd_hwspinlock_dev *sprd_hwlock;
+
+ if(!hwlocks[lock_id]){
+ printk(KERN_ERR "This hwspinlock is not register!\n");
+ return;
+ }
+
+ sprd_hwlock = hwlock_to_sprdlock_dev(hwlocks[lock_id]);
+ if (!sprd_hwlock){
+ printk(KERN_ERR "Can't find hwspinlock device!\n");
+ return;
+ }
+ /*setting the abnormal clear bit to 1 makes the corresponding
+ *lock to Not Taken state
+ */
+ writel_relaxed(HWSPINLOCK_ENABLE_CLEAR,(void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_CLEAREN));
+ writel_relaxed((1 << lock_id), (void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_CLEAR));
+ writel_relaxed(~HWSPINLOCK_ENABLE_CLEAR, (void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_CLEAREN));
+}
+
+void hwspinlock_clear_all(void)
+{
+ unsigned int lock_id = 0;
+ do {
+ hwspinlock_clear(lock_id);
+ } while (lock_id++ < HWSPINLOCK_ID_TOTAL_NUMS);
+}
+
+static int sprd_check_hwspinlock_vid(struct hwspinlock *lock)
+{
+ struct sprd_hwspinlock_dev *sprd_hwlock;
+
+ sprd_hwlock = hwlock_to_sprdlock_dev(lock);
+ if (!sprd_hwlock){
+ printk(KERN_ERR "%s:Can't find hwspinlock device!\n",__func__);
+ return -ENODEV;
+ }
+
+ /* for sharkl/sharkl64/whale project */
+ if (sprd_hwlock->hwspinlock_vid == 0x100 || sprd_hwlock->hwspinlock_vid == 0x200
+ || sprd_hwlock->hwspinlock_vid == 0x300){
+ return 1;
+ } else {
+ printk(KERN_ERR "Warning:hwlock version id is 0x%x!\n",sprd_hwlock->hwspinlock_vid);
+ return 0;
+ }
+}
+
+static unsigned long sprd_hwspinlock_addr(unsigned int lock_id)
+{
+ unsigned long addr;
+ struct hwspinlock *lock;
+
+ if(lock_id >= HWSPINLOCK_ID_TOTAL_NUMS){
+ printk(KERN_ERR "Hwspinlock id is wrong!\n");
+ return 0;
+ }
+
+ lock = hwlocks[lock_id];
+ if (!lock){
+ printk(KERN_ERR "Call %s: can't find hwspinlock!\n",__func__);
+ return 0;
+ }
+ else
+ addr = (unsigned long)lock->priv;
+
+ return addr;
+}
+
+static int sprd_init_hwlocks(int hwlock_id)
+{
+ struct hwspinlock **plock;
+ int i;
+
+ sprd_hwlocks_implemented(hwlock_id);
+
+ if (hwlock_id == AON_HWSPLOCK)
+ i = 0;
+ else if (hwlock_id == AP_HWSPLOCK)
+ i = 31;
+ else
+ i = HWSPINLOCK_ID_TOTAL_NUMS;
+
+ for (; i < HWSPINLOCK_ID_TOTAL_NUMS; ++i) {
+ if (hwlocks_implemented[i]) {
+ plock = &hwlocks[i];
+ *plock = hwspin_lock_request_specific(i);
+ if (WARN_ON(IS_ERR_OR_NULL(*plock)))
+ *plock = NULL;
+ else
+ pr_info("early alloc hwspinlock id %d\n",
+ hwspin_lock_get_id(*plock));
+ }
+ }
+ return 0;
+}
+
+static unsigned int do_lock_key(struct hwspinlock *lock)
+{
+ unsigned int key = THIS_PROCESSOR_KEY;
+ return key;
+}
+
+static int __hwspinlock_trylock(struct hwspinlock *lock)
+{
+ void __iomem *addr = lock->priv;
+
+ if (sprd_check_hwspinlock_vid(lock)) {
+ if (!readl_relaxed(addr))
+ goto __locked;
+ } else {
+ unsigned int key = do_lock_key(lock);
+ if (!(key ^ HWSPINLOCK_NOTTAKEN_V0))
+ BUG_ON(1);
+ if (HWSPINLOCK_NOTTAKEN_V0 == readl_relaxed(addr)) {
+ writel_relaxed(key, addr);
+ if (key == readl_relaxed(addr))
+ goto __locked;
+ }
+ }
+
+ printk(KERN_ERR "Hwspinlock [%d] lock failed!\n",hwlock_to_id(lock));
+ return 0;
+
+__locked:
+ sprd_record_hwlock_sts(hwlock_to_id(lock), 1);
+ return 1;
+}
+
+static void __hwspinlock_unlock(struct hwspinlock *lock)
+{
+ void __iomem *lock_addr = lock->priv;
+ int unlock_key;
+
+ if (sprd_check_hwspinlock_vid(lock)) {
+ unlock_key = HWSPINLOCK_NOTTAKEN_V1;
+ } else {
+ unlock_key = HWSPINLOCK_NOTTAKEN_V0;
+ if (!(readl_relaxed(lock_addr) ^ unlock_key))
+ BUG_ON(1);
+ }
+
+ writel_relaxed(unlock_key, lock_addr);
+ sprd_record_hwlock_sts(hwlock_to_id(lock), 0);
+}
+
+/*
+ * relax the 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 __hwspinlock_relax(struct hwspinlock *lock)
+{
+ ndelay(10);
+}
+
+static const struct hwspinlock_ops sprd_hwspinlock_ops = {
+ .trylock = __hwspinlock_trylock,
+ .unlock = __hwspinlock_unlock,
+ .relax = __hwspinlock_relax,
+};
+
+static int sprd_hwspinlock_probe(struct platform_device *pdev)
+{
+ struct hwspinlock *hwlock;
+ struct sprd_hwspinlock_dev *sprd_hwlock;
+ static int hwspinlock_exist_cnt = 0;
+ u32 num_locks = 0;
+ int i, ret, hwlock_id;
+
+ ret = of_alias_get_id(pdev->dev.of_node, "hwspinlock");
+ if (IS_ERR_VALUE(ret)) {
+ printk(KERN_ERR "Hwspinlock get alias failed!\n");
+ return ret;
+ }
+ hwlock_id = ret;
+
+ ret = of_property_read_u32(pdev->dev.of_node,"hwspinlock_cnt",&num_locks);
+ if(ret){
+ printk(KERN_ERR "Hwspinlock get count failed!\n");
+ num_locks = 32;
+ }
+
+ sprd_hwlock = devm_kzalloc(&pdev->dev, sizeof(struct sprd_hwspinlock_dev)
+ + num_locks * sizeof(*hwlock), GFP_KERNEL);
+ if (!sprd_hwlock) {
+ printk(KERN_ERR "Hwspinlock device alloc memory failed!\n");
+ return -ENOMEM;
+ }
+
+ /* intial the hwspinlock */
+ sprd_hwlock_init();
+ sprd_hwlock->hwspinlock_cnt = num_locks;
+ if (hwlock_id == 1)
+ sprd_hwlock->hwspinlock_base = SPRD_HWSPINLOCK1_BASE;
+ else if (hwlock_id == 0)
+ sprd_hwlock->hwspinlock_base = SPRD_HWSPINLOCK0_BASE;
+ else {
+ printk(KERN_ERR "Hwspinlock id is wrong!\n");
+ return -ENODEV;
+ }
+ sprd_hwlock->hwspinlock_vid = readl_relaxed((void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_VERID));
+
+ for (i = 0, hwlock = &sprd_hwlock->bank.lock[0]; i < num_locks; i++, hwlock++) {
+ if (sprd_hwlock->hwspinlock_vid == 0x100 || sprd_hwlock->hwspinlock_vid == 0x200
+ || sprd_hwlock->hwspinlock_vid == 0x300)
+ hwlock->priv = (void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_TOKEN_V1(i));
+ else
+ hwlock->priv = (void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_TOKEN_V0(i));
+ }
+
+ /*
+ * runtime PM will make sure the clock of this module is
+ * enabled if at least one lock is requested
+ */
+ platform_set_drvdata(pdev, sprd_hwlock);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = hwspin_lock_register(&sprd_hwlock->bank, &pdev->dev, &sprd_hwspinlock_ops,
+ hwspinlock_exist_cnt, num_locks);
+ if (ret){
+ printk(KERN_ERR "Hwspinlock register failed!\n");
+ goto reg_fail;
+ }
+
+ hwspinlock_exist_cnt += num_locks;
+ /* inital the hwlocks */
+ sprd_init_hwlocks(hwlock_id);
+#ifdef CONFIG_ARCH_WHALE
+ /* record master user bits */
+ writel_relaxed(HWSPINLOCK_USER_BITS, (void __iomem *)(sprd_hwlock->hwspinlock_base + HWSPINLOCK_RECCTRL));
+#endif
+
+ printk("sprd hwspinlock probe ok: hwspinlock name = %s, hwspinlock_vid = 0x%x,"
+ "hwspinlock_exist_cnt = %d\n", pdev->name,sprd_hwlock->hwspinlock_vid,hwspinlock_exist_cnt);
+
+ return 0;
+
+reg_fail:
+ pm_runtime_disable(&pdev->dev);
+ return ret;
+}
+
+static int sprd_hwspinlock_remove(struct platform_device *pdev)
+{
+ struct sprd_hwspinlock_dev *sprd_hwlock = platform_get_drvdata(pdev);
+ int ret;
+
+ ret = hwspin_lock_unregister(&sprd_hwlock->bank);
+ if (ret) {
+ dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret);
+ return ret;
+ }
+
+ pm_runtime_disable(&pdev->dev);
+ return 0;
+}
+
+static const struct of_device_id sprd_hwspinlock_of_match[] = {
+ { .compatible = "sprd,sprd-hwspinlock", },
+ { /* sentinel */ }
+};
+
+static struct platform_driver sprd_hwspinlock_driver = {
+ .probe = sprd_hwspinlock_probe,
+ .remove = sprd_hwspinlock_remove,
+ .driver = {
+ .name = "sprd_hwspinlock",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(sprd_hwspinlock_of_match),
+ },
+};
+
+static int __init sprd_hwspinlock_init(void)
+{
+ return platform_driver_register(&sprd_hwspinlock_driver);
+}
+
+/* board init code might need to reserve hwspinlocks for predefined purposes */
+postcore_initcall(sprd_hwspinlock_init);
+
+static void __exit sprd_hwspinlock_exit(void)
+{
+ platform_driver_unregister(&sprd_hwspinlock_driver);
+}
+
+module_exit(sprd_hwspinlock_exit);
+
+static int remove_node(struct device_node *np)
+{
+ struct property *ps;
+
+ ps = kzalloc(sizeof(*ps), GFP_KERNEL);
+ if (!ps)
+ return -1;
+
+ ps->value = kstrdup("no", GFP_KERNEL);
+ ps->length = strlen(ps->value);
+ ps->name = kstrdup("status", GFP_KERNEL);
+ of_update_property(np, ps);
+ printk("After register,hwspinlock1 node available=%d\n",of_device_is_available(np));
+
+ return 0;
+}
+
+int __init early_hwspinlocks_init(void)
+{
+ struct device_node *np;
+ struct platform_device *pdev;
+ int ret;
+
+ np = of_find_node_by_name(NULL, "hwspinlock1");
+ if (!np) {
+ pr_warn("Can't get the hwspinlock1 node!\n");
+ return -ENODEV;
+ }
+
+ pdev = of_platform_device_create(np, 0, NULL);
+ if (!pdev) {
+ pr_warn("register hwspinlock1 failed!\n");
+ return -ENODEV;
+ }
+
+ pr_info("SPRD register hwspinlock1 ok,hwspinlock1's name is %s!\n",pdev->name);
+ ret = remove_node(np);
+ return ret;
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Hardware spinlock driver for Spreadtrum");
+MODULE_AUTHOR("baolin.wang <baolin.wang@spreadtrum.com>");