diff --git a/boards/pjrc/teensy4/Kconfig.defconfig b/boards/pjrc/teensy4/Kconfig.defconfig index 4bdd626e94a62d..2af9d12ef9f092 100644 --- a/boards/pjrc/teensy4/Kconfig.defconfig +++ b/boards/pjrc/teensy4/Kconfig.defconfig @@ -13,4 +13,12 @@ config BUILD_OUTPUT_HEX config DISK_DRIVER_SDMMC default y if DISK_DRIVERS +if NETWORKING + +config NET_L2_ETHERNET + default n if BOARD_TEENSY40 + default y if BOARD_TEENSY41 + +endif # NETWORKING + endif # BOARD_TEENSY40 || BOARD_TEENSY41 diff --git a/boards/pjrc/teensy4/teensy4-pinctrl.dtsi b/boards/pjrc/teensy4/teensy4-pinctrl.dtsi index 430020f5098f3f..c12573cb6b38e0 100644 --- a/boards/pjrc/teensy4/teensy4-pinctrl.dtsi +++ b/boards/pjrc/teensy4/teensy4-pinctrl.dtsi @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, NXP + * Copyright (c) 2024, Bernhard Kraemer * SPDX-License-Identifier: Apache-2.0 * * Note: File generated by gen_board_pinctrl.py @@ -12,41 +13,40 @@ /* Mode Straps configuration DP83825 */ pinmux_enet: pinmux_enet { group0 { + pinmux = <&iomuxc_gpio_b1_10_enet_ref_clk>; + bias-disable; + drive-strength = "r0-6"; + slew-rate = "slow"; + nxp,speed = "100-mhz"; + input-enable; + }; + group1 { pinmux = <&iomuxc_gpio_b1_04_enet_rx_data0>, + <&iomuxc_gpio_b1_05_enet_rx_data1>, <&iomuxc_gpio_b1_06_enet_rx_en>, + <&iomuxc_gpio_b1_07_enet_tx_data0>, + <&iomuxc_gpio_b1_08_enet_tx_data1>, + <&iomuxc_gpio_b1_09_enet_tx_en>, <&iomuxc_gpio_b1_11_enet_rx_er>; drive-strength = "r0-5"; - bias-pull-down; - bias-pull-down-value = "100k"; - slew-rate = "fast"; - nxp,speed = "200-mhz"; - }; - group1 { - pinmux = <&iomuxc_gpio_b1_05_enet_rx_data1>; - drive-strength = "r0-5"; bias-pull-up; - bias-pull-up-value = "22k"; + bias-pull-up-value = "100k"; slew-rate = "fast"; nxp,speed = "200-mhz"; }; - group2 { - pinmux = <&iomuxc_gpio_b1_07_enet_tx_data0>, - <&iomuxc_gpio_b1_08_enet_tx_data1>, - <&iomuxc_gpio_b1_09_enet_tx_en>, - <&iomuxc_gpio_b1_14_enet_mdc>, + }; + + pinmux_enet_mdio: pinmux_enet_mdio { + group0 { + pinmux = <&iomuxc_gpio_b1_14_enet_mdc>, <&iomuxc_gpio_b1_15_enet_mdio>, <&iomuxc_gpio_b0_15_gpio2_io15>, <&iomuxc_gpio_b0_14_gpio2_io14>; - drive-strength = "r0-6"; - slew-rate = "slow"; - nxp,speed = "100-mhz"; - }; - group3 { - pinmux = <&iomuxc_gpio_b1_10_enet_ref_clk>; - drive-strength = "r0-6"; - slew-rate = "slow"; - nxp,speed = "100-mhz"; - input-enable; + drive-strength = "r0-5"; + bias-pull-up; + bias-pull-up-value = "100k"; + slew-rate = "fast"; + nxp,speed = "200-mhz"; }; }; diff --git a/boards/pjrc/teensy4/teensy40.dts b/boards/pjrc/teensy4/teensy40.dts index 63775c3d1c618e..ddbefe8d8c9d6c 100644 --- a/boards/pjrc/teensy4/teensy40.dts +++ b/boards/pjrc/teensy4/teensy40.dts @@ -70,24 +70,8 @@ zephyr_udc0: &usb1 { status = "okay"; }; -/* Pinmux settings */ -&enet_mac { - pinctrl-0 = <&pinmux_enet>; - pinctrl-names = "default"; - zephyr,random-mac-address; - phy-connection-type = "rmii"; - phy-handle = <&phy>; -}; - -&enet_mdio { +&edma0 { status = "okay"; - pinctrl-0 = <&pinmux_enet>; - pinctrl-names = "default"; - phy: phy@0 { - compatible = "ethernet-phy"; - reg = <0>; - status = "okay"; - }; }; &flexcan1 { diff --git a/boards/pjrc/teensy4/teensy41.dts b/boards/pjrc/teensy4/teensy41.dts index e113183c66f52e..c742bcc8009f15 100644 --- a/boards/pjrc/teensy4/teensy41.dts +++ b/boards/pjrc/teensy4/teensy41.dts @@ -34,6 +34,29 @@ }; }; +&enet_mac { + status = "okay"; + pinctrl-0 = <&pinmux_enet>; + pinctrl-names = "default"; + nxp,unique-mac; + phy-connection-type = "rmii"; + phy-handle = <&phy>; +}; + +&enet_mdio { + status = "okay"; + pinctrl-0 = <&pinmux_enet_mdio>; + pinctrl-names = "default"; + phy: phy@0 { + status = "okay"; + compatible = "ti,dp83825"; + reg = <0>; + reset-gpios = <&gpio2 14 GPIO_ACTIVE_LOW>; + int-gpios = <&gpio2 15 GPIO_ACTIVE_LOW>; + ti,interface-type = "rmii"; + }; +}; + &lpspi3 { status = "okay"; }; diff --git a/boards/pjrc/teensy4/teensy41.yaml b/boards/pjrc/teensy4/teensy41.yaml index b2ad1e303ca70e..18f320a2df6148 100644 --- a/boards/pjrc/teensy4/teensy41.yaml +++ b/boards/pjrc/teensy4/teensy41.yaml @@ -19,6 +19,7 @@ supported: - gpio - sdhc - usb_device + - netif:eth testing: ignore_tags: - net diff --git a/drivers/ethernet/phy/CMakeLists.txt b/drivers/ethernet/phy/CMakeLists.txt index a6bef065784b57..d94a0c9e7fb673 100644 --- a/drivers/ethernet/phy/CMakeLists.txt +++ b/drivers/ethernet/phy/CMakeLists.txt @@ -4,5 +4,6 @@ zephyr_library_sources_ifdef(CONFIG_PHY_GENERIC_MII phy_mii.c) zephyr_library_sources_ifdef(CONFIG_PHY_ADIN2111 phy_adin2111.c) zephyr_library_sources_ifdef(CONFIG_PHY_TJA1103 phy_tja1103.c) zephyr_library_sources_ifdef(CONFIG_PHY_MICROCHIP_KSZ8081 phy_microchip_ksz8081.c) +zephyr_library_sources_ifdef(CONFIG_PHY_TI_DP83825 phy_ti_dp83825.c) zephyr_library_sources_ifdef(CONFIG_PHY_REALTEK_RTL8211F phy_realtek_rtl8211f.c) zephyr_library_sources_ifdef(CONFIG_PHY_QUALCOMM_AR8031 phy_qualcomm_ar8031.c) diff --git a/drivers/ethernet/phy/Kconfig b/drivers/ethernet/phy/Kconfig index 31659272e2fc3a..554616fd0e3665 100644 --- a/drivers/ethernet/phy/Kconfig +++ b/drivers/ethernet/phy/Kconfig @@ -49,6 +49,15 @@ config PHY_MICROCHIP_KSZ8081 help Enable Microchip KSZ8081 Ethernet PHY Driver +config PHY_TI_DP83825 + bool "TI DP83825 PHY Driver" + default y + depends on DT_HAS_TI_DP83825_ENABLED + depends on MDIO + depends on GPIO + help + Enable TI DP83825 Ethernet PHY Driver + config PHY_REALTEK_RTL8211F bool "Realtek RTL8211F PHY Driver" default y diff --git a/drivers/ethernet/phy/phy_ti_dp83825.c b/drivers/ethernet/phy/phy_ti_dp83825.c new file mode 100644 index 00000000000000..3f700ac4ec2f46 --- /dev/null +++ b/drivers/ethernet/phy/phy_ti_dp83825.c @@ -0,0 +1,602 @@ +/* + * Copyright 2024 Bernhard Kraemer + * + * Inspiration from phy_realtek_rtl8211f.c, which is: + * Copyright 2023-2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_dp83825 + +#include +#include +#include +#include +#include +#include +#include + +#define LOG_MODULE_NAME phy_ti_dp83825 +#define LOG_LEVEL CONFIG_PHY_LOG_LEVEL +#include +LOG_MODULE_REGISTER(LOG_MODULE_NAME); + +#define PHY_TI_DP83825_PHYSCR_REG 0x11 +#define PHY_TI_DP83825_PHYSCR_REG_IE BIT(1) +#define PHY_TI_DP83825_PHYSCR_REG_IOE BIT(0) + +#define PHY_TI_DP83825_MISR_REG 0x12 +#define PHY_TI_DP83825_MISR_REG_LSCE BIT(5) + +#define PHY_TI_DP83825_RCSR_REG 0x17 +#define PHY_TI_DP83825_RCSR_REF_CLK_SEL BIT(7) + +#define PHY_TI_DP83825_POR_DELAY 50 + +enum dp83825_interface { + DP83825_RMII, + DP83825_RMII_25MHZ +}; + +struct ti_dp83825_config { + uint8_t addr; + const struct device *mdio_dev; + enum dp83825_interface phy_iface; +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) + const struct gpio_dt_spec reset_gpio; +#endif +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) + const struct gpio_dt_spec interrupt_gpio; +#endif +}; + +struct ti_dp83825_data { + const struct device *dev; + struct phy_link_state state; + phy_callback_t cb; +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) + struct gpio_callback gpio_callback; +#endif + void *cb_data; + struct k_mutex mutex; + struct k_work_delayable phy_monitor_work; +}; + +static int phy_ti_dp83825_read(const struct device *dev, uint16_t reg_addr, uint32_t *data) +{ + const struct ti_dp83825_config *config = dev->config; + int ret; + + /* Make sure excessive bits 16-31 are reset */ + *data = 0U; + + ret = mdio_read(config->mdio_dev, config->addr, reg_addr, (uint16_t *)data); + if (ret) { + return ret; + } + + return 0; +} + +static int phy_ti_dp83825_write(const struct device *dev, uint16_t reg_addr, uint32_t data) +{ + const struct ti_dp83825_config *config = dev->config; + int ret; + + ret = mdio_write(config->mdio_dev, config->addr, reg_addr, (uint16_t)data); + if (ret) { + return ret; + } + + return 0; +} + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) +static int phy_ti_dp83825_clear_interrupt(struct ti_dp83825_data *data) +{ + const struct device *dev = data->dev; + const struct ti_dp83825_config *config = dev->config; + uint32_t reg_val; + int ret; + + /* Lock mutex */ + ret = k_mutex_lock(&data->mutex, K_FOREVER); + if (ret) { + LOG_ERR("PHY mutex lock error"); + return ret; + } + + /* Read/clear PHY interrupt status register */ + ret = phy_ti_dp83825_read(dev, PHY_TI_DP83825_MISR_REG, ®_val); + if (ret) { + LOG_ERR("Error reading phy (%d) interrupt status register", config->addr); + } + + /* Unlock mutex */ + (void)k_mutex_unlock(&data->mutex); + + return ret; +} + +static void phy_ti_dp83825_interrupt_handler(const struct device *port, struct gpio_callback *cb, + gpio_port_pins_t pins) +{ + struct ti_dp83825_data *data = CONTAINER_OF(cb, struct ti_dp83825_data, gpio_callback); + int ret; + + ret = k_work_reschedule(&data->phy_monitor_work, K_NO_WAIT); + if (ret < 0) { + LOG_ERR("Failed to schedule phy_monitor_work from ISR"); + } +} +#endif /* DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) */ + +static int phy_ti_dp83825_autonegotiate(const struct device *dev) +{ + const struct ti_dp83825_config *config = dev->config; + int ret; + uint32_t bmcr = 0; + + /* Read control register to write back with autonegotiation bit */ + ret = phy_ti_dp83825_read(dev, MII_BMCR, &bmcr); + if (ret) { + LOG_ERR("Error reading phy (%d) basic control register", config->addr); + return ret; + } + + /* (re)start autonegotiation */ + LOG_DBG("PHY (%d) is entering autonegotiation sequence", config->addr); + bmcr |= MII_BMCR_AUTONEG_ENABLE | MII_BMCR_AUTONEG_RESTART; + bmcr &= ~MII_BMCR_ISOLATE; + + ret = phy_ti_dp83825_write(dev, MII_BMCR, bmcr); + if (ret) { + LOG_ERR("Error writing phy (%d) basic control register", config->addr); + return ret; + } + + return 0; +} + +static int phy_ti_dp83825_get_link(const struct device *dev, struct phy_link_state *state) +{ + const struct ti_dp83825_config *config = dev->config; + struct ti_dp83825_data *data = dev->data; + int ret; + uint32_t bmsr = 0; + uint32_t anar = 0; + uint32_t anlpar = 0; + uint32_t mutual_capabilities; + struct phy_link_state old_state = data->state; + + /* Lock mutex */ + ret = k_mutex_lock(&data->mutex, K_FOREVER); + if (ret) { + LOG_ERR("PHY mutex lock error"); + return ret; + } + + /* Read link state */ + ret = phy_ti_dp83825_read(dev, MII_BMSR, &bmsr); + if (ret) { + LOG_ERR("Error reading phy (%d) basic status register", config->addr); + k_mutex_unlock(&data->mutex); + return ret; + } + state->is_up = bmsr & MII_BMSR_LINK_STATUS; + + if (!state->is_up) { + k_mutex_unlock(&data->mutex); + goto result; + } + + /* Read currently configured advertising options */ + ret = phy_ti_dp83825_read(dev, MII_ANAR, &anar); + if (ret) { + LOG_ERR("Error reading phy (%d) advertising register", config->addr); + k_mutex_unlock(&data->mutex); + return ret; + } + + /* Read link partner capability */ + ret = phy_ti_dp83825_read(dev, MII_ANLPAR, &anlpar); + if (ret) { + LOG_ERR("Error reading phy (%d) link partner register", config->addr); + k_mutex_unlock(&data->mutex); + return ret; + } + + /* Unlock mutex */ + k_mutex_unlock(&data->mutex); + + mutual_capabilities = anar & anlpar; + + if (mutual_capabilities & MII_ADVERTISE_100_FULL) { + state->speed = LINK_FULL_100BASE_T; + } else if (mutual_capabilities & MII_ADVERTISE_100_HALF) { + state->speed = LINK_HALF_100BASE_T; + } else if (mutual_capabilities & MII_ADVERTISE_10_FULL) { + state->speed = LINK_FULL_10BASE_T; + } else if (mutual_capabilities & MII_ADVERTISE_10_HALF) { + state->speed = LINK_HALF_10BASE_T; + } else { + return -EIO; + } + +result: + if (memcmp(&old_state, state, sizeof(struct phy_link_state)) != 0) { + LOG_DBG("PHY %d is %s", config->addr, state->is_up ? "up" : "down"); + if (state->is_up) { + LOG_INF("PHY (%d) Link speed %s Mb, %s duplex\n", config->addr, + (PHY_LINK_IS_SPEED_100M(state->speed) ? "100" : "10"), + PHY_LINK_IS_FULL_DUPLEX(state->speed) ? "full" : "half"); + } + } + + return ret; +} + +/* + * Configuration set statically (DT) that should never change + * This function is needed in case the PHY is reset then the next call + * to configure the phy will ensure this configuration will be redone + */ +static int phy_ti_dp83825_static_cfg(const struct device *dev) +{ + const struct ti_dp83825_config *config = dev->config; +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) + struct ti_dp83825_data *data = dev->data; +#endif /* DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) */ + uint32_t reg_val = 0; + int ret = 0; + + /* Select correct reference clock mode depending on interface setup */ + ret = phy_ti_dp83825_read(dev, PHY_TI_DP83825_RCSR_REG, (uint32_t *)®_val); + if (ret) { + return ret; + } + + if (config->phy_iface == DP83825_RMII) { + reg_val |= PHY_TI_DP83825_RCSR_REF_CLK_SEL; + } else { + reg_val &= ~PHY_TI_DP83825_RCSR_REF_CLK_SEL; + } + + ret = phy_ti_dp83825_write(dev, PHY_TI_DP83825_RCSR_REG, (uint32_t)reg_val); + if (ret) { + return ret; + } + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) + /* Read PHYSCR register to write back */ + ret = phy_ti_dp83825_read(dev, PHY_TI_DP83825_PHYSCR_REG, ®_val); + if (ret) { + return ret; + } + + /* Config INTR/PWRDN pin as Interrupt output, enable event interrupts */ + reg_val |= PHY_TI_DP83825_PHYSCR_REG_IOE | PHY_TI_DP83825_PHYSCR_REG_IE; + + /* Write settings to physcr register */ + ret = phy_ti_dp83825_write(dev, PHY_TI_DP83825_PHYSCR_REG, reg_val); + if (ret) { + return ret; + } + + /* Clear interrupt */ + ret = phy_ti_dp83825_clear_interrupt(data); + if (ret) { + return ret; + } + + /* Read MISR register to write back */ + ret = phy_ti_dp83825_read(dev, PHY_TI_DP83825_MISR_REG, ®_val); + if (ret) { + return ret; + } + + /* Enable link state changed interrupt*/ + reg_val |= PHY_TI_DP83825_MISR_REG_LSCE; + + /* Write settings to misr register */ + ret = phy_ti_dp83825_write(dev, PHY_TI_DP83825_MISR_REG, reg_val); +#endif /* DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) */ + + return ret; +} + +static int phy_ti_dp83825_reset(const struct device *dev) +{ + const struct ti_dp83825_config *config = dev->config; + struct ti_dp83825_data *data = dev->data; + int ret; + + /* Lock mutex */ + ret = k_mutex_lock(&data->mutex, K_FOREVER); + if (ret) { + LOG_ERR("PHY mutex lock error"); + return ret; + } + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) + if (!config->reset_gpio.port) { + goto skip_reset_gpio; + } + + /* Start reset (logically ACTIVE, physically LOW) */ + ret = gpio_pin_set_dt(&config->reset_gpio, 1); + if (ret) { + goto done; + } + + /* Reset pulse (minimum specified width is T1=25us) */ + k_busy_wait(USEC_PER_MSEC * 1); + + /* Reset over (logically INACTIVE, physically HIGH) */ + ret = gpio_pin_set_dt(&config->reset_gpio, 0); + + /* POR release time (minimum specified is T4=50ms) */ + k_busy_wait(USEC_PER_MSEC * PHY_TI_DP83825_POR_DELAY); + + goto done; +skip_reset_gpio: +#endif /* DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) */ + ret = phy_ti_dp83825_write(dev, MII_BMCR, MII_BMCR_RESET); + if (ret) { + goto done; + } + /* POR release time (minimum specified is T4=50ms) */ + k_busy_wait(USEC_PER_MSEC * PHY_TI_DP83825_POR_DELAY); + +done: + /* Unlock mutex */ + k_mutex_unlock(&data->mutex); + + LOG_DBG("PHY (%d) reset completed", config->addr); + + return ret; +} + +static int phy_ti_dp83825_cfg_link(const struct device *dev, enum phy_link_speed speeds) +{ + const struct ti_dp83825_config *config = dev->config; + struct ti_dp83825_data *data = dev->data; + int ret; + uint32_t anar; + + /* Lock mutex */ + ret = k_mutex_lock(&data->mutex, K_FOREVER); + if (ret) { + LOG_ERR("PHY mutex lock error"); + goto done; + } + + /* We are going to reconfigure the phy, don't need to monitor until done */ +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) + if (!config->interrupt_gpio.port) { + k_work_cancel_delayable(&data->phy_monitor_work); + } +#else + k_work_cancel_delayable(&data->phy_monitor_work); +#endif /* DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) */ + + /* Reset PHY */ + ret = phy_ti_dp83825_reset(dev); + if (ret) { + goto done; + } + + /* DT configurations */ + ret = phy_ti_dp83825_static_cfg(dev); + if (ret) { + goto done; + } + + /* Read ANAR register to write back */ + ret = phy_ti_dp83825_read(dev, MII_ANAR, &anar); + if (ret) { + LOG_ERR("Error reading phy (%d) advertising register", config->addr); + goto done; + } + + /* Setup advertising register */ + if (speeds & LINK_FULL_100BASE_T) { + anar |= MII_ADVERTISE_100_FULL; + } else { + anar &= ~MII_ADVERTISE_100_FULL; + } + + if (speeds & LINK_HALF_100BASE_T) { + anar |= MII_ADVERTISE_100_HALF; + } else { + anar &= ~MII_ADVERTISE_100_HALF; + } + + if (speeds & LINK_FULL_10BASE_T) { + anar |= MII_ADVERTISE_10_FULL; + } else { + anar &= ~MII_ADVERTISE_10_FULL; + } + + if (speeds & LINK_HALF_10BASE_T) { + anar |= MII_ADVERTISE_10_HALF; + } else { + anar &= ~MII_ADVERTISE_10_HALF; + } + + /* Write capabilities to advertising register */ + ret = phy_ti_dp83825_write(dev, MII_ANAR, anar); + if (ret) { + LOG_ERR("Error writing phy (%d) advertising register", config->addr); + goto done; + } + + /* (re)do autonegotiation */ + ret = phy_ti_dp83825_autonegotiate(dev); + if (ret && (ret != -ENETDOWN)) { + LOG_ERR("Error in autonegotiation"); + goto done; + } + +done: + /* Unlock mutex */ + k_mutex_unlock(&data->mutex); + + /* Start monitoring */ +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) + if (!config->interrupt_gpio.port) { + k_work_reschedule(&data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); + } +#else + k_work_reschedule(&data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); +#endif /* DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) */ + + return ret; +} + +static int phy_ti_dp83825_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data) +{ + struct ti_dp83825_data *data = dev->data; + + data->cb = cb; + data->cb_data = user_data; + + phy_ti_dp83825_get_link(dev, &data->state); + + data->cb(dev, &data->state, data->cb_data); + + return 0; +} + +static void phy_ti_dp83825_monitor_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct ti_dp83825_data *data = + CONTAINER_OF(dwork, struct ti_dp83825_data, phy_monitor_work); + const struct device *dev = data->dev; +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) + const struct ti_dp83825_config *config = dev->config; +#endif /* DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) */ + struct phy_link_state state = {}; + int ret; + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) + if (config->interrupt_gpio.port) { + ret = phy_ti_dp83825_clear_interrupt(data); + if (ret) { + return; + } + } +#endif /* DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) */ + + ret = phy_ti_dp83825_get_link(dev, &state); + + if (ret == 0 && memcmp(&state, &data->state, sizeof(struct phy_link_state)) != 0) { + memcpy(&data->state, &state, sizeof(struct phy_link_state)); + if (data->cb) { + data->cb(dev, &data->state, data->cb_data); + } + } + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) + if (!config->interrupt_gpio.port) { + k_work_reschedule(&data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); + } +#else + k_work_reschedule(&data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); +#endif /* DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) */ +} + +static int phy_ti_dp83825_init(const struct device *dev) +{ + const struct ti_dp83825_config *config = dev->config; + struct ti_dp83825_data *data = dev->data; + int ret; + + data->dev = dev; + + ret = k_mutex_init(&data->mutex); + if (ret) { + return ret; + } + + mdio_bus_enable(config->mdio_dev); + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) + if (config->reset_gpio.port) { + ret = gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_INACTIVE); + if (ret) { + return ret; + } + } +#endif /* DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) */ + + k_work_init_delayable(&data->phy_monitor_work, phy_ti_dp83825_monitor_work_handler); + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) + if (!config->interrupt_gpio.port) { + phy_ti_dp83825_monitor_work_handler(&data->phy_monitor_work.work); + goto skip_int_gpio; + } + + /* Configure interrupt pin */ + ret = gpio_pin_configure_dt(&config->interrupt_gpio, GPIO_INPUT); + if (ret) { + return ret; + } + + gpio_init_callback(&data->gpio_callback, phy_ti_dp83825_interrupt_handler, + BIT(config->interrupt_gpio.pin)); + ret = gpio_add_callback_dt(&config->interrupt_gpio, &data->gpio_callback); + if (ret) { + return ret; + } + + ret = gpio_pin_interrupt_configure_dt(&config->interrupt_gpio, GPIO_INT_EDGE_TO_ACTIVE); + if (ret) { + return ret; + } + +skip_int_gpio: +#else + phy_ti_dp83825_monitor_work_handler(&data->phy_monitor_work.work); +#endif /* DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) */ + + return 0; +} + +static const struct ethphy_driver_api ti_dp83825_phy_api = { + .get_link = phy_ti_dp83825_get_link, + .cfg_link = phy_ti_dp83825_cfg_link, + .link_cb_set = phy_ti_dp83825_link_cb_set, + .read = phy_ti_dp83825_read, + .write = phy_ti_dp83825_write, +}; + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(reset_gpios) +#define RESET_GPIO(n) .reset_gpio = GPIO_DT_SPEC_INST_GET_OR(n, reset_gpios, {0}), +#else +#define RESET_GPIO(n) +#endif /* reset gpio */ + +#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) +#define INTERRUPT_GPIO(n) .interrupt_gpio = GPIO_DT_SPEC_INST_GET_OR(n, int_gpios, {0}), +#else +#define INTERRUPT_GPIO(n) +#endif /* interrupt gpio */ + +#define TI_DP83825_INIT(n) \ + static const struct ti_dp83825_config ti_dp83825_##n##_config = { \ + .addr = DT_INST_REG_ADDR(n), \ + .mdio_dev = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .phy_iface = DT_INST_ENUM_IDX(n, ti_interface_type), \ + RESET_GPIO(n) INTERRUPT_GPIO(n)}; \ + \ + static struct ti_dp83825_data ti_dp83825_##n##_data; \ + \ + DEVICE_DT_INST_DEFINE(n, &phy_ti_dp83825_init, NULL, &ti_dp83825_##n##_data, \ + &ti_dp83825_##n##_config, POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, \ + &ti_dp83825_phy_api); + +DT_INST_FOREACH_STATUS_OKAY(TI_DP83825_INIT) diff --git a/dts/bindings/ethernet/ti,dp83825.yaml b/dts/bindings/ethernet/ti,dp83825.yaml new file mode 100644 index 00000000000000..4846f31f08eaca --- /dev/null +++ b/dts/bindings/ethernet/ti,dp83825.yaml @@ -0,0 +1,23 @@ +# Copyright 2023-2024 NXP +# SPDX-License-Identifier: Apache-2.0 + +description: TI DP83825 Ethernet PHY device + +compatible: "ti,dp83825" + +include: ethernet-phy.yaml + +properties: + reset-gpios: + type: phandle-array + description: GPIO connected to PHY reset signal pin. Reset is active low. + int-gpios: + type: phandle-array + description: GPIO for interrupt signal indicating PHY state change. + ti,interface-type: + type: string + required: true + description: Which type of phy connection the phy is set up for + enum: + - "rmii" + - "rmii-25MHz"