1 /*
2 * Copyright (c) 2023 Sequans Communications
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT sqn_hwspinlock
8
9 #include <zephyr/device.h>
10 #include <zephyr/kernel.h>
11 #include <zephyr/sys/sys_io.h>
12 #include <zephyr/drivers/hwspinlock.h>
13
14 #include <zephyr/sys/printk.h>
15 #include <zephyr/logging/log.h>
16 LOG_MODULE_REGISTER(sqn_hwspinlock);
17
18 struct sqn_hwspinlock_data {
19 DEVICE_MMIO_RAM;
20 };
21
22 struct sqn_hwspinlock_config {
23 DEVICE_MMIO_ROM;
24 uint32_t num_locks;
25 };
26
get_lock_addr(const struct device * dev,uint32_t id)27 static inline mem_addr_t get_lock_addr(const struct device *dev, uint32_t id)
28 {
29 return (mem_addr_t)(DEVICE_MMIO_GET(dev) + id * sizeof(uint32_t));
30 }
31
32 /*
33 * To define CPU id, we use the affinity2 and affinity1
34 * fields of the MPIDR register.
35 */
mpidr_to_cpuid(uint64_t mpidr_val)36 static uint8_t mpidr_to_cpuid(uint64_t mpidr_val)
37 {
38 uint8_t cpuid = ((mpidr_val >> 8) & 0x0F) | ((mpidr_val >> 12) & 0xF0);
39
40 return ++cpuid;
41 }
42
sqn_hwspinlock_trylock(const struct device * dev,uint32_t id)43 static int sqn_hwspinlock_trylock(const struct device *dev, uint32_t id)
44 {
45 const struct sqn_hwspinlock_config *config = dev->config;
46 uint8_t cpuid;
47
48 if (id > config->num_locks) {
49 return -EINVAL;
50 }
51
52 /*
53 * If the register value is equal to cpuid, this means that the current
54 * core has already locked the HW spinlock.
55 * If not, we try to lock the HW spinlock by writing cpuid, then check
56 * whether it is locked.
57 */
58
59 cpuid = mpidr_to_cpuid(read_mpidr_el1());
60 if (sys_read8(get_lock_addr(dev, id)) == cpuid) {
61 return 0;
62 }
63
64 sys_write8(cpuid, get_lock_addr(dev, id));
65 if (sys_read8(get_lock_addr(dev, id)) == cpuid) {
66 return 0;
67 }
68
69 return -EBUSY;
70 }
71
sqn_hwspinlock_lock(const struct device * dev,uint32_t id)72 static void sqn_hwspinlock_lock(const struct device *dev, uint32_t id)
73 {
74 const struct sqn_hwspinlock_config *config = dev->config;
75 uint8_t cpuid;
76
77 if (id > config->num_locks) {
78 LOG_ERR("unsupported hwspinlock id '%d'", id);
79 return;
80 }
81
82 /*
83 * Writing cpuid is equivalent to trying to lock HW spinlock, after
84 * which we check whether we've locked by reading the register value
85 * and comparing it with cpuid.
86 */
87
88 cpuid = mpidr_to_cpuid(read_mpidr_el1());
89 if (sys_read8(get_lock_addr(dev, id)) == 0) {
90 sys_write8(cpuid, get_lock_addr(dev, id));
91 }
92
93 while (sys_read8(get_lock_addr(dev, id)) != cpuid) {
94 k_busy_wait(CONFIG_SQN_HWSPINLOCK_RELAX_TIME);
95 sys_write8(cpuid, get_lock_addr(dev, id));
96 }
97 }
98
sqn_hwspinlock_unlock(const struct device * dev,uint32_t id)99 static void sqn_hwspinlock_unlock(const struct device *dev, uint32_t id)
100 {
101 const struct sqn_hwspinlock_config *config = dev->config;
102 uint8_t cpuid;
103
104 if (id > config->num_locks) {
105 LOG_ERR("unsupported hwspinlock id '%d'", id);
106 return;
107 }
108
109 /*
110 * If the HW spinlock register value is equal to the cpuid and we write
111 * the cpuid, then the register value will be 0. So to unlock the
112 * hwspinlock, we write cpuid.
113 */
114
115 cpuid = mpidr_to_cpuid(read_mpidr_el1());
116 sys_write8(cpuid, get_lock_addr(dev, id));
117 }
118
sqn_hwspinlock_get_max_id(const struct device * dev)119 static uint32_t sqn_hwspinlock_get_max_id(const struct device *dev)
120 {
121 const struct sqn_hwspinlock_config *config = dev->config;
122
123 return config->num_locks;
124 }
125
126 static DEVICE_API(hwspinlock, hwspinlock_api) = {
127 .trylock = sqn_hwspinlock_trylock,
128 .lock = sqn_hwspinlock_lock,
129 .unlock = sqn_hwspinlock_unlock,
130 .get_max_id = sqn_hwspinlock_get_max_id,
131 };
132
sqn_hwspinlock_init(const struct device * dev)133 static int sqn_hwspinlock_init(const struct device *dev)
134 {
135 DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
136
137 return 0;
138 }
139
140 #define SQN_HWSPINLOCK_INIT(idx) \
141 static struct sqn_hwspinlock_data sqn_hwspinlock##idx##_data; \
142 static const struct sqn_hwspinlock_config sqn_hwspinlock##idx##_config = { \
143 DEVICE_MMIO_ROM_INIT(DT_DRV_INST(idx)), \
144 .num_locks = DT_INST_PROP(idx, num_locks), \
145 }; \
146 DEVICE_DT_INST_DEFINE(idx, \
147 sqn_hwspinlock_init, \
148 NULL, \
149 &sqn_hwspinlock##idx##_data, \
150 &sqn_hwspinlock##idx##_config, \
151 PRE_KERNEL_1, CONFIG_HWSPINLOCK_INIT_PRIORITY, \
152 &hwspinlock_api)
153
154 DT_INST_FOREACH_STATUS_OKAY(SQN_HWSPINLOCK_INIT);
155