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)