drivers: dm8806: add new driver for davicom dm8806 phy mac

New driver for Davicom DM8806 PHY. Driver is using standar mdio API
to manage the DM8806 switch controller. Register access needs the
PHY addres or switch address to be one of five possible values, since
DM8806 has built-in five PHY's. These values should be defined in the
application .dts file. One DM8806 ethernet port must corresponds with
one ethernet PHY node with two properties for ethernet port: one for
PHY address and one for switch address - <reg> for register access from
Internal PHY Register area and <reg-switch> for register access from
Switch Per-Port Registers area. Device tree example below:

example device-tree:
  dm8806_phy: ethernet-phy@0 {
    reg = <2>;
    reg-switch = <8>;
    compatible = "davicom,dm8806-phy";
    status = "okay";
    davicom,interface-type = "rmii";
    reset-gpio = <&gpiod 2 GPIO_ACTIVE_LOW>;
    interrupt-gpio = <&gpioc 1 GPIO_ACTIVE_HIGH>;
  };

Signed-off-by: Robert Slawinski <robert.slawinski1@gmail.com>
This commit is contained in:
Robert Slawinski 2024-09-12 15:33:07 +02:00 committed by Benjamin Cabé
commit 19e74f1ba0
5 changed files with 417 additions and 0 deletions

View file

@ -2,6 +2,7 @@
zephyr_library_sources_ifdef(CONFIG_PHY_GENERIC_MII phy_mii.c) 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_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_TJA1103 phy_tja1103.c)
zephyr_library_sources_ifdef(CONFIG_PHY_MICROCHIP_KSZ8081 phy_microchip_ksz8081.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_TI_DP83825 phy_ti_dp83825.c)

View file

@ -40,6 +40,13 @@ config PHY_ADIN2111
help help
Enable ADIN2111 PHY driver. 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 config PHY_MICROCHIP_KSZ8081
bool "Microchip KSZ8081 PHY Driver" bool "Microchip KSZ8081 PHY Driver"
default y default y

View file

@ -0,0 +1,265 @@
/*
* 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;
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)

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2024 Robert Slawinski <robert.slawinski1@gmail.com>
*
* 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))

View file

@ -0,0 +1,61 @@
# Copyright 2024 Robert Slawinski <robert.slawinski1@gmail.com>
# 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"