119a0f612SBjorn Andersson /* 219a0f612SBjorn Andersson * Copyright (c) 2013, The Linux Foundation. All rights reserved. 319a0f612SBjorn Andersson * Copyright (c) 2015, Sony Mobile Communications AB 419a0f612SBjorn Andersson * 519a0f612SBjorn Andersson * This software is licensed under the terms of the GNU General Public 619a0f612SBjorn Andersson * License version 2, as published by the Free Software Foundation, and 719a0f612SBjorn Andersson * may be copied, distributed, and modified under those terms. 819a0f612SBjorn Andersson * 919a0f612SBjorn Andersson * This program is distributed in the hope that it will be useful, 1019a0f612SBjorn Andersson * but WITHOUT ANY WARRANTY; without even the implied warranty of 1119a0f612SBjorn Andersson * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1219a0f612SBjorn Andersson * GNU General Public License for more details. 1319a0f612SBjorn Andersson */ 1419a0f612SBjorn Andersson 1519a0f612SBjorn Andersson #include <linux/hwspinlock.h> 1619a0f612SBjorn Andersson #include <linux/io.h> 1719a0f612SBjorn Andersson #include <linux/kernel.h> 1819a0f612SBjorn Andersson #include <linux/mfd/syscon.h> 1919a0f612SBjorn Andersson #include <linux/module.h> 2019a0f612SBjorn Andersson #include <linux/of.h> 2119a0f612SBjorn Andersson #include <linux/of_device.h> 2219a0f612SBjorn Andersson #include <linux/platform_device.h> 2319a0f612SBjorn Andersson #include <linux/pm_runtime.h> 2419a0f612SBjorn Andersson #include <linux/regmap.h> 2519a0f612SBjorn Andersson 2619a0f612SBjorn Andersson #include "hwspinlock_internal.h" 2719a0f612SBjorn Andersson 2819a0f612SBjorn Andersson #define QCOM_MUTEX_APPS_PROC_ID 1 2919a0f612SBjorn Andersson #define QCOM_MUTEX_NUM_LOCKS 32 3019a0f612SBjorn Andersson 3119a0f612SBjorn Andersson static int qcom_hwspinlock_trylock(struct hwspinlock *lock) 3219a0f612SBjorn Andersson { 3319a0f612SBjorn Andersson struct regmap_field *field = lock->priv; 3419a0f612SBjorn Andersson u32 lock_owner; 3519a0f612SBjorn Andersson int ret; 3619a0f612SBjorn Andersson 3719a0f612SBjorn Andersson ret = regmap_field_write(field, QCOM_MUTEX_APPS_PROC_ID); 3819a0f612SBjorn Andersson if (ret) 3919a0f612SBjorn Andersson return ret; 4019a0f612SBjorn Andersson 4119a0f612SBjorn Andersson ret = regmap_field_read(field, &lock_owner); 4219a0f612SBjorn Andersson if (ret) 4319a0f612SBjorn Andersson return ret; 4419a0f612SBjorn Andersson 4519a0f612SBjorn Andersson return lock_owner == QCOM_MUTEX_APPS_PROC_ID; 4619a0f612SBjorn Andersson } 4719a0f612SBjorn Andersson 4819a0f612SBjorn Andersson static void qcom_hwspinlock_unlock(struct hwspinlock *lock) 4919a0f612SBjorn Andersson { 5019a0f612SBjorn Andersson struct regmap_field *field = lock->priv; 5119a0f612SBjorn Andersson u32 lock_owner; 5219a0f612SBjorn Andersson int ret; 5319a0f612SBjorn Andersson 5419a0f612SBjorn Andersson ret = regmap_field_read(field, &lock_owner); 5519a0f612SBjorn Andersson if (ret) { 5619a0f612SBjorn Andersson pr_err("%s: unable to query spinlock owner\n", __func__); 5719a0f612SBjorn Andersson return; 5819a0f612SBjorn Andersson } 5919a0f612SBjorn Andersson 6019a0f612SBjorn Andersson if (lock_owner != QCOM_MUTEX_APPS_PROC_ID) { 6119a0f612SBjorn Andersson pr_err("%s: spinlock not owned by us (actual owner is %d)\n", 6219a0f612SBjorn Andersson __func__, lock_owner); 6319a0f612SBjorn Andersson } 6419a0f612SBjorn Andersson 6519a0f612SBjorn Andersson ret = regmap_field_write(field, 0); 6619a0f612SBjorn Andersson if (ret) 6719a0f612SBjorn Andersson pr_err("%s: failed to unlock spinlock\n", __func__); 6819a0f612SBjorn Andersson } 6919a0f612SBjorn Andersson 7019a0f612SBjorn Andersson static const struct hwspinlock_ops qcom_hwspinlock_ops = { 7119a0f612SBjorn Andersson .trylock = qcom_hwspinlock_trylock, 7219a0f612SBjorn Andersson .unlock = qcom_hwspinlock_unlock, 7319a0f612SBjorn Andersson }; 7419a0f612SBjorn Andersson 7519a0f612SBjorn Andersson static const struct of_device_id qcom_hwspinlock_of_match[] = { 7619a0f612SBjorn Andersson { .compatible = "qcom,sfpb-mutex" }, 7719a0f612SBjorn Andersson { .compatible = "qcom,tcsr-mutex" }, 7819a0f612SBjorn Andersson { } 7919a0f612SBjorn Andersson }; 8019a0f612SBjorn Andersson MODULE_DEVICE_TABLE(of, qcom_hwspinlock_of_match); 8119a0f612SBjorn Andersson 8219a0f612SBjorn Andersson static int qcom_hwspinlock_probe(struct platform_device *pdev) 8319a0f612SBjorn Andersson { 8419a0f612SBjorn Andersson struct hwspinlock_device *bank; 8519a0f612SBjorn Andersson struct device_node *syscon; 8619a0f612SBjorn Andersson struct reg_field field; 8719a0f612SBjorn Andersson struct regmap *regmap; 8819a0f612SBjorn Andersson size_t array_size; 8919a0f612SBjorn Andersson u32 stride; 9019a0f612SBjorn Andersson u32 base; 9119a0f612SBjorn Andersson int ret; 9219a0f612SBjorn Andersson int i; 9319a0f612SBjorn Andersson 9419a0f612SBjorn Andersson syscon = of_parse_phandle(pdev->dev.of_node, "syscon", 0); 9519a0f612SBjorn Andersson if (!syscon) { 9619a0f612SBjorn Andersson dev_err(&pdev->dev, "no syscon property\n"); 9719a0f612SBjorn Andersson return -ENODEV; 9819a0f612SBjorn Andersson } 9919a0f612SBjorn Andersson 10019a0f612SBjorn Andersson regmap = syscon_node_to_regmap(syscon); 10119a0f612SBjorn Andersson if (IS_ERR(regmap)) 10219a0f612SBjorn Andersson return PTR_ERR(regmap); 10319a0f612SBjorn Andersson 10419a0f612SBjorn Andersson ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 1, &base); 10519a0f612SBjorn Andersson if (ret < 0) { 10619a0f612SBjorn Andersson dev_err(&pdev->dev, "no offset in syscon\n"); 10719a0f612SBjorn Andersson return -EINVAL; 10819a0f612SBjorn Andersson } 10919a0f612SBjorn Andersson 11019a0f612SBjorn Andersson ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 2, &stride); 11119a0f612SBjorn Andersson if (ret < 0) { 11219a0f612SBjorn Andersson dev_err(&pdev->dev, "no stride syscon\n"); 11319a0f612SBjorn Andersson return -EINVAL; 11419a0f612SBjorn Andersson } 11519a0f612SBjorn Andersson 11619a0f612SBjorn Andersson array_size = QCOM_MUTEX_NUM_LOCKS * sizeof(struct hwspinlock); 11719a0f612SBjorn Andersson bank = devm_kzalloc(&pdev->dev, sizeof(*bank) + array_size, GFP_KERNEL); 11819a0f612SBjorn Andersson if (!bank) 11919a0f612SBjorn Andersson return -ENOMEM; 12019a0f612SBjorn Andersson 12119a0f612SBjorn Andersson platform_set_drvdata(pdev, bank); 12219a0f612SBjorn Andersson 12319a0f612SBjorn Andersson for (i = 0; i < QCOM_MUTEX_NUM_LOCKS; i++) { 12419a0f612SBjorn Andersson field.reg = base + i * stride; 12519a0f612SBjorn Andersson field.lsb = 0; 126*bd5717a4SBjorn Andersson field.msb = 31; 12719a0f612SBjorn Andersson 12819a0f612SBjorn Andersson bank->lock[i].priv = devm_regmap_field_alloc(&pdev->dev, 12919a0f612SBjorn Andersson regmap, field); 13019a0f612SBjorn Andersson } 13119a0f612SBjorn Andersson 13219a0f612SBjorn Andersson pm_runtime_enable(&pdev->dev); 13319a0f612SBjorn Andersson 13419a0f612SBjorn Andersson ret = hwspin_lock_register(bank, &pdev->dev, &qcom_hwspinlock_ops, 13519a0f612SBjorn Andersson 0, QCOM_MUTEX_NUM_LOCKS); 13619a0f612SBjorn Andersson if (ret) 13719a0f612SBjorn Andersson pm_runtime_disable(&pdev->dev); 13819a0f612SBjorn Andersson 13919a0f612SBjorn Andersson return ret; 14019a0f612SBjorn Andersson } 14119a0f612SBjorn Andersson 14219a0f612SBjorn Andersson static int qcom_hwspinlock_remove(struct platform_device *pdev) 14319a0f612SBjorn Andersson { 14419a0f612SBjorn Andersson struct hwspinlock_device *bank = platform_get_drvdata(pdev); 14519a0f612SBjorn Andersson int ret; 14619a0f612SBjorn Andersson 14719a0f612SBjorn Andersson ret = hwspin_lock_unregister(bank); 14819a0f612SBjorn Andersson if (ret) { 14919a0f612SBjorn Andersson dev_err(&pdev->dev, "%s failed: %d\n", __func__, ret); 15019a0f612SBjorn Andersson return ret; 15119a0f612SBjorn Andersson } 15219a0f612SBjorn Andersson 15319a0f612SBjorn Andersson pm_runtime_disable(&pdev->dev); 15419a0f612SBjorn Andersson 15519a0f612SBjorn Andersson return 0; 15619a0f612SBjorn Andersson } 15719a0f612SBjorn Andersson 15819a0f612SBjorn Andersson static struct platform_driver qcom_hwspinlock_driver = { 15919a0f612SBjorn Andersson .probe = qcom_hwspinlock_probe, 16019a0f612SBjorn Andersson .remove = qcom_hwspinlock_remove, 16119a0f612SBjorn Andersson .driver = { 16219a0f612SBjorn Andersson .name = "qcom_hwspinlock", 16319a0f612SBjorn Andersson .of_match_table = qcom_hwspinlock_of_match, 16419a0f612SBjorn Andersson }, 16519a0f612SBjorn Andersson }; 16619a0f612SBjorn Andersson 16719a0f612SBjorn Andersson static int __init qcom_hwspinlock_init(void) 16819a0f612SBjorn Andersson { 16919a0f612SBjorn Andersson return platform_driver_register(&qcom_hwspinlock_driver); 17019a0f612SBjorn Andersson } 17119a0f612SBjorn Andersson /* board init code might need to reserve hwspinlocks for predefined purposes */ 17219a0f612SBjorn Andersson postcore_initcall(qcom_hwspinlock_init); 17319a0f612SBjorn Andersson 17419a0f612SBjorn Andersson static void __exit qcom_hwspinlock_exit(void) 17519a0f612SBjorn Andersson { 17619a0f612SBjorn Andersson platform_driver_unregister(&qcom_hwspinlock_driver); 17719a0f612SBjorn Andersson } 17819a0f612SBjorn Andersson module_exit(qcom_hwspinlock_exit); 17919a0f612SBjorn Andersson 18019a0f612SBjorn Andersson MODULE_LICENSE("GPL v2"); 18119a0f612SBjorn Andersson MODULE_DESCRIPTION("Hardware spinlock driver for Qualcomm SoCs"); 182