|  | /* | 
|  | * DM8806 Stand-alone Ethernet PHY with RMII | 
|  | * | 
|  | * Copyright (c) 2024 Robert Slawinski <robert.slawinski1@gmail.com> | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT davicom_dm8806_phy | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | LOG_MODULE_REGISTER(eth_dm8806_phy, CONFIG_ETHERNET_LOG_LEVEL); | 
|  |  | 
|  | #include <stdio.h> | 
|  | #include <sys/types.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/net/phy.h> | 
|  | #include <zephyr/drivers/mdio.h> | 
|  | #include <zephyr/drivers/gpio.h> | 
|  |  | 
|  | #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; | 
|  | 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 | 
|  | }; | 
|  |  | 
|  | #ifdef CONFIG_PHY_DM8806_SMI_BUS_CHECK | 
|  | static uint16_t phy_calculate_checksum(uint16_t data, uint16_t reg_addr, uint8_t opcode) | 
|  | { | 
|  | uint16_t csum[8]; | 
|  | uint16_t checksum = 0; | 
|  |  | 
|  | /* Checksum calculated formula proposed by Davicom on datasheet: | 
|  | * DM8806-DAVICOM - par. 7.2.1: | 
|  | * Host SMI Bus Error Check Function, page 141. | 
|  | */ | 
|  | csum[0] = (((data >> 0) & 1) ^ ((data >> 8) & 1) ^ ((reg_addr >> 0) & 1) ^ | 
|  | ((reg_addr >> 8) & 1)); | 
|  | csum[1] = (((data >> 1) & 1) ^ ((data >> 9) & 1) ^ ((reg_addr >> 1) & 1) ^ | 
|  | ((reg_addr >> 9) & 1)); | 
|  | csum[2] = (((data >> 2) & 1) ^ ((data >> 10) & 1) ^ ((reg_addr >> 2) & 1) ^ | 
|  | ((opcode >> 0) & 1)); | 
|  | csum[3] = (((data >> 3) & 1) ^ ((data >> 11) & 1) ^ ((reg_addr >> 3) & 1) ^ | 
|  | ((opcode >> 1) & 1)); | 
|  | csum[4] = (((data >> 4) & 1) ^ ((data >> 12) & 1) ^ ((reg_addr >> 4) & 1)); | 
|  | csum[5] = (((data >> 5) & 1) ^ ((data >> 13) & 1) ^ ((reg_addr >> 5) & 1)); | 
|  | csum[6] = (((data >> 6) & 1) ^ ((data >> 14) & 1) ^ ((reg_addr >> 6) & 1)); | 
|  | csum[7] = (((data >> 7) & 1) ^ ((data >> 15) & 1) ^ ((reg_addr >> 7) & 1)); | 
|  | for (int cnt = 0; cnt < 8; cnt++) { | 
|  | checksum |= (csum[cnt] << cnt); | 
|  | } | 
|  | return checksum; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static int phy_dm8806_write_reg(const struct device *dev, uint8_t phyad, uint8_t regad, | 
|  | uint16_t data) | 
|  | { | 
|  | int res = 0; | 
|  | const struct phy_dm8806_config *cfg = dev->config; | 
|  |  | 
|  | /* SMI bus check function should be activated each time, before writing | 
|  | * procedure to the DM8806 registers. This is standard procedure described in | 
|  | * the datasheet of the DM8806. | 
|  | */ | 
|  | #ifdef CONFIG_PHY_DM8806_SMI_BUS_CHECK | 
|  | uint16_t checksum_status; | 
|  | bool checksum_mismatch; | 
|  | uint16_t sw_checksum = 0; | 
|  | uint16_t abs_reg; | 
|  | int repetition = 0; | 
|  |  | 
|  | do { | 
|  | /* Set register 33AH.[0] = 1 to enable SMI Bus Error Check function. */ | 
|  | res = mdio_write(cfg->mdio, DM8806_SMI_BUS_CTRL_PHY_ADDRESS, | 
|  | DM8806_SMI_BUS_CTRL_REG_ADDRESS, DM8806_SMI_ECE); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to write data to PHY register: SMI_BUS_CTRL_REG_ADDRESS, " | 
|  | "error code: %d", | 
|  | res); | 
|  | return res; | 
|  | } | 
|  | #endif | 
|  | res = mdio_write(cfg->mdio, phyad, regad, data); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to read data from PHY, error code: %d", res); | 
|  | return res; | 
|  | } | 
|  | #ifdef CONFIG_PHY_DM8806_SMI_BUS_CHECK | 
|  | /* Calculate checksum */ | 
|  | abs_reg = (phyad << DM8806_REGAD_WIDTH); | 
|  | abs_reg |= (regad & BIT_MASK(DM8806_REGAD_WIDTH)); | 
|  | sw_checksum = phy_calculate_checksum(data, abs_reg, DM8806_PHY_WRITE); | 
|  | sw_checksum &= BIT_MASK(8); | 
|  | /* Write calculated checksum to the PHY register 339H.[7:0] */ | 
|  | res = mdio_write(cfg->mdio, DM8806_SMI_BUS_ERR_CHK_PHY_ADDRESS, | 
|  | DM8806_SMI_BUS_ERR_CHK_REG_ADDRESS, sw_checksum); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to write calculated checksum to the PHY register, " | 
|  | "error code: %d", | 
|  | res); | 
|  | return res; | 
|  | } | 
|  |  | 
|  | /* Read status of the checksum from Serial Bus Error Check Register | 
|  | * 339H.[8]. | 
|  | */ | 
|  | res = mdio_read(cfg->mdio, DM8806_SMI_BUS_ERR_CHK_PHY_ADDRESS, | 
|  | DM8806_SMI_BUS_ERR_CHK_REG_ADDRESS, &checksum_status); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to read hardware calculated checksum from PHY, error code: " | 
|  | "%d", | 
|  | res); | 
|  | return res; | 
|  | } | 
|  | /* Checksum status is present on the 8-th bit of the Serial Bus Error | 
|  | * Check Register (339h) [8]. | 
|  | */ | 
|  | checksum_mismatch = (bool)(checksum_status & BIT(DM8806_SMI_ERR)); | 
|  |  | 
|  | /* Repeat the writing procedure for the number of attempts defined in | 
|  | * KConfig after which the transfer will failed. | 
|  | */ | 
|  | if (CONFIG_PHY_DM8806_SMI_BUS_CHECK_REPETITION > 0) { | 
|  | repetition++; | 
|  | if (checksum_mismatch) { | 
|  | LOG_WRN("%d repeat of PHY read procedure due to checksum error.", | 
|  | repetition); | 
|  | if (repetition >= CONFIG_PHY_DM8806_SMI_BUS_CHECK_REPETITION) { | 
|  | LOG_ERR("Maximum number of PHY write repetition exceed."); | 
|  | res = -EIO; | 
|  | } | 
|  | } else { | 
|  | break; | 
|  | } | 
|  | /* Do not repeat the transfer if repetition number is set to 0. Just check | 
|  | * the checksum in this case and report the error in case of wrong checksum | 
|  | * sum. | 
|  | */ | 
|  | } else { | 
|  | if (checksum_mismatch) { | 
|  | LOG_ERR("Wrong checksum, during PHY write procedure."); | 
|  | res = -EIO; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } while (repetition < CONFIG_PHY_DM8806_SMI_BUS_CHECK_REPETITION); | 
|  | #endif | 
|  |  | 
|  | return res; | 
|  | } | 
|  |  | 
|  | static int phy_dm8806_read_reg(const struct device *dev, uint8_t phyad, uint8_t regad, | 
|  | uint16_t *data) | 
|  | { | 
|  | int res = 0; | 
|  | const struct phy_dm8806_config *cfg = dev->config; | 
|  |  | 
|  | /* SMI bus check function should be activated each time, before reading | 
|  | * procedure to the DM8806 registers. This is standard procedure described in | 
|  | * the datasheet of the DM8806. | 
|  | */ | 
|  | #ifdef CONFIG_PHY_DM8806_SMI_BUS_CHECK | 
|  | uint16_t hw_checksum; | 
|  | uint16_t sw_checksum = 0; | 
|  | uint16_t abs_reg; | 
|  | int repetition = 0; | 
|  |  | 
|  | do { | 
|  | /* Set register 33AH.[0] = 1 to enable SMI Bus Error Check function. */ | 
|  | res = mdio_write(cfg->mdio, DM8806_SMI_BUS_CTRL_PHY_ADDRESS, | 
|  | DM8806_SMI_BUS_CTRL_REG_ADDRESS, DM8806_SMI_ECE); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to write data to PHY register: SMI_BUS_CTRL_REG_ADDRESS, " | 
|  | "error code: %d", | 
|  | res); | 
|  | return res; | 
|  | } | 
|  | #endif | 
|  | res = mdio_read(cfg->mdio, phyad, regad, data); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to read data from PHY, error code: %d", res); | 
|  | return res; | 
|  | } | 
|  | #ifdef CONFIG_PHY_DM8806_SMI_BUS_CHECK | 
|  | /* Read hardware calculated checksum from Serial Bus Error Check Register. */ | 
|  | res = mdio_read(cfg->mdio, DM8806_SMI_BUS_ERR_CHK_PHY_ADDRESS, | 
|  | DM8806_SMI_BUS_ERR_CHK_REG_ADDRESS, &hw_checksum); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to read hardware calculated checksum from PHY, error code: " | 
|  | "%d", | 
|  | res); | 
|  | return res; | 
|  | } | 
|  | /* Calculate checksum */ | 
|  | abs_reg = (phyad << DM8806_REGAD_WIDTH); | 
|  | abs_reg |= (regad & BIT_MASK(DM8806_REGAD_WIDTH)); | 
|  | sw_checksum = phy_calculate_checksum(*data, abs_reg, DM8806_PHY_READ); | 
|  |  | 
|  | if (CONFIG_PHY_DM8806_SMI_BUS_CHECK_REPETITION > 0) { | 
|  | repetition++; | 
|  | if (hw_checksum == sw_checksum) { | 
|  | break; | 
|  | } | 
|  | LOG_WRN("%d repeat PHY read procedure due to checksum error.", repetition); | 
|  | if (repetition >= CONFIG_PHY_DM8806_SMI_BUS_CHECK_REPETITION) { | 
|  | LOG_ERR("Maximum number of PHY read repetition exceed."); | 
|  | res = -EIO; | 
|  | } | 
|  | } else { | 
|  | if (hw_checksum != sw_checksum) { | 
|  | LOG_ERR("Wrong checksum, during PHY read procedure."); | 
|  | res = -EIO; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } while (repetition < CONFIG_PHY_DM8806_SMI_BUS_CHECK_REPETITION); | 
|  | #endif | 
|  |  | 
|  | return res; | 
|  | } | 
|  |  | 
|  | 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; | 
|  | int res; | 
|  |  | 
|  | 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) | 
|  | */ | 
|  | res = mdio_read(cfg->mdio, DM8806_INT_STAT_PHY_ADDR, DM8806_INT_STAT_REG_ADDR, &data); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to read regad: %d, error: %d", DM8806_INT_STAT_REG_ADDR, res); | 
|  | } | 
|  |  | 
|  | data |= 0x1; | 
|  |  | 
|  | res = mdio_write(cfg->mdio, DM8806_INT_STAT_PHY_ADDR, DM8806_INT_STAT_REG_ADDR, data); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to write regad: %d, error: %d", DM8806_INT_STAT_REG_ADDR, res); | 
|  | } | 
|  |  | 
|  | 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, DM8806_INT_MASK_CTRL_PHY_ADDR, DM8806_INT_MASK_CTRL_REG_ADDR, | 
|  | &data); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", res); | 
|  | return res; | 
|  | } | 
|  | data |= 0x1; | 
|  | res = mdio_write(cfg->mdio, DM8806_INT_MASK_CTRL_PHY_ADDR, DM8806_INT_MASK_CTRL_REG_ADDR, | 
|  | data); | 
|  | if (res < 0) { | 
|  | 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, DM8806_WOLL_CTRL_REG_PHY_ADDR, DM8806_WOLL_CTRL_REG_REG_ADDR, | 
|  | &data); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", res); | 
|  | return res; | 
|  | } | 
|  | data |= 0xF; | 
|  | res = mdio_write(cfg->mdio, DM8806_WOLL_CTRL_REG_PHY_ADDR, DM8806_WOLL_CTRL_REG_REG_ADDR, | 
|  | data); | 
|  | if (res < 0) { | 
|  | 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, DM8806_PHY_ADDRESS_18H, DM8806_PORT5_MAC_CONTROL, &val); | 
|  | if (ret) { | 
|  | LOG_ERR("Failed to read PORT5_MAC_CONTROL: %i", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Activate default working mode*/ | 
|  | val |= (DM8806_P5_50M_INT_CLK_SOURCE | DM8806_P5_50M_CLK_OUT_ENABLE | DM8806_P5_EN_FORCE); | 
|  | val &= (DM8806_P5_SPEED_100M | DM8806_P5_FULL_DUPLEX | DM8806_P5_FORCE_LINK_ON); | 
|  |  | 
|  | ret = mdio_write(cfg->mdio, DM8806_PHY_ADDRESS_18H, DM8806_PORT5_MAC_CONTROL, val); | 
|  | if (ret) { | 
|  | LOG_ERR("Failed to write PORT5_MAC_CONTROL, %i", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = mdio_read(cfg->mdio, DM8806_PHY_ADDRESS_18H, DM8806_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 &= DM8806_LED_MODE_0; | 
|  | ret = mdio_write(cfg->mdio, DM8806_PHY_ADDRESS_18H, DM8806_IRQ_LED_CONTROL, val); | 
|  | if (ret) { | 
|  | LOG_ERR("Failed to write IRQ_LED_CONTROL, %i", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (!IS_ENABLED(CONFIG_PHY_DM8806_ENERGY_EFFICIENT_MODE)) { | 
|  | /* Disable - 802.3az Energy Efficient Ethernet | 
|  | * The switch chip DM8806 only works reliably in this mode. | 
|  | */ | 
|  | for (uint32_t port_address = DM8806_SWITCH_REGISTER_OFFSET; | 
|  | port_address <= DM8806_SWITCH_REGISTER_OFFSET + 5; port_address++) { | 
|  | ret = mdio_read(cfg->mdio, port_address, | 
|  | DM8806_ENERGY_EFFICIENT_ETH_CTRL_REG_ADDR, &val); | 
|  | if (ret) { | 
|  | LOG_ERR("Failed to read ENERGY_EFFICIENT_ETH_CTRL_REG, %i", ret); | 
|  | return ret; | 
|  | } | 
|  | val &= (~DM8806_EEE_EN); | 
|  | ret = mdio_write(cfg->mdio, port_address, | 
|  | DM8806_ENERGY_EFFICIENT_ETH_CTRL_REG_ADDR, val); | 
|  | if (ret) { | 
|  | LOG_ERR("Failed to write ENERGY_EFFICIENT_ETH_CTRL_REG, %i", ret); | 
|  | return ret; | 
|  | } | 
|  | } | 
|  | } /* CONFIG_PHY_DM8806_ENERGY_EFFICIENT_MODE */ | 
|  |  | 
|  | #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; | 
|  | } | 
|  |  | 
|  | 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; | 
|  |  | 
|  | #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 = phy_dm8806_read_reg(dev, cfg->switch_addr, DM8806_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 >>= DM8806_SPEED_AND_DUPLEX_OFFSET; | 
|  | switch (status & DM8806_SPEED_AND_DUPLEX_MASK) { | 
|  | case DM8806_SPEED_10MBPS_HALF_DUPLEX: | 
|  | state->speed = LINK_HALF_10BASE; | 
|  | break; | 
|  | case DM8806_SPEED_10MBPS_FULL_DUPLEX: | 
|  | state->speed = LINK_FULL_10BASE; | 
|  | break; | 
|  | case DM8806_SPEED_100MBPS_HALF_DUPLEX: | 
|  | state->speed = LINK_HALF_100BASE; | 
|  | break; | 
|  | case DM8806_SPEED_100MBPS_FULL_DUPLEX: | 
|  | state->speed = LINK_FULL_100BASE; | 
|  | break; | 
|  | } | 
|  | /* Extract link status from Switch Per-Port Register: Per Port Status Data | 
|  | * Register | 
|  | */ | 
|  | status = data; | 
|  | if (status & DM8806_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, | 
|  | enum phy_cfg_link_flag flags) | 
|  | { | 
|  | int ret; | 
|  | uint16_t data; | 
|  | uint16_t req_speed; | 
|  | const struct phy_dm8806_config *cfg = dev->config; | 
|  |  | 
|  | ARG_UNUSED(flags); | 
|  |  | 
|  | req_speed = adv_speeds; | 
|  | switch (req_speed) { | 
|  | case LINK_HALF_10BASE: | 
|  | req_speed = DM8806_MODE_10_BASET_HALF_DUPLEX; | 
|  | break; | 
|  |  | 
|  | case LINK_FULL_10BASE: | 
|  | req_speed = DM8806_MODE_10_BASET_FULL_DUPLEX; | 
|  | break; | 
|  |  | 
|  | case LINK_HALF_100BASE: | 
|  | req_speed = DM8806_MODE_100_BASET_HALF_DUPLEX; | 
|  | break; | 
|  |  | 
|  | case LINK_FULL_100BASE: | 
|  | req_speed = DM8806_MODE_100_BASET_FULL_DUPLEX; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | LOG_ERR("Invalid speed %d for PHY (%d)", adv_speeds, cfg->phy_addr); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* Power down */ | 
|  | ret = phy_dm8806_read_reg(dev, cfg->phy_addr, DM8806_PORTX_PHY_CONTROL_REGISTER, &data); | 
|  | if (ret) { | 
|  | LOG_ERR("Failes to read data drom DM8806"); | 
|  | return ret; | 
|  | } | 
|  | k_busy_wait(500); | 
|  | data |= DM8806_POWER_DOWN; | 
|  | ret = phy_dm8806_write_reg(dev, cfg->phy_addr, DM8806_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 = phy_dm8806_read_reg(dev, cfg->phy_addr, DM8806_PORTX_PHY_CONTROL_REGISTER, &data); | 
|  | if (ret) { | 
|  | LOG_ERR("Failed to write data to DM8806"); | 
|  | return ret; | 
|  | } | 
|  | k_busy_wait(500); | 
|  | data &= ~(DM8806_AUTO_NEGOTIATION); | 
|  | ret = phy_dm8806_write_reg(dev, cfg->phy_addr, DM8806_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 = phy_dm8806_read_reg(dev, cfg->phy_addr, DM8806_PORTX_PHY_CONTROL_REGISTER, &data); | 
|  | if (ret) { | 
|  | LOG_ERR("Failed to read data from DM8806"); | 
|  | return ret; | 
|  | } | 
|  | k_busy_wait(500); | 
|  | data &= ~(DM8806_LINK_SPEED | DM8806_DUPLEX_MODE); | 
|  | data |= req_speed; | 
|  | ret = phy_dm8806_write_reg(dev, cfg->phy_addr, DM8806_PORTX_PHY_CONTROL_REGISTER, data); | 
|  | if (ret) { | 
|  | LOG_ERR("Failed to write data to DM8806"); | 
|  | return ret; | 
|  | } | 
|  | k_busy_wait(500); | 
|  |  | 
|  | /* Power up ethernet port*/ | 
|  | ret = phy_dm8806_read_reg(dev, cfg->phy_addr, DM8806_PORTX_PHY_CONTROL_REGISTER, &data); | 
|  | if (ret) { | 
|  | LOG_ERR("Failes to read data drom DM8806"); | 
|  | return ret; | 
|  | } | 
|  | k_busy_wait(500); | 
|  | data &= ~(DM8806_POWER_DOWN); | 
|  | ret = phy_dm8806_write_reg(dev, cfg->phy_addr, DM8806_PORTX_PHY_CONTROL_REGISTER, data); | 
|  | if (ret) { | 
|  | LOG_ERR("Failed to write data to DM8806"); | 
|  | return ret; | 
|  | } | 
|  | k_busy_wait(500); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | 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, cfg->switch_addr, reg_addr, (uint16_t *)data); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to read data from DM8806"); | 
|  | return res; | 
|  | } | 
|  | return res; | 
|  | } | 
|  |  | 
|  | 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, cfg->switch_addr, reg_addr, data); | 
|  | if (res < 0) { | 
|  | LOG_ERR("Failed to write data to DM8806"); | 
|  | return res; | 
|  | } | 
|  | return res; | 
|  | } | 
|  |  | 
|  | 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, | 
|  | }; | 
|  |  | 
|  | #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_INST_PROP(n, reg_switch),                                        \ | 
|  | .gpio_int = GPIO_DT_SPEC_INST_GET(n, int_gpios),                                   \ | 
|  | .gpio_rst = GPIO_DT_SPEC_INST_GET(n, reset_gpios),                                 \ | 
|  | } | 
|  |  | 
|  | #define DM8806_PHY_INITIALIZE(n)                                                                   \ | 
|  | DM8806_PHY_DEFINE_CONFIG(n);                                                               \ | 
|  | static struct phy_dm8806_data phy_dm8806_data_##n = {                                      \ | 
|  | .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,                      \ | 
|  | &phy_dm8806_config_##n, POST_KERNEL, CONFIG_PHY_INIT_PRIORITY,       \ | 
|  | &phy_dm8806_api); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(DM8806_PHY_INITIALIZE) |