diff --git a/drivers/ethernet/phy/CMakeLists.txt b/drivers/ethernet/phy/CMakeLists.txt index d94a0c9e7fb6..32c218fdd5ef 100644 --- a/drivers/ethernet/phy/CMakeLists.txt +++ b/drivers/ethernet/phy/CMakeLists.txt @@ -2,6 +2,7 @@ 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_DM8806 phy_dm8806.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) diff --git a/drivers/ethernet/phy/Kconfig b/drivers/ethernet/phy/Kconfig index 554616fd0e36..cf910d846431 100644 --- a/drivers/ethernet/phy/Kconfig +++ b/drivers/ethernet/phy/Kconfig @@ -40,6 +40,13 @@ 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/phy_dm8806.c b/drivers/ethernet/phy/phy_dm8806.c new file mode 100644 index 000000000000..c4058a9fa46f --- /dev/null +++ b/drivers/ethernet/phy/phy_dm8806.c @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2024 Robert Slawinski + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT davicom_dm8806_phy + +#include +LOG_MODULE_REGISTER(eth_dm8806_phy, CONFIG_ETHERNET_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include + +#include "phy_dm8806_priv.h" + +struct phy_dm8806_config { + const struct device *mdio; + uint8_t phy_addr; + uint8_t switch_addr; + struct gpio_dt_spec gpio_rst; + struct gpio_dt_spec gpio_int; + bool mii; +}; + +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; + void *cb_data; +}; + +static int phy_dm8806_init(const struct device *dev) +{ + int ret; + uint16_t val; + const struct phy_dm8806_config *cfg = dev->config; + + ret = mdio_read(cfg->mdio, PHY_ADDRESS_18H, PORT5_MAC_CONTROL, &val); + if (ret) { + LOG_ERR("Failed to read PORT5_MAC_CONTROL: %i", ret); + return ret; + } + + /* Activate default working mode*/ + val |= (P5_50M_INT_CLK_SOURCE | P5_50M_CLK_OUT_ENABLE | P5_EN_FORCE); + val &= (P5_SPEED_100M | P5_FULL_DUPLEX | P5_FORCE_LINK_ON); + + ret = mdio_write(cfg->mdio, PHY_ADDRESS_18H, PORT5_MAC_CONTROL, val); + if (ret) { + LOG_ERR("Failed to write PORT5_MAC_CONTROL, %i", ret); + return ret; + } + + ret = mdio_read(cfg->mdio, PHY_ADDRESS_18H, IRQ_LED_CONTROL, &val); + if (ret) { + LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", ret); + return ret; + } + + /* Activate LED blinking mode indicator mode 0. */ + val &= LED_MODE_0; + ret = mdio_write(cfg->mdio, PHY_ADDRESS_18H, IRQ_LED_CONTROL, val); + if (ret) { + LOG_ERR("Failed to write IRQ_LED_CONTROL, %i", ret); + return ret; + } + return 0; +} + +static int phy_dm8806_get_link_state(const struct device *dev, struct phy_link_state *state) +{ + int ret; + uint16_t status; + uint16_t data; + const struct phy_dm8806_config *cfg = dev->config; + + /* Read data from Switch Per-Port Register. */ + ret = mdio_read(cfg->mdio, cfg->switch_addr, PORTX_SWITCH_STATUS, &data); + if (ret) { + LOG_ERR("Failes to read data drom DM8806 Switch Per-Port Registers area"); + return ret; + } + /* Extract speed and duplex status from Switch Per-Port Register: Per Port + * Status Data Register + */ + status = data; + status >>= SPEED_AND_DUPLEX_OFFSET; + switch (status & SPEED_AND_DUPLEX_MASK) { + case SPEED_10MBPS_HALF_DUPLEX: + state->speed = LINK_HALF_10BASE_T; + break; + case SPEED_10MBPS_FULL_DUPLEX: + state->speed = LINK_FULL_10BASE_T; + break; + case SPEED_100MBPS_HALF_DUPLEX: + state->speed = LINK_HALF_100BASE_T; + break; + case SPEED_100MBPS_FULL_DUPLEX: + state->speed = LINK_FULL_100BASE_T; + break; + } + /* Extract link status from Switch Per-Port Register: Per Port Status Data + * Register + */ + status = data; + if (status & LINK_STATUS_MASK) { + state->is_up = true; + } else { + state->is_up = false; + } + return ret; +} + +static int phy_dm8806_cfg_link(const struct device *dev, enum phy_link_speed adv_speeds) +{ + uint8_t ret; + uint16_t data; + uint16_t req_speed; + const struct phy_dm8806_config *cfg = dev->config; + + req_speed = adv_speeds; + switch (req_speed) { + case LINK_HALF_10BASE_T: + req_speed = MODE_10_BASET_HALF_DUPLEX; + break; + + case LINK_FULL_10BASE_T: + req_speed = MODE_10_BASET_FULL_DUPLEX; + break; + + case LINK_HALF_100BASE_T: + req_speed = MODE_100_BASET_HALF_DUPLEX; + break; + + case LINK_FULL_100BASE_T: + req_speed = MODE_100_BASET_FULL_DUPLEX; + break; + } + + /* Power down */ + ret = mdio_read(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, &data); + if (ret) { + LOG_ERR("Failes to read data drom DM8806"); + return ret; + } + 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; + } + + /* Turn off the auto-negotiation process. */ + ret = mdio_read(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, &data); + if (ret) { + LOG_ERR("Failed to write data to DM8806"); + return ret; + } + 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; + } + + /* Change the link speed. */ + ret = mdio_read(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, &data); + if (ret) { + LOG_ERR("Failed to read data from DM8806"); + return ret; + } + data &= ~(LINK_SPEED | DUPLEX_MODE); + data |= req_speed; + 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; + } + + /* Power up ethernet port*/ + ret = mdio_read(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, &data); + if (ret) { + LOG_ERR("Failes to read data drom DM8806"); + return ret; + } + 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; + } + 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) +{ + int res; + const struct phy_dm8806_config *cfg = dev->config; + + res = mdio_read(cfg->mdio, phy_addr, reg_addr, (uint16_t *)data); + if (res) { + LOG_ERR("Failed to read data from DM8806"); + return res; + } + return res; +} + +static int phy_dm8806_reg_write(const struct device *dev, uint16_t phy_addr, 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); + if (res) { + LOG_ERR("Failed to write data to DM8806"); + return res; + } + return res; +} + +static const struct ethphy_driver_api phy_dm8806_api = { + .get_link = phy_dm8806_get_link_state, + .cfg_link = phy_dm8806_cfg_link, + .link_cb_set = phy_dm8806_link_cb_set, + .read = phy_dm8806_reg_read, + .write = phy_dm8806_reg_write, +}; + +#define DM8806_PHY_DEFINE_CONFIG(n) \ + static const struct phy_dm8806_config phy_dm8806_config_##n = { \ + .mdio = DEVICE_DT_GET(DT_INST_BUS(n)), \ + .phy_addr = DT_INST_REG_ADDR(n), \ + .switch_addr = DT_PROP(DT_NODELABEL(dm8806_phy##n), reg_switch), \ + .gpio_int = GPIO_DT_SPEC_INST_GET(n, interrupt_gpio), \ + .gpio_rst = GPIO_DT_SPEC_INST_GET(n, reset_gpio), \ + } + +#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), \ + }; \ + 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); + +DT_INST_FOREACH_STATUS_OKAY(DM8806_PHY_INITIALIZE) diff --git a/drivers/ethernet/phy/phy_dm8806_priv.h b/drivers/ethernet/phy/phy_dm8806_priv.h new file mode 100644 index 000000000000..df94a7443588 --- /dev/null +++ b/drivers/ethernet/phy/phy_dm8806_priv.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 Robert Slawinski + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Port 0~4 PHY Control Register. */ +#define PORTX_PHY_CONTROL_REGISTER 0x0u +/* 10 Mbit/s transfer with half duplex mask. */ +#define MODE_10_BASET_HALF_DUPLEX 0x0u +/* 10 Mbit/s transfer with full duplex mask. */ +#define MODE_10_BASET_FULL_DUPLEX 0x100u +/* 100 Mbit/s transfer with half duplex mask. */ +#define MODE_100_BASET_HALF_DUPLEX 0x2000u +/* 100 Mbit/s transfer with full duplex mask. */ +#define MODE_100_BASET_FULL_DUPLEX 0x2100u +/* Duplex mode ability offset. */ +#define DUPLEX_MODE (1 << 8) +/* Power down mode offset. */ +#define POWER_DOWN (1 << 11) +/* Auto negotiation mode offset. */ +#define AUTO_NEGOTIATION (1 << 12) +/* Link speed selection offset. */ +#define LINK_SPEED (1 << 13) + +/* Port 0~4 Status Data Register. */ +#define PORTX_SWITCH_STATUS 0x10u +/* 10 Mbit/s transfer speed with half duplex. */ +#define SPEED_10MBPS_HALF_DUPLEX 0x00u +/* 10 Mbit/s transfer speed with full duplex. */ +#define SPEED_10MBPS_FULL_DUPLEX 0x01u +/* 100 Mbit/s transfer speed with half duplex. */ +#define SPEED_100MBPS_HALF_DUPLEX 0x02u +/* 100 Mbit/s transfer speed with full duplex. */ +#define SPEED_100MBPS_FULL_DUPLEX 0x03u +/* Speed and duplex mode status offset. */ +#define SPEED_AND_DUPLEX_OFFSET 0x01u +/* Speed and duplex mode staus mask. */ +#define SPEED_AND_DUPLEX_MASK 0x07u +/* Link status mask. */ +#define LINK_STATUS_MASK 0x1u + +/* PHY address 0x18h */ +#define PHY_ADDRESS_18H 0x18u + +#define PORT5_MAC_CONTROL 0x15u +/* Port 5 Force Speed control bit */ +#define P5_SPEED_100M ~BIT(0) +/* Port 5 Force Duplex control bit */ +#define P5_FULL_DUPLEX ~BIT(1) +/* Port 5 Force Link control bit. Only available in force mode. */ +#define P5_FORCE_LINK_ON ~BIT(2) +/* Port 5 Force Mode Enable control bit. Only available for + * MII/RevMII/RMII + */ +#define P5_EN_FORCE BIT(3) +/* Bit 4 is reserved and should not be use */ +/* Port 5 50MHz Clock Output Enable control bit. Only available when Port 5 + * be configured as RMII + */ +#define P5_50M_CLK_OUT_ENABLE BIT(5) +/* Port 5 Clock Source Selection control bit. Only available when Port 5 + * is configured as RMII + */ +#define P5_50M_INT_CLK_SOURCE BIT(6) +/* Port 5 Output Pin Slew Rate. */ +#define P5_NORMAL_SLEW_RATE ~BIT(7) +/* IRQ and LED Control Register. */ +#define IRQ_LED_CONTROL 0x17u +/* LED mode 0: + * LNK_LED: + * 100M link fail - LED off + * 100M link ok and no TX/RX activity - LED on + * 100M link ok and TX/RX activity - LED blinking + * SPD_LED: + * No colision: - LED off + * Colision: - LED blinking + * FDX_LED: + * 10M link fail - LED off + * 10M link ok and no TX/RX activity - LED on + * 10M link ok and TX/RX activity - LED blinking + */ +#define LED_MODE_0 ~(BIT(0) | BIT(1)) diff --git a/dts/bindings/ethernet/davicom,dm8806-phy.yaml b/dts/bindings/ethernet/davicom,dm8806-phy.yaml new file mode 100644 index 000000000000..13fe82c1e930 --- /dev/null +++ b/dts/bindings/ethernet/davicom,dm8806-phy.yaml @@ -0,0 +1,61 @@ +# Copyright 2024 Robert Slawinski +# SPDX-License-Identifier: Apache-2.0 + +description: Davicom DM8806 Ethernet MAC and PHY with RMII interface + +compatible: "davicom,dm8806-phy" + +include: [ethernet-phy.yaml] + +on-bus: mdio + +properties: + reg: + required: true + description: | + 5-bit PHY address for Internal PHY Registers group of Davicom DM8806 MAC + PHY Ethernet Switch Controller, separate for each MAC PHY, build in DM8806 + and correlate with Ethenet port. DM8806 has five MAC PHY inside, but it is + not mandatory to define all of them in device tree if there is no need to + communicate with all of them. Each MAC PHY has its own PHY address, which + together with Register address creates the absolute address of the + concrete register in conrete MAC PHY acoring to Clause 22 MDIO + communication standard. Below example shows how the PHY address and + Register address are glued together in Internal PHY Registers group: + + Internal PHY Registers + Port0: (5-bit PHY Address) + (5-bit Register address) = Absolute address + + Absolute address is the address of the concrete register in MAC PHY0 + which is responsible for Ethernet Port0 in Davicom DM8806 + reg-switch: + required: true + description: | + 5-bit PHY address for Switch Per-Port Registers group of Davicom DM8806 + MAC PHY Ethernet Switch Controller, separate for each MAC PHY, build in + DM8806 and correlate with Ethenet port. DM8806 has five MAC PHY inside, + but it is not mandatory to define all of them in device tree if there is + no need to communicate with all of them. Each MAC PHY has its own PHY + address, which together with Register address creates the absolute address + of the concrete register in conrete MAC PHY acoring to Clause 22 MDIO + communication standard. Below example shows how the PHY address and + Register address are glued together in Switch Per-Port Registers group: + + Switch Per-Port Registers + Port0: (5bit PHY Address) + (5bit Register address) = Absolute address + + Absolute address is the address of the concrete register in MAC PHY0 + which is responsible for Ethernet Port0 in Davicom DM8806 + reset-gpios: + type: phandle-array + description: GPIO connected to MAC PHY reset signal pin. Reset is active low. + int-gpios: + type: phandle-array + description: GPIO for upt signal indicating MAC PHY state change. + davicom,interface-type: + type: string + required: true + description: Which type of phy connection the mac phy is set up for + enum: + - "mii" + - "rmii"