drivers: syscon: Add support for multiple regions

1. Add support for multiple syscon entries (as a side effect, this also
   fixed syscon.c implementations which weren't being linked to their
   syscon.h counterparts).
2. Add support for different width registers in syscon.
3. Add tests for syscon

Signed-off-by: Yuval Peress <peress@chromium.org>
This commit is contained in:
Yuval Peress 2021-08-03 23:43:58 -06:00 committed by Christopher Friedt
parent 805b8eae17
commit a1f6d97978
12 changed files with 359 additions and 52 deletions

View File

@ -194,7 +194,7 @@
/drivers/bbram/* @yperess @sjg20 @jackrosenthal
/drivers/bluetooth/ @joerchan @jhedberg @Vudentz
/drivers/cache/ @carlocaione
/drivers/syscon/ @carlocaione
/drivers/syscon/ @carlocaione @yperess
/drivers/can/ @alexanderwachter
/drivers/can/*mcp2515* @karstenkoenig
/drivers/can/*rcar* @julien-massot

View File

@ -30,10 +30,9 @@ config SYSCON_GENERIC
help
Enable generic SYSCON (System Controller) driver
config SYSCON_GENERIC_INIT_PRIORITY_DEVICE
int "SYSCON (System Controller) generic init device priority"
config SYSCON_INIT_PRIORITY
int "SYSCON (System Controller) driver init priority"
default 50
depends on SYSCON_GENERIC
help
This option controls the priority of the syscon device
initialization. Higher priority ensures that the device is

View File

@ -12,14 +12,11 @@
#include <drivers/syscon.h>
static const struct device *syscon_dev;
#include "syscon_common.h"
struct syscon_generic_config {
DEVICE_MMIO_ROM;
};
static struct syscon_generic_config syscon_generic_config_0 = {
DEVICE_MMIO_ROM_INIT(DT_DRV_INST(0)),
uint8_t reg_width;
};
struct syscon_generic_data {
@ -27,91 +24,124 @@ struct syscon_generic_data {
size_t size;
};
static struct syscon_generic_data syscon_generic_data_0;
uintptr_t syscon_generic_get_base(void)
static int syscon_generic_get_base(const struct device *dev, uintptr_t *addr)
{
if (!syscon_dev) {
if (!dev) {
return -ENODEV;
}
return DEVICE_MMIO_GET(syscon_dev);
}
static int sanitize_reg(uint16_t *reg, size_t reg_size)
{
/* Avoid unaligned readings */
*reg = ROUND_DOWN(*reg, sizeof(uint32_t));
/* Check for out-of-bounds readings */
if (*reg >= reg_size) {
return -EINVAL;
}
*addr = DEVICE_MMIO_GET(dev);
return 0;
}
int syscon_generic_read_reg(uint16_t reg, uint32_t *val)
static int syscon_generic_read_reg(const struct device *dev, uint16_t reg, uint32_t *val)
{
const struct syscon_generic_config *config;
struct syscon_generic_data *data;
uintptr_t base_address;
if (!syscon_dev) {
if (!dev) {
return -ENODEV;
}
data = syscon_dev->data;
data = dev->data;
config = dev->config;
if (!val) {
return -EINVAL;
}
if (sanitize_reg(&reg, data->size)) {
if (syscon_sanitize_reg(&reg, data->size, config->reg_width)) {
return -EINVAL;
}
base_address = DEVICE_MMIO_GET(syscon_dev);
base_address = DEVICE_MMIO_GET(dev);
*val = sys_read32(base_address + reg);
switch (config->reg_width) {
case 1:
*val = sys_read8(base_address + reg);
break;
case 2:
*val = sys_read16(base_address + reg);
break;
case 4:
*val = sys_read32(base_address + reg);
break;
default:
return -EINVAL;
}
return 0;
}
int syscon_generic_write_reg(uint16_t reg, uint32_t val)
static int syscon_generic_write_reg(const struct device *dev, uint16_t reg, uint32_t val)
{
const struct syscon_generic_config *config;
struct syscon_generic_data *data;
uintptr_t base_address;
if (!syscon_dev) {
if (!dev) {
return -ENODEV;
}
data = syscon_dev->data;
data = dev->data;
config = dev->config;
if (sanitize_reg(&reg, data->size)) {
if (syscon_sanitize_reg(&reg, data->size, config->reg_width)) {
return -EINVAL;
}
base_address = DEVICE_MMIO_GET(syscon_dev);
base_address = DEVICE_MMIO_GET(dev);
sys_write32(val, (base_address + reg));
switch (config->reg_width) {
case 1:
sys_write8(val, (base_address + reg));
break;
case 2:
sys_write16(val, (base_address + reg));
break;
case 4:
sys_write32(val, (base_address + reg));
break;
default:
return -EINVAL;
}
return 0;
}
int syscon_generic_init(const struct device *dev)
static int syscon_generic_get_size(const struct device *dev, size_t *size)
{
struct syscon_generic_data *data = dev->data;
syscon_dev = dev;
*size = data->size;
return 0;
}
DEVICE_MMIO_MAP(syscon_dev, K_MEM_CACHE_NONE);
static const struct syscon_driver_api syscon_generic_driver_api = {
.read = syscon_generic_read_reg,
.write = syscon_generic_write_reg,
.get_base = syscon_generic_get_base,
.get_size = syscon_generic_get_size,
};
data->size = DT_REG_SIZE(DT_DRV_INST(0));
static int syscon_generic_init(const struct device *dev)
{
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
return 0;
}
DEVICE_DT_INST_DEFINE(0, syscon_generic_init, NULL, &syscon_generic_data_0,
&syscon_generic_config_0, PRE_KERNEL_1,
CONFIG_SYSCON_GENERIC_INIT_PRIORITY_DEVICE, NULL);
#define SYSCON_INIT(inst) \
static const struct syscon_generic_config syscon_generic_config_##inst = { \
DEVICE_MMIO_ROM_INIT(DT_DRV_INST(inst)), \
.reg_width = DT_INST_PROP_OR(inst, reg_io_width, 4), \
}; \
static struct syscon_generic_data syscon_generic_data_##inst = { \
.size = DT_REG_SIZE(DT_DRV_INST(inst)), \
}; \
DEVICE_DT_INST_DEFINE(inst, syscon_generic_init, NULL, &syscon_generic_data_##inst, \
&syscon_generic_config_##inst, PRE_KERNEL_1, \
CONFIG_SYSCON_INIT_PRIORITY, &syscon_generic_driver_api);
DT_INST_FOREACH_STATUS_OKAY(SYSCON_INIT);

View File

@ -0,0 +1,41 @@
/*
* Copyright 2021 Google LLC.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef DRIVERS_SYSCON_SYSCON_COMMON_H_
#define DRIVERS_SYSCON_SYSCON_COMMON_H_
#include <sys/util.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Align and check register address
*
* @param reg Pointer to the register address in question.
* @param reg_size The size of the syscon register region.
* @param reg_width The width of a single register (in bytes).
* @return 0 if the register read is valid.
* @return -EINVAL is the read is invalid.
*/
static inline int syscon_sanitize_reg(uint16_t *reg, size_t reg_size, uint8_t reg_width)
{
/* Avoid unaligned readings */
*reg = ROUND_DOWN(*reg, reg_width);
/* Check for out-of-bounds readings */
if (*reg >= reg_size) {
return -EINVAL;
}
return 0;
}
#ifdef __cplusplus
}
#endif
#endif /* DRIVERS_SYSCON_SYSCON_COMMON_H_ */

View File

@ -10,3 +10,7 @@ include: base.yaml
properties:
reg:
required: true
reg-io-width:
type: int
required: false
description: The width of the registers in the syscon region in bytes. Default is 4 bytes.

View File

@ -26,38 +26,129 @@
extern "C" {
#endif
/**
* API template to get the base address of the syscon region.
*
* @see syscon_get_base
*/
typedef int (*syscon_api_get_base)(const struct device *dev, uintptr_t *addr);
/**
* API template to read a single register.
*
* @see syscon_read_reg
*/
typedef int (*syscon_api_read_reg)(const struct device *dev, uint16_t reg, uint32_t *val);
/**
* API template to write a single register.
*
* @see syscon_write_reg
*/
typedef int (*syscon_api_write_reg)(const struct device *dev, uint16_t reg, uint32_t val);
/**
* API template to get the size of the syscon register.
*
* @see syscon_get_size
*/
typedef int (*syscon_api_get_size)(const struct device *dev, size_t *size);
/**
* @brief System Control (syscon) register driver API
*/
__subsystem struct syscon_driver_api {
syscon_api_read_reg read;
syscon_api_write_reg write;
syscon_api_get_base get_base;
syscon_api_get_size get_size;
};
/**
* @brief Get the syscon base address
*
* This function returns the syscon base address
*
* @return 0 on error, the base address on success
* @param dev The device to get the register size for.
* @param addr Where to write the base address.
* @return 0 When addr was written to.
*/
uintptr_t syscon_get_base(void);
__syscall int syscon_get_base(const struct device *dev, uintptr_t *addr);
static inline int z_impl_syscon_get_base(const struct device *dev, uintptr_t *addr)
{
const struct syscon_driver_api *api = (const struct syscon_driver_api *)dev->api;
if (api == NULL) {
return -ENOTSUP;
}
return api->get_base(dev, addr);
}
/**
* @brief Read from syscon register
*
* This function reads from a specific register in the syscon area
*
* @param dev The device to get the register size for.
* @param reg The register offset
* @param val The returned value read from the syscon register
*
* @return 0 on success, negative on error
*/
int syscon_read_reg(uint16_t reg, uint32_t *val);
__syscall int syscon_read_reg(const struct device *dev, uint16_t reg, uint32_t *val);
static inline int z_impl_syscon_read_reg(const struct device *dev, uint16_t reg, uint32_t *val)
{
const struct syscon_driver_api *api = (const struct syscon_driver_api *)dev->api;
if (api == NULL) {
return -ENOTSUP;
}
return api->read(dev, reg, val);
}
/**
* @brief Write to syscon register
*
* This function writes to a specific register in the syscon area
*
* @param dev The device to get the register size for.
* @param reg The register offset
* @param val The value to be written in the register
*
* @return 0 on success, negative on error
*/
int syscon_write_reg(uint16_t reg, uint32_t val);
__syscall int syscon_write_reg(const struct device *dev, uint16_t reg, uint32_t val);
static inline int z_impl_syscon_write_reg(const struct device *dev, uint16_t reg, uint32_t val)
{
const struct syscon_driver_api *api = (const struct syscon_driver_api *)dev->api;
if (api == NULL) {
return -ENOTSUP;
}
return api->write(dev, reg, val);
}
/**
* Get the size of the syscon register in bytes.
*
* @param dev The device to get the register size for.
* @param size Pointer to write the size to.
* @return 0 for success.
*/
__syscall int syscon_get_size(const struct device *dev, size_t *size);
static inline int z_impl_syscon_get_size(const struct device *dev, size_t *size)
{
const struct syscon_driver_api *api = (const struct syscon_driver_api *)dev->api;
return api->get_size(dev, size);
}
/**
* @}
@ -67,4 +158,6 @@ int syscon_write_reg(uint16_t reg, uint32_t val);
}
#endif
#endif /* ZEPHYR_INCLUDE_DRIVERS_SYSCON_H_ */
#include <syscalls/syscon.h>
#endif /* ZEPHYR_INCLUDE_DRIVERS_SYSCON_H_ */

View File

@ -0,0 +1,10 @@
# Copyright 2021 Google LLC
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(syscon)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

View File

@ -0,0 +1,24 @@
/* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/ {
reserved-memory {
compatible = "reserved-memory";
#address-cells = <1>;
#size-cells = <1>;
status = "okay";
ranges;
res0: res@42000000 {
reg = <0x42000000 0x8>;
label = "res0";
};
};
syscon: syscon@42000000 {
compatible = "syscon";
status = "okay";
reg = <0x42000000 0x8>;
reg-io-width = <1>;
};
};

View File

@ -0,0 +1,23 @@
/*
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
#include <autoconf.h>
#include <linker/sections.h>
#include <devicetree.h>
#include <linker/linker-defs.h>
#include <linker/linker-tool.h>
MEMORY
{
DT_RESERVED_MEM_REGIONS()
}
SECTIONS
{
DT_RESERVED_MEM_SECTIONS()
}
#include <arch/arm64/scripts/linker.ld>

View File

@ -0,0 +1,8 @@
# Copyright 2021 Google LLC
# SPDX-License-Identifier: Apache-2.0
CONFIG_ZTEST=y
CONFIG_SYSCON=y
CONFIG_MMU=y
CONFIG_HAVE_CUSTOM_LINKER_SCRIPT=y
CONFIG_CUSTOM_LINKER_SCRIPT="linker_arm64_reserved.ld"

View File

@ -0,0 +1,67 @@
/* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
#include <device.h>
#include <devicetree.h>
#include <drivers/syscon.h>
#include <ztest.h>
uint8_t var_in_res0[DT_REG_SIZE(DT_NODELABEL(syscon))] __attribute((__section__(".res0")));
static void test_size(void)
{
const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(syscon));
const size_t expected_size = DT_REG_SIZE(DT_NODELABEL(syscon));
size_t size;
zassert_not_null(dev, NULL);
zassert_ok(syscon_get_size(dev, &size), NULL);
zassert_equal(size, expected_size, "size(0x%x) != expected_size(0x%x)", size,
expected_size);
}
static void test_out_of_bounds(void)
{
const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(syscon));
uint32_t val;
zassert_equal(syscon_read_reg(dev, DT_REG_SIZE(DT_NODELABEL(syscon)), &val), -EINVAL, NULL);
zassert_equal(syscon_write_reg(dev, DT_REG_SIZE(DT_NODELABEL(syscon)), val), -EINVAL, NULL);
}
static void test_read(void)
{
const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(syscon));
uintptr_t base_addr;
uint32_t val;
zassert_ok(syscon_get_base(dev, &base_addr), NULL);
for (size_t i = 0; i < ARRAY_SIZE(var_in_res0); ++i) {
((uint8_t *)base_addr)[i] = i;
zassert_ok(syscon_read_reg(dev, i, &val), NULL);
zassert_equal(i, val, NULL);
}
}
static void test_write(void)
{
const struct device *dev = DEVICE_DT_GET(DT_NODELABEL(syscon));
uintptr_t base_addr;
zassert_ok(syscon_get_base(dev, &base_addr), NULL);
for (uint32_t i = 0; i < ARRAY_SIZE(var_in_res0); ++i) {
zassert_ok(syscon_write_reg(dev, i, i), NULL);
zassert_equal(((uint8_t *)base_addr)[i], i, NULL);
}
}
void test_main(void)
{
ztest_test_suite(syscon,
ztest_unit_test(test_size),
ztest_unit_test(test_out_of_bounds),
ztest_unit_test(test_read),
ztest_unit_test(test_write));
ztest_run_test_suite(syscon);
}

View File

@ -0,0 +1,8 @@
# Copyright 2021 Google LLC
# SPDX-License-Identifier: Apache-2.0
tests:
drivers.syscon:
tags: drivers syscon
harness: ztest
platform_allow: qemu_cortex_a53