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 	 * If the register value is equal to cpuid, this means that the current
53 	 * core has already locked the HW spinlock.
54 	 * If not, we try to lock the HW spinlock by writing cpuid, then check
55 	 * whether it is locked.
56 	 */
57 
58 	cpuid = mpidr_to_cpuid(read_mpidr_el1());
59 	if (sys_read8(get_lock_addr(dev, id)) == cpuid)
60 		return 0;
61 
62 	sys_write8(cpuid, get_lock_addr(dev, id));
63 	if (sys_read8(get_lock_addr(dev, id)) == cpuid)
64 		return 0;
65 
66 	return -EBUSY;
67 }
68 
sqn_hwspinlock_lock(const struct device * dev,uint32_t id)69 static void sqn_hwspinlock_lock(const struct device *dev, uint32_t id)
70 {
71 	const struct sqn_hwspinlock_config *config = dev->config;
72 	uint8_t cpuid;
73 
74 	if (id > config->num_locks) {
75 		LOG_ERR("unsupported hwspinlock id '%d'", id);
76 		return;
77 	}
78 
79 	/*
80 	 * Writing cpuid is equivalent to trying to lock HW spinlock, after
81 	 * which we check whether we've locked by reading the register value
82 	 * and comparing it with cpuid.
83 	 */
84 
85 	cpuid = mpidr_to_cpuid(read_mpidr_el1());
86 	if (sys_read8(get_lock_addr(dev, id)) == 0) {
87 		sys_write8(cpuid, get_lock_addr(dev, id));
88 	}
89 
90 	while (sys_read8(get_lock_addr(dev, id)) != cpuid) {
91 		k_busy_wait(CONFIG_SQN_HWSPINLOCK_RELAX_TIME);
92 		sys_write8(cpuid, get_lock_addr(dev, id));
93 	}
94 }
95 
sqn_hwspinlock_unlock(const struct device * dev,uint32_t id)96 static void sqn_hwspinlock_unlock(const struct device *dev, uint32_t id)
97 {
98 	const struct sqn_hwspinlock_config *config = dev->config;
99 	uint8_t cpuid;
100 
101 	if (id > config->num_locks) {
102 		LOG_ERR("unsupported hwspinlock id '%d'", id);
103 		return;
104 	}
105 
106 	/*
107 	 * If the HW spinlock register value is equal to the cpuid and we write
108 	 * the cpuid, then the register value will be 0. So to unlock the
109 	 * hwspinlock, we write cpuid.
110 	 */
111 
112 	cpuid = mpidr_to_cpuid(read_mpidr_el1());
113 	sys_write8(cpuid, get_lock_addr(dev, id));
114 }
115 
sqn_hwspinlock_get_max_id(const struct device * dev)116 static uint32_t sqn_hwspinlock_get_max_id(const struct device *dev)
117 {
118 	const struct sqn_hwspinlock_config *config = dev->config;
119 
120 	return config->num_locks;
121 }
122 
123 static const struct hwspinlock_driver_api hwspinlock_api = {
124 	.trylock = sqn_hwspinlock_trylock,
125 	.lock = sqn_hwspinlock_lock,
126 	.unlock = sqn_hwspinlock_unlock,
127 	.get_max_id = sqn_hwspinlock_get_max_id,
128 };
129 
sqn_hwspinlock_init(const struct device * dev)130 static int sqn_hwspinlock_init(const struct device *dev)
131 {
132 	DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
133 
134 	return 0;
135 }
136 
137 #define SQN_HWSPINLOCK_INIT(idx)                                                \
138 	static struct sqn_hwspinlock_data sqn_hwspinlock##idx##_data;           \
139 	static const struct sqn_hwspinlock_config sqn_hwspinlock##idx##_config = {    \
140 		DEVICE_MMIO_ROM_INIT(DT_DRV_INST(idx)),                         \
141 		.num_locks = DT_INST_PROP(idx, num_locks),                      \
142 	};                                                                      \
143 	DEVICE_DT_INST_DEFINE(idx,                                              \
144 			      sqn_hwspinlock_init,                              \
145 			      NULL,                                             \
146 			      &sqn_hwspinlock##idx##_data,                      \
147 			      &sqn_hwspinlock##idx##_config,                    \
148 			      PRE_KERNEL_1, CONFIG_HWSPINLOCK_INIT_PRIORITY,    \
149 			      &hwspinlock_api)
150 
151 DT_INST_FOREACH_STATUS_OKAY(SQN_HWSPINLOCK_INIT);
152