1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) STMicroelectronics SA 2018 |
4 | * Author: Benjamin Gaignard <benjamin.gaignard@st.com> for STMicroelectronics. |
5 | */ |
6 | |
7 | #include <linux/clk.h> |
8 | #include <linux/delay.h> |
9 | #include <linux/hwspinlock.h> |
10 | #include <linux/io.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | #include <linux/platform_device.h> |
15 | #include <linux/pm_runtime.h> |
16 | |
17 | #include "hwspinlock_internal.h" |
18 | |
19 | #define STM32_MUTEX_COREID BIT(8) |
20 | #define STM32_MUTEX_LOCK_BIT BIT(31) |
21 | #define STM32_MUTEX_NUM_LOCKS 32 |
22 | |
23 | struct stm32_hwspinlock { |
24 | struct clk *clk; |
25 | struct hwspinlock_device bank; |
26 | }; |
27 | |
28 | static int stm32_hwspinlock_trylock(struct hwspinlock *lock) |
29 | { |
30 | void __iomem *lock_addr = lock->priv; |
31 | u32 status; |
32 | |
33 | writel(STM32_MUTEX_LOCK_BIT | STM32_MUTEX_COREID, addr: lock_addr); |
34 | status = readl(addr: lock_addr); |
35 | |
36 | return status == (STM32_MUTEX_LOCK_BIT | STM32_MUTEX_COREID); |
37 | } |
38 | |
39 | static void stm32_hwspinlock_unlock(struct hwspinlock *lock) |
40 | { |
41 | void __iomem *lock_addr = lock->priv; |
42 | |
43 | writel(STM32_MUTEX_COREID, addr: lock_addr); |
44 | } |
45 | |
46 | static void stm32_hwspinlock_relax(struct hwspinlock *lock) |
47 | { |
48 | ndelay(50); |
49 | } |
50 | |
51 | static const struct hwspinlock_ops stm32_hwspinlock_ops = { |
52 | .trylock = stm32_hwspinlock_trylock, |
53 | .unlock = stm32_hwspinlock_unlock, |
54 | .relax = stm32_hwspinlock_relax, |
55 | }; |
56 | |
57 | static void stm32_hwspinlock_disable_clk(void *data) |
58 | { |
59 | struct platform_device *pdev = data; |
60 | struct stm32_hwspinlock *hw = platform_get_drvdata(pdev); |
61 | struct device *dev = &pdev->dev; |
62 | |
63 | pm_runtime_get_sync(dev); |
64 | pm_runtime_disable(dev); |
65 | pm_runtime_set_suspended(dev); |
66 | pm_runtime_put_noidle(dev); |
67 | |
68 | clk_disable_unprepare(clk: hw->clk); |
69 | } |
70 | |
71 | static int stm32_hwspinlock_probe(struct platform_device *pdev) |
72 | { |
73 | struct device *dev = &pdev->dev; |
74 | struct stm32_hwspinlock *hw; |
75 | void __iomem *io_base; |
76 | int i, ret; |
77 | |
78 | io_base = devm_platform_ioremap_resource(pdev, index: 0); |
79 | if (IS_ERR(ptr: io_base)) |
80 | return PTR_ERR(ptr: io_base); |
81 | |
82 | hw = devm_kzalloc(dev, struct_size(hw, bank.lock, STM32_MUTEX_NUM_LOCKS), GFP_KERNEL); |
83 | if (!hw) |
84 | return -ENOMEM; |
85 | |
86 | hw->clk = devm_clk_get(dev, id: "hsem" ); |
87 | if (IS_ERR(ptr: hw->clk)) |
88 | return PTR_ERR(ptr: hw->clk); |
89 | |
90 | ret = clk_prepare_enable(clk: hw->clk); |
91 | if (ret) { |
92 | dev_err(dev, "Failed to prepare_enable clock\n" ); |
93 | return ret; |
94 | } |
95 | |
96 | platform_set_drvdata(pdev, data: hw); |
97 | |
98 | pm_runtime_get_noresume(dev); |
99 | pm_runtime_set_active(dev); |
100 | pm_runtime_enable(dev); |
101 | pm_runtime_put(dev); |
102 | |
103 | ret = devm_add_action_or_reset(dev, stm32_hwspinlock_disable_clk, pdev); |
104 | if (ret) { |
105 | dev_err(dev, "Failed to register action\n" ); |
106 | return ret; |
107 | } |
108 | |
109 | for (i = 0; i < STM32_MUTEX_NUM_LOCKS; i++) |
110 | hw->bank.lock[i].priv = io_base + i * sizeof(u32); |
111 | |
112 | ret = devm_hwspin_lock_register(dev, bank: &hw->bank, ops: &stm32_hwspinlock_ops, |
113 | base_id: 0, STM32_MUTEX_NUM_LOCKS); |
114 | |
115 | if (ret) |
116 | dev_err(dev, "Failed to register hwspinlock\n" ); |
117 | |
118 | return ret; |
119 | } |
120 | |
121 | static int __maybe_unused stm32_hwspinlock_runtime_suspend(struct device *dev) |
122 | { |
123 | struct stm32_hwspinlock *hw = dev_get_drvdata(dev); |
124 | |
125 | clk_disable_unprepare(clk: hw->clk); |
126 | |
127 | return 0; |
128 | } |
129 | |
130 | static int __maybe_unused stm32_hwspinlock_runtime_resume(struct device *dev) |
131 | { |
132 | struct stm32_hwspinlock *hw = dev_get_drvdata(dev); |
133 | |
134 | clk_prepare_enable(clk: hw->clk); |
135 | |
136 | return 0; |
137 | } |
138 | |
139 | static const struct dev_pm_ops stm32_hwspinlock_pm_ops = { |
140 | SET_RUNTIME_PM_OPS(stm32_hwspinlock_runtime_suspend, |
141 | stm32_hwspinlock_runtime_resume, |
142 | NULL) |
143 | }; |
144 | |
145 | static const struct of_device_id stm32_hwpinlock_ids[] = { |
146 | { .compatible = "st,stm32-hwspinlock" , }, |
147 | {}, |
148 | }; |
149 | MODULE_DEVICE_TABLE(of, stm32_hwpinlock_ids); |
150 | |
151 | static struct platform_driver stm32_hwspinlock_driver = { |
152 | .probe = stm32_hwspinlock_probe, |
153 | .driver = { |
154 | .name = "stm32_hwspinlock" , |
155 | .of_match_table = stm32_hwpinlock_ids, |
156 | .pm = &stm32_hwspinlock_pm_ops, |
157 | }, |
158 | }; |
159 | |
160 | static int __init stm32_hwspinlock_init(void) |
161 | { |
162 | return platform_driver_register(&stm32_hwspinlock_driver); |
163 | } |
164 | /* board init code might need to reserve hwspinlocks for predefined purposes */ |
165 | postcore_initcall(stm32_hwspinlock_init); |
166 | |
167 | static void __exit stm32_hwspinlock_exit(void) |
168 | { |
169 | platform_driver_unregister(&stm32_hwspinlock_driver); |
170 | } |
171 | module_exit(stm32_hwspinlock_exit); |
172 | |
173 | MODULE_LICENSE("GPL v2" ); |
174 | MODULE_DESCRIPTION("Hardware spinlock driver for STM32 SoCs" ); |
175 | MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@st.com>" ); |
176 | |