264 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (c) 2013, The Linux Foundation. All rights reserved.4 * Copyright (c) 2015, Sony Mobile Communications AB5 */6 7#include <linux/hwspinlock.h>8#include <linux/io.h>9#include <linux/kernel.h>10#include <linux/mfd/syscon.h>11#include <linux/module.h>12#include <linux/of.h>13#include <linux/of_device.h>14#include <linux/platform_device.h>15#include <linux/regmap.h>16 17#include "hwspinlock_internal.h"18 19#define QCOM_MUTEX_APPS_PROC_ID 120#define QCOM_MUTEX_NUM_LOCKS 3221 22struct qcom_hwspinlock_of_data {23 u32 offset;24 u32 stride;25 const struct regmap_config *regmap_config;26};27 28static int qcom_hwspinlock_trylock(struct hwspinlock *lock)29{30 struct regmap_field *field = lock->priv;31 u32 lock_owner;32 int ret;33 34 ret = regmap_field_write(field, QCOM_MUTEX_APPS_PROC_ID);35 if (ret)36 return ret;37 38 ret = regmap_field_read(field, &lock_owner);39 if (ret)40 return ret;41 42 return lock_owner == QCOM_MUTEX_APPS_PROC_ID;43}44 45static void qcom_hwspinlock_unlock(struct hwspinlock *lock)46{47 struct regmap_field *field = lock->priv;48 u32 lock_owner;49 int ret;50 51 ret = regmap_field_read(field, &lock_owner);52 if (ret) {53 pr_err("%s: unable to query spinlock owner\n", __func__);54 return;55 }56 57 if (lock_owner != QCOM_MUTEX_APPS_PROC_ID) {58 pr_err("%s: spinlock not owned by us (actual owner is %d)\n",59 __func__, lock_owner);60 }61 62 ret = regmap_field_write(field, 0);63 if (ret)64 pr_err("%s: failed to unlock spinlock\n", __func__);65}66 67static int qcom_hwspinlock_bust(struct hwspinlock *lock, unsigned int id)68{69 struct regmap_field *field = lock->priv;70 u32 owner;71 int ret;72 73 ret = regmap_field_read(field, &owner);74 if (ret) {75 dev_err(lock->bank->dev, "unable to query spinlock owner\n");76 return ret;77 }78 79 if (owner != id)80 return 0;81 82 ret = regmap_field_write(field, 0);83 if (ret) {84 dev_err(lock->bank->dev, "failed to bust spinlock\n");85 return ret;86 }87 88 return 0;89}90 91static const struct hwspinlock_ops qcom_hwspinlock_ops = {92 .trylock = qcom_hwspinlock_trylock,93 .unlock = qcom_hwspinlock_unlock,94 .bust = qcom_hwspinlock_bust,95};96 97static const struct regmap_config sfpb_mutex_config = {98 .reg_bits = 32,99 .reg_stride = 4,100 .val_bits = 32,101 .max_register = 0x100,102 .fast_io = true,103};104 105static const struct qcom_hwspinlock_of_data of_sfpb_mutex = {106 .offset = 0x4,107 .stride = 0x4,108 .regmap_config = &sfpb_mutex_config,109};110 111static const struct regmap_config tcsr_msm8226_mutex_config = {112 .reg_bits = 32,113 .reg_stride = 4,114 .val_bits = 32,115 .max_register = 0x1000,116 .fast_io = true,117};118 119static const struct qcom_hwspinlock_of_data of_msm8226_tcsr_mutex = {120 .offset = 0,121 .stride = 0x80,122 .regmap_config = &tcsr_msm8226_mutex_config,123};124 125static const struct regmap_config tcsr_mutex_config = {126 .reg_bits = 32,127 .reg_stride = 4,128 .val_bits = 32,129 .max_register = 0x20000,130 .fast_io = true,131};132 133static const struct qcom_hwspinlock_of_data of_tcsr_mutex = {134 .offset = 0,135 .stride = 0x1000,136 .regmap_config = &tcsr_mutex_config,137};138 139static const struct of_device_id qcom_hwspinlock_of_match[] = {140 { .compatible = "qcom,sfpb-mutex", .data = &of_sfpb_mutex },141 { .compatible = "qcom,tcsr-mutex", .data = &of_tcsr_mutex },142 { .compatible = "qcom,apq8084-tcsr-mutex", .data = &of_msm8226_tcsr_mutex },143 { .compatible = "qcom,msm8226-tcsr-mutex", .data = &of_msm8226_tcsr_mutex },144 { .compatible = "qcom,msm8974-tcsr-mutex", .data = &of_msm8226_tcsr_mutex },145 { .compatible = "qcom,msm8994-tcsr-mutex", .data = &of_msm8226_tcsr_mutex },146 { }147};148MODULE_DEVICE_TABLE(of, qcom_hwspinlock_of_match);149 150static struct regmap *qcom_hwspinlock_probe_syscon(struct platform_device *pdev,151 u32 *base, u32 *stride)152{153 struct device_node *syscon;154 struct regmap *regmap;155 int ret;156 157 syscon = of_parse_phandle(pdev->dev.of_node, "syscon", 0);158 if (!syscon)159 return ERR_PTR(-ENODEV);160 161 regmap = syscon_node_to_regmap(syscon);162 of_node_put(syscon);163 if (IS_ERR(regmap))164 return regmap;165 166 ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 1, base);167 if (ret < 0) {168 dev_err(&pdev->dev, "no offset in syscon\n");169 return ERR_PTR(-EINVAL);170 }171 172 ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 2, stride);173 if (ret < 0) {174 dev_err(&pdev->dev, "no stride syscon\n");175 return ERR_PTR(-EINVAL);176 }177 178 return regmap;179}180 181static struct regmap *qcom_hwspinlock_probe_mmio(struct platform_device *pdev,182 u32 *offset, u32 *stride)183{184 const struct qcom_hwspinlock_of_data *data;185 struct device *dev = &pdev->dev;186 void __iomem *base;187 188 data = of_device_get_match_data(dev);189 if (!data->regmap_config)190 return ERR_PTR(-EINVAL);191 192 *offset = data->offset;193 *stride = data->stride;194 195 base = devm_platform_ioremap_resource(pdev, 0);196 if (IS_ERR(base))197 return ERR_CAST(base);198 199 return devm_regmap_init_mmio(dev, base, data->regmap_config);200}201 202static int qcom_hwspinlock_probe(struct platform_device *pdev)203{204 struct hwspinlock_device *bank;205 struct reg_field field;206 struct regmap *regmap;207 size_t array_size;208 u32 stride;209 u32 base;210 int i;211 212 regmap = qcom_hwspinlock_probe_syscon(pdev, &base, &stride);213 if (IS_ERR(regmap) && PTR_ERR(regmap) == -ENODEV)214 regmap = qcom_hwspinlock_probe_mmio(pdev, &base, &stride);215 216 if (IS_ERR(regmap))217 return PTR_ERR(regmap);218 219 array_size = QCOM_MUTEX_NUM_LOCKS * sizeof(struct hwspinlock);220 bank = devm_kzalloc(&pdev->dev, sizeof(*bank) + array_size, GFP_KERNEL);221 if (!bank)222 return -ENOMEM;223 224 platform_set_drvdata(pdev, bank);225 226 for (i = 0; i < QCOM_MUTEX_NUM_LOCKS; i++) {227 field.reg = base + i * stride;228 field.lsb = 0;229 field.msb = 31;230 231 bank->lock[i].priv = devm_regmap_field_alloc(&pdev->dev,232 regmap, field);233 if (IS_ERR(bank->lock[i].priv))234 return PTR_ERR(bank->lock[i].priv);235 }236 237 return devm_hwspin_lock_register(&pdev->dev, bank, &qcom_hwspinlock_ops,238 0, QCOM_MUTEX_NUM_LOCKS);239}240 241static struct platform_driver qcom_hwspinlock_driver = {242 .probe = qcom_hwspinlock_probe,243 .driver = {244 .name = "qcom_hwspinlock",245 .of_match_table = qcom_hwspinlock_of_match,246 },247};248 249static int __init qcom_hwspinlock_init(void)250{251 return platform_driver_register(&qcom_hwspinlock_driver);252}253/* board init code might need to reserve hwspinlocks for predefined purposes */254postcore_initcall(qcom_hwspinlock_init);255 256static void __exit qcom_hwspinlock_exit(void)257{258 platform_driver_unregister(&qcom_hwspinlock_driver);259}260module_exit(qcom_hwspinlock_exit);261 262MODULE_LICENSE("GPL v2");263MODULE_DESCRIPTION("Hardware spinlock driver for Qualcomm SoCs");264