From a1e2fdcc4d2ea5904d5d650dc427cbd6b2fbec4e Mon Sep 17 00:00:00 2001 From: Aleksandr Senin Date: Sun, 10 Sep 2023 17:56:31 +0300 Subject: [PATCH] drivers: mdio: add bit-bang driver Add MDIO driver that works through GPIO pins. This driver is useful when a microcontroller doesn't have MDIO bus or when multiple separate MDIO buses are required. The driver provides access to the MDIO bus through GPIO pins for any SoC that has GPIO pin control available. Signed-off-by: Aleksandr Senin --- drivers/mdio/CMakeLists.txt | 1 + drivers/mdio/Kconfig | 1 + drivers/mdio/Kconfig.gpio | 9 ++ drivers/mdio/mdio_gpio.c | 195 ++++++++++++++++++++++++ drivers/mdio/mdio_shell.c | 2 + dts/bindings/mdio/zephyr,mdio-gpio.yaml | 19 +++ 6 files changed, 227 insertions(+) create mode 100644 drivers/mdio/Kconfig.gpio create mode 100644 drivers/mdio/mdio_gpio.c create mode 100644 dts/bindings/mdio/zephyr,mdio-gpio.yaml diff --git a/drivers/mdio/CMakeLists.txt b/drivers/mdio/CMakeLists.txt index 7fc03a8fc8f..4972626cdb5 100644 --- a/drivers/mdio/CMakeLists.txt +++ b/drivers/mdio/CMakeLists.txt @@ -7,3 +7,4 @@ zephyr_library_sources_ifdef(CONFIG_MDIO_ATMEL_SAM mdio_sam.c) zephyr_library_sources_ifdef(CONFIG_MDIO_ESP32 mdio_esp32.c) zephyr_library_sources_ifdef(CONFIG_MDIO_NXP_S32_NETC mdio_nxp_s32_netc.c) zephyr_library_sources_ifdef(CONFIG_MDIO_ADIN2111 mdio_adin2111.c) +zephyr_library_sources_ifdef(CONFIG_MDIO_GPIO mdio_gpio.c) diff --git a/drivers/mdio/Kconfig b/drivers/mdio/Kconfig index 0f2d3b84a84..ddca38359fa 100644 --- a/drivers/mdio/Kconfig +++ b/drivers/mdio/Kconfig @@ -29,6 +29,7 @@ source "drivers/mdio/Kconfig.esp32" source "drivers/mdio/Kconfig.sam" source "drivers/mdio/Kconfig.nxp_s32" source "drivers/mdio/Kconfig.adin2111" +source "drivers/mdio/Kconfig.gpio" config MDIO_INIT_PRIORITY int "Init priority" diff --git a/drivers/mdio/Kconfig.gpio b/drivers/mdio/Kconfig.gpio new file mode 100644 index 00000000000..1ab5dd70dc4 --- /dev/null +++ b/drivers/mdio/Kconfig.gpio @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Aleksandr Senin +# SPDX-License-Identifier: Apache-2.0 + +config MDIO_GPIO + bool "GPIO bitbang MDIO controller driver" + default y + depends on DT_HAS_ZEPHYR_MDIO_GPIO_ENABLED + help + Enable software driven (bit banging) MDIO support using GPIO pins diff --git a/drivers/mdio/mdio_gpio.c b/drivers/mdio/mdio_gpio.c new file mode 100644 index 00000000000..46b99963d3c --- /dev/null +++ b/drivers/mdio/mdio_gpio.c @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2023 Aleksandr Senin + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_mdio_gpio + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(mdio_gpio, CONFIG_MDIO_LOG_LEVEL); + +#define MDIO_GPIO_READ_OP 0 +#define MDIO_GPIO_WRITE_OP 1 +#define MDIO_GPIO_MSB 0x80000000 + +struct mdio_gpio_data { + struct k_sem sem; +}; + +struct mdio_gpio_config { + struct gpio_dt_spec mdc_gpio; + struct gpio_dt_spec mdio_gpio; +}; + +static ALWAYS_INLINE void mdio_gpio_clock_the_bit(const struct mdio_gpio_config *dev_cfg) +{ + k_busy_wait(1); + gpio_pin_set_dt(&dev_cfg->mdc_gpio, 1); + k_busy_wait(1); + gpio_pin_set_dt(&dev_cfg->mdc_gpio, 0); +} + +static ALWAYS_INLINE void mdio_gpio_dir(const struct mdio_gpio_config *dev_cfg, uint8_t dir) +{ + gpio_pin_configure_dt(&dev_cfg->mdio_gpio, dir ? GPIO_OUTPUT_ACTIVE : GPIO_INPUT); + if (dir == 0) { + mdio_gpio_clock_the_bit(dev_cfg); + } +} + +static ALWAYS_INLINE void mdio_gpio_read(const struct mdio_gpio_config *dev_cfg, uint16_t *pdata) +{ + uint16_t data = 0; + + for (uint16_t i = 0; i < 16; i++) { + data <<= 1; + mdio_gpio_clock_the_bit(dev_cfg); + if (gpio_pin_get_dt(&dev_cfg->mdio_gpio) == 1) { + data |= 1; + } + } + + *pdata = data; +} + +static ALWAYS_INLINE void mdio_gpio_write(const struct mdio_gpio_config *dev_cfg, + uint32_t data, uint8_t len) +{ + uint32_t v_data = data; + uint32_t v_len = len; + + v_data <<= 32 - v_len; + for (; v_len > 0; v_len--) { + gpio_pin_set_dt(&dev_cfg->mdio_gpio, (v_data & MDIO_GPIO_MSB) ? 1 : 0); + mdio_gpio_clock_the_bit(dev_cfg); + v_data <<= 1; + } +} + +static int mdio_gpio_transfer(const struct device *dev, uint8_t prtad, uint8_t devad, uint8_t rw, + uint16_t data_in, uint16_t *data_out) +{ + const struct mdio_gpio_config *const dev_cfg = dev->config; + struct mdio_gpio_data *const dev_data = dev->data; + + k_sem_take(&dev_data->sem, K_FOREVER); + + /* DIR: output */ + mdio_gpio_dir(dev_cfg, MDIO_GPIO_WRITE_OP); + /* PRE32: 32 bits '1' for sync*/ + mdio_gpio_write(dev_cfg, 0xFFFFFFFF, 32); + /* ST: 2 bits start of frame */ + mdio_gpio_write(dev_cfg, 0x1, 2); + /* OP: 2 bits opcode, read '10' or write '01' */ + mdio_gpio_write(dev_cfg, rw ? 0x1 : 0x2, 2); + /* PA5: 5 bits PHY address */ + mdio_gpio_write(dev_cfg, prtad, 5); + /* RA5: 5 bits register address */ + mdio_gpio_write(dev_cfg, devad, 5); + + if (rw) { /* Write data */ + /* TA: 2 bits turn-around */ + mdio_gpio_write(dev_cfg, 0x2, 2); + mdio_gpio_write(dev_cfg, data_in, 16); + } else { /* Read data */ + /* Release the MDIO line */ + mdio_gpio_dir(dev_cfg, MDIO_GPIO_READ_OP); + mdio_gpio_read(dev_cfg, data_out); + } + + /* DIR: input. Tristate MDIO line */ + mdio_gpio_dir(dev_cfg, MDIO_GPIO_READ_OP); + + k_sem_give(&dev_data->sem); + + return 0; +} + +static int mdio_gpio_read_mmi(const struct device *dev, uint8_t prtad, uint8_t devad, + uint16_t *data) +{ + return mdio_gpio_transfer(dev, prtad, devad, MDIO_GPIO_READ_OP, 0, data); +} + +static int mdio_gpio_write_mmi(const struct device *dev, uint8_t prtad, uint8_t devad, + uint16_t data) +{ + return mdio_gpio_transfer(dev, prtad, devad, MDIO_GPIO_WRITE_OP, data, NULL); +} + +static int mdio_gpio_initialize(const struct device *dev) +{ + const struct mdio_gpio_config *const dev_cfg = dev->config; + struct mdio_gpio_data *const dev_data = dev->data; + int rc; + + k_sem_init(&dev_data->sem, 1, 1); + + if (!device_is_ready(dev_cfg->mdc_gpio.port)) { + LOG_ERR("GPIO port for MDC pin is not ready"); + return -ENODEV; + } + + if (!device_is_ready(dev_cfg->mdio_gpio.port)) { + LOG_ERR("GPIO port for MDIO pin is not ready"); + return -ENODEV; + } + + rc = gpio_pin_configure_dt(&dev_cfg->mdc_gpio, GPIO_OUTPUT_INACTIVE); + if (rc < 0) { + LOG_ERR("Couldn't configure MDC pin; (%d)", rc); + return rc; + } + + rc = gpio_pin_configure_dt(&dev_cfg->mdio_gpio, GPIO_INPUT); + if (rc < 0) { + LOG_ERR("Couldn't configure MDIO pin; (%d)", rc); + return rc; + } + + return 0; +} + +static void mdio_gpio_bus_enable(const struct device *dev) +{ + ARG_UNUSED(dev); +} + +static void mdio_gpio_bus_disable(const struct device *dev) +{ + ARG_UNUSED(dev); +} + +static const struct mdio_driver_api mdio_gpio_driver_api = { + .read = mdio_gpio_read_mmi, + .write = mdio_gpio_write_mmi, + .bus_enable = mdio_gpio_bus_enable, + .bus_disable = mdio_gpio_bus_disable, +}; + +#define MDIO_GPIO_CONFIG(inst) \ + static struct mdio_gpio_config mdio_gpio_dev_config_##inst = { \ + .mdc_gpio = GPIO_DT_SPEC_INST_GET(inst, mdc_gpios), \ + .mdio_gpio = GPIO_DT_SPEC_INST_GET(inst, mdio_gpios), \ + }; + +#define MDIO_GPIO_PROTOCOL_ASSERT(inst) \ + BUILD_ASSERT(DT_INST_ENUM_IDX(inst, protocol) == CLAUSE_22, \ + "MDIO GPIO only supports CLAUSE_22 protocol") + +#define MDIO_GPIO_DEVICE(inst) \ + MDIO_GPIO_PROTOCOL_ASSERT(inst); \ + MDIO_GPIO_CONFIG(inst); \ + static struct mdio_gpio_data mdio_gpio_dev_data_##inst; \ + DEVICE_DT_INST_DEFINE(inst, &mdio_gpio_initialize, NULL, &mdio_gpio_dev_data_##inst, \ + &mdio_gpio_dev_config_##inst, POST_KERNEL, \ + CONFIG_MDIO_INIT_PRIORITY, &mdio_gpio_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(MDIO_GPIO_DEVICE) diff --git a/drivers/mdio/mdio_shell.c b/drivers/mdio/mdio_shell.c index 090cde16ece..9ca57e12c3e 100644 --- a/drivers/mdio/mdio_shell.c +++ b/drivers/mdio/mdio_shell.c @@ -24,6 +24,8 @@ LOG_MODULE_REGISTER(mdio_shell, CONFIG_LOG_DEFAULT_LEVEL); #define DT_DRV_COMPAT adi_adin2111_mdio #elif DT_HAS_COMPAT_STATUS_OKAY(smsc_lan91c111_mdio) #define DT_DRV_COMPAT smsc_lan91c111_mdio +#elif DT_HAS_COMPAT_STATUS_OKAY(zephyr_mdio_gpio) +#define DT_DRV_COMPAT zephyr_mdio_gpio #else #error "No known devicetree compatible match for MDIO shell" #endif diff --git a/dts/bindings/mdio/zephyr,mdio-gpio.yaml b/dts/bindings/mdio/zephyr,mdio-gpio.yaml new file mode 100644 index 00000000000..6e847f969e4 --- /dev/null +++ b/dts/bindings/mdio/zephyr,mdio-gpio.yaml @@ -0,0 +1,19 @@ +# Copyright (c) 2023 Aleksandr Senin +# SPDX-License-Identifier: Apache-2.0 + +description: Zephyr MDIO bitbang driver + +compatible: "zephyr,mdio-gpio" + +include: mdio-controller.yaml + +properties: + mdc-gpios: + type: phandle-array + required: true + description: GPIO pin for the MDC + + mdio-gpios: + type: phandle-array + required: true + description: GPIO pin for the MDIO