From 15c0ca757dccdc0f1b8416b1064c5264e56284e3 Mon Sep 17 00:00:00 2001 From: Robert Slawinski Date: Wed, 9 Oct 2024 12:29:57 +0200 Subject: [PATCH] drivers: dm8806: link speed change interrupt handling On the interrupt handling, one thread per driver instance is involved into monitoring the semaphor, sends inside the gpio callback triggered by the gpio interrupt. Each time, when the link parameters are change, the DM8806 is generating the gpio interrupt. After getting semaphor, the application callback function which was linked during initialization process is called to get the new link parameters with standard API calls (cherry picked from commit ef6f804d8f2648935afadaab09b511913ffd9f4c) Original-Signed-off-by: Robert Slawinski GitOrigin-RevId: ef6f804d8f2648935afadaab09b511913ffd9f4c Cr-Build-Id: 8729035348064486593 Cr-Build-Url: https://cr-buildbucket.appspot.com/build/8729035348064486593 Copybot-Job-Name: zephyr-main-copybot-downstream Change-Id: I5c7bdc3ff391a3afbb72b084d12583e33f679512 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/zephyr/+/6079808 Commit-Queue: Jeremy Bettis Tested-by: Jeremy Bettis Tested-by: ChromeOS Prod (Robot) Reviewed-by: Jeremy Bettis --- drivers/ethernet/phy/Kconfig | 8 +- drivers/ethernet/phy/Kconfig.dm8806 | 49 +++++ drivers/ethernet/phy/phy_dm8806.c | 243 ++++++++++++++++++++++--- drivers/ethernet/phy/phy_dm8806_priv.h | 73 ++++++++ 4 files changed, 345 insertions(+), 28 deletions(-) create mode 100644 drivers/ethernet/phy/Kconfig.dm8806 diff --git a/drivers/ethernet/phy/Kconfig b/drivers/ethernet/phy/Kconfig index cf910d84643..dfb9862aca7 100644 --- a/drivers/ethernet/phy/Kconfig +++ b/drivers/ethernet/phy/Kconfig @@ -15,6 +15,7 @@ module-str = Log level for Ethernet PHY driver module-help = Sets log level for Ethernet PHY Device Drivers. source "subsys/net/Kconfig.template.log_config.net" source "drivers/ethernet/phy/Kconfig.tja1103" +source "drivers/ethernet/phy/Kconfig.dm8806" config PHY_INIT_PRIORITY int "Ethernet PHY driver init priority" @@ -40,13 +41,6 @@ config PHY_ADIN2111 help Enable ADIN2111 PHY driver. -config PHY_DM8806 - bool "DM8806 PHY driver" - default y - depends on DT_HAS_DAVICOM_DM8806_PHY_ENABLED - help - Enable DM8806 PHY driver. - config PHY_MICROCHIP_KSZ8081 bool "Microchip KSZ8081 PHY Driver" default y diff --git a/drivers/ethernet/phy/Kconfig.dm8806 b/drivers/ethernet/phy/Kconfig.dm8806 new file mode 100644 index 00000000000..bab9a5d0ad8 --- /dev/null +++ b/drivers/ethernet/phy/Kconfig.dm8806 @@ -0,0 +1,49 @@ +# Copyright 2024 Robert Slawinski +# SPDX-License-Identifier: Apache-2.0 + +# Davicom PHY DM8806 driver configuration options + +menuconfig PHY_DM8806 + bool "Davicom PHY DM8806 driver" + default y + depends on DT_HAS_DAVICOM_DM8806_PHY_ENABLED + depends on MDIO + help + Enable driver for Davicom DM8806 PHY. + +if PHY_DM8806 + +choice PHY_DM8806_TRIGGER_MODE + prompt "Trigger mode" + default PHY_DM8806_TRIGGER_GLOBAL_THREAD + help + Specify the type of triggering to be used by the driver. + +config PHY_DM8806_TRIGGER_NONE + bool "No trigger" + +config PHY_DM8806_TRIGGER_GLOBAL_THREAD + bool "Use global thread" + depends on GPIO + select PHY_DM8806_TRIGGER + +endchoice + +config PHY_DM8806_TRIGGER + bool + +config PHY_DM8806_THREAD_PRIORITY + int "Thread priority" + depends on PHY_DM8806_TRIGGER_GLOBAL_THREAD + default 13 + help + Priority of thread used by the driver to handle interrupts. + +config PHY_DM8806_THREAD_STACK_SIZE + int "Thread stack size" + depends on PHY_DM8806_TRIGGER_GLOBAL_THREAD + default 1024 + help + Stack size of thread used by the driver to handle interrupts. + +endif # PHY_DM8806_TRIGGER diff --git a/drivers/ethernet/phy/phy_dm8806.c b/drivers/ethernet/phy/phy_dm8806.c index c4058a9fa46..76ae387316d 100644 --- a/drivers/ethernet/phy/phy_dm8806.c +++ b/drivers/ethernet/phy/phy_dm8806.c @@ -1,4 +1,6 @@ /* + * DM8806 Stand-alone Ethernet PHY with RMII + * * Copyright (c) 2024 Robert Slawinski * * SPDX-License-Identifier: Apache-2.0 @@ -30,18 +32,181 @@ struct phy_dm8806_config { struct phy_dm8806_data { const struct device *dev; struct phy_link_state state; - struct k_sem sem; - struct k_work_delayable monitor_work; - phy_callback_t cb; + phy_callback_t link_speed_chenge_cb; void *cb_data; + struct gpio_callback gpio_cb; +#ifdef CONFIG_PHY_DM8806_TRIGGER + K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_PHY_DM8806_THREAD_STACK_SIZE); + struct k_thread thread; + struct k_sem gpio_sem; +#endif }; +static void phy_dm8806_gpio_callback(const struct device *dev, struct gpio_callback *cb, + uint32_t pins) +{ + ARG_UNUSED(pins); + struct phy_dm8806_data *drv_data = CONTAINER_OF(cb, struct phy_dm8806_data, gpio_cb); + const struct phy_dm8806_config *cfg = drv_data->dev->config; + + gpio_pin_interrupt_configure_dt(&cfg->gpio_int, GPIO_INT_DISABLE); + k_sem_give(&drv_data->gpio_sem); +} + +static void phy_dm8806_thread_cb(const struct device *dev, struct phy_link_state *state, + void *cb_data) +{ + uint16_t data; + struct phy_dm8806_data *drv_data = dev->data; + const struct phy_dm8806_config *cfg = dev->config; + + if (drv_data->link_speed_chenge_cb != NULL) { + drv_data->link_speed_chenge_cb(dev, state, cb_data); + } + /* Clear the interrupt flag, by writing "1" to LNKCHG bit of Interrupt Status + * Register (318h) + */ + mdio_read(cfg->mdio, INT_STAT_PHY_ADDR, INT_STAT_REG_ADDR, &data); + data |= 0x1; + mdio_write(cfg->mdio, INT_STAT_PHY_ADDR, INT_STAT_REG_ADDR, data); + gpio_pin_interrupt_configure_dt(&cfg->gpio_int, GPIO_INT_EDGE_TO_ACTIVE); +} + +static void phy_dm8806_thread(void *p1, void *p2, void *p3) +{ + struct phy_dm8806_data *drv_data = p1; + void *cb_data = p2; + struct phy_link_state *state = p3; + + while (1) { + k_sem_take(&drv_data->gpio_sem, K_FOREVER); + phy_dm8806_thread_cb(drv_data->dev, state, cb_data); + } +} + +int phy_dm8806_port_init(const struct device *dev) +{ + int res; + const struct phy_dm8806_config *cfg = dev->config; + + res = gpio_pin_configure_dt(&cfg->gpio_rst, (GPIO_OUTPUT_INACTIVE | GPIO_PULL_UP)); + if (res != 0) { + LOG_ERR("Failed to configure gpio reset pin for PHY DM886 as an output"); + return res; + } + /* Hardware reset of the PHY DM8806 */ + gpio_pin_set_dt(&cfg->gpio_rst, true); + if (res != 0) { + LOG_ERR("Failed to assert gpio reset pin of the PHY DM886 to physical 0"); + return res; + } + /* According to DM8806 datasheet (DM8806-DAVICOM.pdf), low active state on + * the reset pin must remain minimum 10ms to perform hardware reset. + */ + k_msleep(10); + res = gpio_pin_set_dt(&cfg->gpio_rst, false); + if (res != 0) { + LOG_ERR("Failed to assert gpio reset pin of the PHY DM886 to physical 1"); + return res; + } + + return res; +} + +int phy_dm8806_init_interrupt(const struct device *dev) +{ + int res = 0; + uint16_t data; + struct phy_dm8806_data *drv_data = dev->data; + void *cb_data = drv_data->cb_data; + const struct phy_dm8806_config *cfg = dev->config; + + /* Configure Davicom PHY DM8806 interrupts: + * Activate global interrupt by writing "1" to LNKCHG of Interrupt Mask + * And Control Register (319h) + */ + res = mdio_read(cfg->mdio, INT_MASK_CTRL_PHY_ADDR, INT_MASK_CTRL_REG_ADDR, &data); + if (res) { + LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", res); + return res; + } + data |= 0x1; + res = mdio_write(cfg->mdio, INT_MASK_CTRL_PHY_ADDR, INT_MASK_CTRL_REG_ADDR, data); + if (res) { + LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", res); + return res; + } + + /* Activate interrupt per Ethernet port by writing "1" to LNK_EN0~3 + * of WoL Control Register (2BBh) + */ + res = mdio_read(cfg->mdio, WOLL_CTRL_REG_PHY_ADDR, WOLL_CTRL_REG_REG_ADDR, &data); + if (res) { + LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", res); + return res; + } + data |= 0xF; + res = mdio_write(cfg->mdio, WOLL_CTRL_REG_PHY_ADDR, WOLL_CTRL_REG_REG_ADDR, data); + if (res) { + LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", res); + return res; + } + + /* Configure external interrupts: + * Configure interrupt pin to recognize the rising edge on the Davicom + * PHY DM8806 as external interrupt + */ + if (device_is_ready(cfg->gpio_int.port) != true) { + LOG_ERR("gpio_int gpio not ready"); + return -ENODEV; + } + drv_data->dev = dev; + res = gpio_pin_configure_dt(&cfg->gpio_int, GPIO_INPUT); + if (res != 0) { + LOG_ERR("Failed to configure gpio interrupt pin for PHY DM886 as an input"); + return res; + } + /* Assign callback function to be fired by Davicom PHY DM8806 external + * interrupt pin + */ + gpio_init_callback(&drv_data->gpio_cb, phy_dm8806_gpio_callback, BIT(cfg->gpio_int.pin)); + res = gpio_add_callback(cfg->gpio_int.port, &drv_data->gpio_cb); + if (res != 0) { + LOG_ERR("Failed to set PHY DM886 gpio callback"); + return res; + } + k_sem_init(&drv_data->gpio_sem, 0, K_SEM_MAX_LIMIT); + k_thread_create(&drv_data->thread, drv_data->thread_stack, + CONFIG_PHY_DM8806_THREAD_STACK_SIZE, phy_dm8806_thread, drv_data, cb_data, + NULL, K_PRIO_COOP(CONFIG_PHY_DM8806_THREAD_PRIORITY), 0, K_NO_WAIT); + /* Configure GPIO interrupt to be triggered on pin state change to logical + * level 1 asserted by Davicom PHY DM8806 interrupt Pin + */ + gpio_pin_interrupt_configure_dt(&cfg->gpio_int, GPIO_INT_EDGE_TO_ACTIVE); + if (res != 0) { + LOG_ERR("Failed to configure PHY DM886 gpio interrupt pin trigger for " + "active edge"); + return res; + } + + return 0; +} + static int phy_dm8806_init(const struct device *dev) { int ret; uint16_t val; const struct phy_dm8806_config *cfg = dev->config; + /* Configure reset pin for Davicom PHY DM8806 to be able to generate reset + * signal + */ + ret = phy_dm8806_port_init(dev); + if (ret != 0) { + LOG_ERR("Failed to reset PHY DM8806 "); + return ret; + } + ret = mdio_read(cfg->mdio, PHY_ADDRESS_18H, PORT5_MAC_CONTROL, &val); if (ret) { LOG_ERR("Failed to read PORT5_MAC_CONTROL: %i", ret); @@ -71,6 +236,14 @@ static int phy_dm8806_init(const struct device *dev) LOG_ERR("Failed to write IRQ_LED_CONTROL, %i", ret); return ret; } + +#ifdef CONFIG_PHY_DM8806_TRIGGER + ret = phy_dm8806_init_interrupt(dev); + if (ret != 0) { + LOG_ERR("Failed to configure interrupt fot PHY DM8806"); + return ret; + } +#endif return 0; } @@ -81,6 +254,13 @@ static int phy_dm8806_get_link_state(const struct device *dev, struct phy_link_s uint16_t data; const struct phy_dm8806_config *cfg = dev->config; +#ifdef CONFIG_PHY_DM8806_TRIGGER + ret = mdio_read(cfg->mdio, 0x18, 0x18, &data); + if (ret) { + LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", ret); + return ret; + } +#endif /* Read data from Switch Per-Port Register. */ ret = mdio_read(cfg->mdio, cfg->switch_addr, PORTX_SWITCH_STATUS, &data); if (ret) { @@ -150,12 +330,14 @@ static int phy_dm8806_cfg_link(const struct device *dev, enum phy_link_speed adv LOG_ERR("Failes to read data drom DM8806"); return ret; } + k_busy_wait(500); data |= POWER_DOWN; ret = mdio_write(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, data); if (ret) { LOG_ERR("Failed to write data to DM8806"); return ret; } + k_busy_wait(500); /* Turn off the auto-negotiation process. */ ret = mdio_read(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, &data); @@ -163,12 +345,14 @@ static int phy_dm8806_cfg_link(const struct device *dev, enum phy_link_speed adv LOG_ERR("Failed to write data to DM8806"); return ret; } + k_busy_wait(500); data &= ~(AUTO_NEGOTIATION); ret = mdio_write(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, data); if (ret) { LOG_ERR("Failed to write data to DM8806"); return ret; } + k_busy_wait(500); /* Change the link speed. */ ret = mdio_read(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, &data); @@ -176,6 +360,7 @@ static int phy_dm8806_cfg_link(const struct device *dev, enum phy_link_speed adv LOG_ERR("Failed to read data from DM8806"); return ret; } + k_busy_wait(500); data &= ~(LINK_SPEED | DUPLEX_MODE); data |= req_speed; ret = mdio_write(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, data); @@ -183,6 +368,7 @@ static int phy_dm8806_cfg_link(const struct device *dev, enum phy_link_speed adv LOG_ERR("Failed to write data to DM8806"); return ret; } + k_busy_wait(500); /* Power up ethernet port*/ ret = mdio_read(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, &data); @@ -190,31 +376,23 @@ static int phy_dm8806_cfg_link(const struct device *dev, enum phy_link_speed adv LOG_ERR("Failes to read data drom DM8806"); return ret; } + k_busy_wait(500); data &= ~(POWER_DOWN); ret = mdio_write(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, data); if (ret) { LOG_ERR("Failed to write data to DM8806"); return ret; } + k_busy_wait(500); return -ENOTSUP; } -static int phy_dm8806_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data) -{ - ARG_UNUSED(dev); - ARG_UNUSED(cb); - ARG_UNUSED(user_data); - - return -ENOTSUP; -} - -static int phy_dm8806_reg_read(const struct device *dev, uint16_t phy_addr, uint16_t reg_addr, - uint32_t *data) +static int phy_dm8806_reg_read(const struct device *dev, uint16_t reg_addr, uint32_t *data) { int res; const struct phy_dm8806_config *cfg = dev->config; - res = mdio_read(cfg->mdio, phy_addr, reg_addr, (uint16_t *)data); + res = mdio_read(cfg->mdio, cfg->switch_addr, reg_addr, (uint16_t *)data); if (res) { LOG_ERR("Failed to read data from DM8806"); return res; @@ -222,13 +400,12 @@ static int phy_dm8806_reg_read(const struct device *dev, uint16_t phy_addr, uint return res; } -static int phy_dm8806_reg_write(const struct device *dev, uint16_t phy_addr, uint16_t reg_addr, - uint32_t data) +static int phy_dm8806_reg_write(const struct device *dev, uint16_t reg_addr, uint32_t data) { int res; const struct phy_dm8806_config *cfg = dev->config; - res = mdio_write(cfg->mdio, phy_addr, reg_addr, data); + res = mdio_write(cfg->mdio, cfg->switch_addr, reg_addr, data); if (res) { LOG_ERR("Failed to write data to DM8806"); return res; @@ -236,10 +413,34 @@ static int phy_dm8806_reg_write(const struct device *dev, uint16_t phy_addr, uin return res; } -static const struct ethphy_driver_api phy_dm8806_api = { +static int phy_dm8806_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data) +{ + int res = 0; + struct phy_dm8806_data *data = dev->data; + const struct phy_dm8806_config *cfg = dev->config; + + res = gpio_pin_interrupt_configure_dt(&cfg->gpio_int, GPIO_INT_DISABLE); + if (res != 0) { + LOG_WRN("Failed to disable DM8806 interrupt: %i", res); + return res; + } + data->link_speed_chenge_cb = cb; + data->cb_data = user_data; + gpio_pin_interrupt_configure_dt(&cfg->gpio_int, GPIO_INT_EDGE_TO_ACTIVE); + if (res != 0) { + LOG_WRN("Failed to enable DM8806 interrupt: %i", res); + return res; + } + + return res; +} + +static DEVICE_API(ethphy, phy_dm8806_api) = { .get_link = phy_dm8806_get_link_state, .cfg_link = phy_dm8806_cfg_link, +#ifdef CONFIG_PHY_DM8806_TRIGGER .link_cb_set = phy_dm8806_link_cb_set, +#endif .read = phy_dm8806_reg_read, .write = phy_dm8806_reg_write, }; @@ -256,9 +457,9 @@ static const struct ethphy_driver_api phy_dm8806_api = { #define DM8806_PHY_INITIALIZE(n) \ DM8806_PHY_DEFINE_CONFIG(n); \ static struct phy_dm8806_data phy_dm8806_data_##n = { \ - .sem = Z_SEM_INITIALIZER(phy_dm8806_data_##n.sem, 1, 1), \ + .gpio_sem = Z_SEM_INITIALIZER(phy_dm8806_data_##n.gpio_sem, 1, 1), \ }; \ - DEVICE_DT_INST_DEFINE(n, &phy_dm8806_init, NULL, &phy_dm8806_data_##n, \ + DEVICE_DT_INST_DEFINE(n, phy_dm8806_init, NULL, &phy_dm8806_data_##n, \ &phy_dm8806_config_##n, POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, \ &phy_dm8806_api); diff --git a/drivers/ethernet/phy/phy_dm8806_priv.h b/drivers/ethernet/phy/phy_dm8806_priv.h index df94a744358..4c4a292cadf 100644 --- a/drivers/ethernet/phy/phy_dm8806_priv.h +++ b/drivers/ethernet/phy/phy_dm8806_priv.h @@ -40,9 +40,82 @@ /* Link status mask. */ #define LINK_STATUS_MASK 0x1u +/* Switch Engine Registers */ +/* Address Table Control And Status Register PHY Address */ +#define ADDR_TAB_CTRL_STAT_PHY_ADDR 0x15u +/* Address Table Control And Status Register Register SAddress */ +#define ADDR_TAB_CTRL_STAT_REG_ADDR 0x10u + +/* Address Table Access bussy flag offset */ +#define ATB_S_OFFSET 0xf +/* Address Table Command Result flag offset */ +#define ATB_CR_OFFSET 0xd +/* Address Table Command Result flag mask */ +#define ATB_CR_MASK 0x3 + +/* Unicast Address Table Index*/ +#define UNICAST_ADDR_TAB (1 << 0 | 1 << 1) +/* Multicast Address Table Index*/ +#define MULTICAST_ADDR_TAB (1 << 0) +/* IGMP Table Index*/ +#define IGMP_ADDR_TAB (1 << 1) + +/* Read a entry with sequence number of address table */ +#define ATB_CMD_READ (1 << 2 | 1 << 3 | 1 << 4) +/* Write a entry with MAC address */ +#define ATB_CMD_WRITE (1 << 2) +/* Delete a entry with MAC address */ +#define ATB_CMD_DELETE (1 << 3) +/* Search a entry with MAC address */ +#define ATB_CMD_SEARCH (1 << 2 | 1 << 3) +/* Clear one or more than one entries with Port or FID */ +#define ATB_CMD_CLEAR (1 << 4) + +/* Address Table Data 0 PHY Address */ +#define ADDR_TAB_DATA0_PHY_ADDR 0x15u +/* Address Table Data 0 Register Address */ +#define ADDR_TAB_DATA0_REG_ADDR 0x11u +/* Port number or port map mask*/ +#define ATB_PORT_MASK 0x1f + +/* Address Table Data 1 PHY Address */ +#define ADDR_TAB_DATA1_PHY_ADDR 0x15u +/* Address Table Data 1 Register Address */ +#define ADDR_TAB_DATA1_REG_ADDR 0x12u + +/* Address Table Data 2 PHY Address */ +#define ADDR_TAB_DATA2_PHY_ADDR 0x15u +/* Address Table Data 2 Register Address */ +#define ADDR_TAB_DATA2_REG_ADDR 0x13u + +/* Address Table Data 3 PHY Address */ +#define ADDR_TAB_DATA3_PHY_ADDR 0x15u +/* Address Table Data 3 Register Address */ +#define ADDR_TAB_DATA3_REG_ADDR 0x14u + +/* Address Table Data 4 PHY Address */ +#define ADDR_TAB_DATA4_PHY_ADDR 0x15u +/* Address Table Data 4 Register Address */ +#define ADDR_TAB_DATA4_REG_ADDR 0x15u + +/* WoL Control Register PHY Address */ +#define WOLL_CTRL_REG_PHY_ADDR 0x15u +/* WoL Control Register Register Address */ +#define WOLL_CTRL_REG_REG_ADDR 0x1bu + /* PHY address 0x18h */ #define PHY_ADDRESS_18H 0x18u +/* Interrupt Status Register PHY Address. */ +#define INT_STAT_PHY_ADDR 0x18u +/* Interrupt Status Register Register Address. */ +#define INT_STAT_REG_ADDR 0x18u + +/* Interrupt Mask & Control Register PHY Address. */ +#define INT_MASK_CTRL_PHY_ADDR 0x18u +/* Interrupt Mask & Control Register Register Address. */ +#define INT_MASK_CTRL_REG_ADDR 0x19u + #define PORT5_MAC_CONTROL 0x15u /* Port 5 Force Speed control bit */ #define P5_SPEED_100M ~BIT(0)