| /* |
| * Copyright (c) 2023 Sequans Communications |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT sqn_hwspinlock |
| |
| #include <zephyr/device.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/sys/sys_io.h> |
| #include <zephyr/drivers/hwspinlock.h> |
| |
| #include <zephyr/sys/printk.h> |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(sqn_hwspinlock); |
| |
| struct sqn_hwspinlock_data { |
| DEVICE_MMIO_RAM; |
| }; |
| |
| struct sqn_hwspinlock_config { |
| DEVICE_MMIO_ROM; |
| uint32_t num_locks; |
| }; |
| |
| static inline mem_addr_t get_lock_addr(const struct device *dev, uint32_t id) |
| { |
| return (mem_addr_t)(DEVICE_MMIO_GET(dev) + id * sizeof(uint32_t)); |
| } |
| |
| /* |
| * To define CPU id, we use the affinity2 and affinity1 |
| * fields of the MPIDR register. |
| */ |
| static uint8_t mpidr_to_cpuid(uint64_t mpidr_val) |
| { |
| uint8_t cpuid = ((mpidr_val >> 8) & 0x0F) | ((mpidr_val >> 12) & 0xF0); |
| |
| return ++cpuid; |
| } |
| |
| static int sqn_hwspinlock_trylock(const struct device *dev, uint32_t id) |
| { |
| const struct sqn_hwspinlock_config *config = dev->config; |
| uint8_t cpuid; |
| |
| if (id > config->num_locks) { |
| return -EINVAL; |
| } |
| |
| /* |
| * If the register value is equal to cpuid, this means that the current |
| * core has already locked the HW spinlock. |
| * If not, we try to lock the HW spinlock by writing cpuid, then check |
| * whether it is locked. |
| */ |
| |
| cpuid = mpidr_to_cpuid(read_mpidr_el1()); |
| if (sys_read8(get_lock_addr(dev, id)) == cpuid) { |
| return 0; |
| } |
| |
| sys_write8(cpuid, get_lock_addr(dev, id)); |
| if (sys_read8(get_lock_addr(dev, id)) == cpuid) { |
| return 0; |
| } |
| |
| return -EBUSY; |
| } |
| |
| static void sqn_hwspinlock_lock(const struct device *dev, uint32_t id) |
| { |
| const struct sqn_hwspinlock_config *config = dev->config; |
| uint8_t cpuid; |
| |
| if (id > config->num_locks) { |
| LOG_ERR("unsupported hwspinlock id '%d'", id); |
| return; |
| } |
| |
| /* |
| * Writing cpuid is equivalent to trying to lock HW spinlock, after |
| * which we check whether we've locked by reading the register value |
| * and comparing it with cpuid. |
| */ |
| |
| cpuid = mpidr_to_cpuid(read_mpidr_el1()); |
| if (sys_read8(get_lock_addr(dev, id)) == 0) { |
| sys_write8(cpuid, get_lock_addr(dev, id)); |
| } |
| |
| while (sys_read8(get_lock_addr(dev, id)) != cpuid) { |
| k_busy_wait(CONFIG_SQN_HWSPINLOCK_RELAX_TIME); |
| sys_write8(cpuid, get_lock_addr(dev, id)); |
| } |
| } |
| |
| static void sqn_hwspinlock_unlock(const struct device *dev, uint32_t id) |
| { |
| const struct sqn_hwspinlock_config *config = dev->config; |
| uint8_t cpuid; |
| |
| if (id > config->num_locks) { |
| LOG_ERR("unsupported hwspinlock id '%d'", id); |
| return; |
| } |
| |
| /* |
| * If the HW spinlock register value is equal to the cpuid and we write |
| * the cpuid, then the register value will be 0. So to unlock the |
| * hwspinlock, we write cpuid. |
| */ |
| |
| cpuid = mpidr_to_cpuid(read_mpidr_el1()); |
| sys_write8(cpuid, get_lock_addr(dev, id)); |
| } |
| |
| static uint32_t sqn_hwspinlock_get_max_id(const struct device *dev) |
| { |
| const struct sqn_hwspinlock_config *config = dev->config; |
| |
| return config->num_locks; |
| } |
| |
| static const struct hwspinlock_driver_api hwspinlock_api = { |
| .trylock = sqn_hwspinlock_trylock, |
| .lock = sqn_hwspinlock_lock, |
| .unlock = sqn_hwspinlock_unlock, |
| .get_max_id = sqn_hwspinlock_get_max_id, |
| }; |
| |
| static int sqn_hwspinlock_init(const struct device *dev) |
| { |
| DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); |
| |
| return 0; |
| } |
| |
| #define SQN_HWSPINLOCK_INIT(idx) \ |
| static struct sqn_hwspinlock_data sqn_hwspinlock##idx##_data; \ |
| static const struct sqn_hwspinlock_config sqn_hwspinlock##idx##_config = { \ |
| DEVICE_MMIO_ROM_INIT(DT_DRV_INST(idx)), \ |
| .num_locks = DT_INST_PROP(idx, num_locks), \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(idx, \ |
| sqn_hwspinlock_init, \ |
| NULL, \ |
| &sqn_hwspinlock##idx##_data, \ |
| &sqn_hwspinlock##idx##_config, \ |
| PRE_KERNEL_1, CONFIG_HWSPINLOCK_INIT_PRIORITY, \ |
| &hwspinlock_api) |
| |
| DT_INST_FOREACH_STATUS_OKAY(SQN_HWSPINLOCK_INIT); |