blob: 9f7a32338fdb7edda737a44666da960f14b8738b [file] [log] [blame]
/*
* Copyright (c) 2024 Microchip Technology Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT microchip_t1s_phy
#include <zephyr/kernel.h>
#include <zephyr/net/phy.h>
#include <zephyr/net/mii.h>
#include <zephyr/net/mdio.h>
#include <zephyr/drivers/mdio.h>
#define LOG_MODULE_NAME phy_mc_t1s
#define LOG_LEVEL CONFIG_PHY_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
/* Support: Microchip Phys:
* lan8650/1 Rev.B0/B1 Internal PHYs
* lan8670/1/2 Rev.C1/C2 PHYs
*/
/* Both Rev.B0 and B1 clause 22 PHYID's are same due to B1 chip limitation */
#define PHY_ID_LAN865X_REVB 0x0007C1B3
#define PHY_ID_LAN867X_REVC1 0x0007C164
#define PHY_ID_LAN867X_REVC2 0x0007C165
/* Configuration param registers */
#define LAN865X_REG_CFGPARAM_ADDR 0x00D8
#define LAN865X_REG_CFGPARAM_DATA 0x00D9
#define LAN865X_REG_CFGPARAM_CTRL 0x00DA
#define LAN865X_REG_STS2 0x0019
#define LAN865X_CFGPARAM_READ_ENABLE BIT(1)
/* Collision detection enable/disable registers */
#define LAN86XX_DISABLE_COL_DET 0x0000
#define LAN86XX_ENABLE_COL_DET 0x8000
#define LAN86XX_COL_DET_MASK 0x8000
#define LAN86XX_REG_COL_DET_CTRL0 0x0087
/* Structure holding configuration register address and value */
typedef struct {
uint32_t address;
uint16_t value;
} lan865x_config;
/* LAN865x Rev.B0/B1 configuration parameters from AN1760
* As per the Configuration Application Note AN1760 published in the below link,
* https://www.microchip.com/en-us/application-notes/an1760
* Revision F (DS60001760G - June 2024)
* Addresses 0x0084, 0x008A, 0x00AD, 0x00AE and 0x00AF will be updated with cfgparam1, cfgparam2,
* cfgparam3, cfgparam4 and cfgparam5 respectively.
*
* LAN867x Rev.C1/C2 configuration settings described in AN1699 are equal to
* the first 11 configuration settings and all the sqi fixup settings from
* LAN865x Rev.B0/B1. So the same fixup registers and values from LAN865x
* Rev.B0/B1 are used for LAN867x Rev.C1/C2 to avoid duplication.
* Refer the below link for the AN1699,
* https://www.microchip.com/en-us/application-notes/an1699
* Revision E (DS60001699F - June 2024)
*/
static lan865x_config lan865x_revb_config[] = {
{.address = 0x00D0, .value = 0x3F31}, {.address = 0x00E0, .value = 0xC000},
{.address = 0x0084, .value = 0x0000}, {.address = 0x008A, .value = 0x0000},
{.address = 0x00E9, .value = 0x9E50}, {.address = 0x00F5, .value = 0x1CF8},
{.address = 0x00F4, .value = 0xC020}, {.address = 0x00F8, .value = 0xB900},
{.address = 0x00F9, .value = 0x4E53}, {.address = 0x0081, .value = 0x0080},
{.address = 0x0091, .value = 0x9660}, {.address = 0x0043, .value = 0x00FF},
{.address = 0x0044, .value = 0xFFFF}, {.address = 0x0045, .value = 0x0000},
{.address = 0x0053, .value = 0x00FF}, {.address = 0x0054, .value = 0xFFFF},
{.address = 0x0055, .value = 0x0000}, {.address = 0x0040, .value = 0x0002},
{.address = 0x0050, .value = 0x0002}, {.address = 0x00AD, .value = 0x0000},
{.address = 0x00AE, .value = 0x0000}, {.address = 0x00AF, .value = 0x0000},
{.address = 0x00B0, .value = 0x0103}, {.address = 0x00B1, .value = 0x0910},
{.address = 0x00B2, .value = 0x1D26}, {.address = 0x00B3, .value = 0x002A},
{.address = 0x00B4, .value = 0x0103}, {.address = 0x00B5, .value = 0x070D},
{.address = 0x00B6, .value = 0x1720}, {.address = 0x00B7, .value = 0x0027},
{.address = 0x00B8, .value = 0x0509}, {.address = 0x00B9, .value = 0x0E13},
{.address = 0x00BA, .value = 0x1C25}, {.address = 0x00BB, .value = 0x002B},
};
struct mc_t1s_plca_config {
bool enable;
uint8_t node_id;
uint8_t node_count;
uint8_t burst_count;
uint8_t burst_timer;
uint8_t to_timer;
};
struct mc_t1s_config {
uint8_t phy_addr;
const struct device *mdio;
struct mc_t1s_plca_config *plca;
};
struct mc_t1s_data {
uint32_t phy_id;
const struct device *dev;
struct phy_link_state state;
phy_callback_t cb;
void *cb_data;
struct k_work_delayable phy_monitor_work;
};
static int phy_mc_t1s_read(const struct device *dev, uint16_t reg, uint32_t *data)
{
const struct mc_t1s_config *cfg = dev->config;
int ret;
/* Make sure excessive bits 16-31 are reset */
*data = 0U;
mdio_bus_enable(cfg->mdio);
ret = mdio_read(cfg->mdio, cfg->phy_addr, reg, (uint16_t *)data);
mdio_bus_disable(cfg->mdio);
return ret;
}
static int phy_mc_t1s_write(const struct device *dev, uint16_t reg, uint32_t data)
{
const struct mc_t1s_config *cfg = dev->config;
int ret;
mdio_bus_enable(cfg->mdio);
ret = mdio_write(cfg->mdio, cfg->phy_addr, reg, (uint16_t)data);
mdio_bus_disable(cfg->mdio);
return ret;
}
static int mdio_setup_c45_indirect_access(const struct device *dev, uint16_t devad, uint16_t reg)
{
const struct mc_t1s_config *cfg = dev->config;
int ret;
ret = mdio_write(cfg->mdio, cfg->phy_addr, MII_MMD_ACR, devad);
if (ret) {
return ret;
}
ret = mdio_write(cfg->mdio, cfg->phy_addr, MII_MMD_AADR, reg);
if (ret < 0) {
return ret;
}
return mdio_write(cfg->mdio, cfg->phy_addr, MII_MMD_ACR, devad | BIT(14));
}
static int phy_mc_t1s_c45_read(const struct device *dev, uint8_t devad, uint16_t reg, uint16_t *val)
{
const struct mc_t1s_config *cfg = dev->config;
struct mc_t1s_data *data = dev->data;
int ret;
/* C45 direct read access is only supported by LAN865x internal PHY */
if (data->phy_id == PHY_ID_LAN865X_REVB) {
return mdio_read_c45(cfg->mdio, cfg->phy_addr, devad, reg, val);
}
mdio_bus_enable(cfg->mdio);
/* Read C45 registers using C22 indirect access registers */
ret = mdio_setup_c45_indirect_access(dev, devad, reg);
if (ret) {
mdio_bus_disable(cfg->mdio);
return ret;
}
ret = mdio_read(cfg->mdio, cfg->phy_addr, MII_MMD_AADR, val);
mdio_bus_disable(cfg->mdio);
return ret;
}
static int phy_mc_t1s_c45_write(const struct device *dev, uint8_t devad, uint16_t reg, uint16_t val)
{
const struct mc_t1s_config *cfg = dev->config;
struct mc_t1s_data *data = dev->data;
int ret;
/* C45 direct write access is only supported by LAN865x internal PHY */
if (data->phy_id == PHY_ID_LAN865X_REVB) {
return mdio_write_c45(cfg->mdio, cfg->phy_addr, devad, reg, val);
}
mdio_bus_enable(cfg->mdio);
/* Write C45 registers using C22 indirect access registers */
ret = mdio_setup_c45_indirect_access(dev, devad, reg);
if (ret) {
mdio_bus_disable(cfg->mdio);
return ret;
}
ret = mdio_write(cfg->mdio, cfg->phy_addr, MII_MMD_AADR, val);
mdio_bus_disable(cfg->mdio);
return ret;
}
static int phy_mc_t1s_get_link(const struct device *dev, struct phy_link_state *state)
{
const struct mc_t1s_config *cfg = dev->config;
struct mc_t1s_data *data = dev->data;
struct phy_link_state old_state = data->state;
uint32_t value = 0;
int ret;
ret = phy_mc_t1s_read(dev, MII_BMSR, &value);
if (ret) {
LOG_ERR("Failed MII_BMSR register read: %d\n", ret);
return ret;
}
state->is_up = value & MII_BMSR_LINK_STATUS;
state->speed = LINK_HALF_10BASE;
if (memcmp(&old_state, state, sizeof(struct phy_link_state)) != 0) {
if (state->is_up) {
LOG_INF("PHY (%d) Link speed 10 Mbps, half duplex\n", cfg->phy_addr);
}
}
return 0;
}
static int phy_mc_t1s_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data)
{
struct mc_t1s_data *data = dev->data;
data->cb = cb;
data->cb_data = user_data;
if (data->cb) {
data->cb(dev, &data->state, data->cb_data);
}
return 0;
}
static void phy_monitor_work_handler(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct mc_t1s_data *const data = CONTAINER_OF(dwork, struct mc_t1s_data, phy_monitor_work);
const struct device *dev = data->dev;
if (!data->cb) {
k_work_reschedule(&data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD));
return;
}
phy_mc_t1s_get_link(dev, &data->state);
data->cb(dev, &data->state, data->cb_data);
/* Submit delayed work */
k_work_reschedule(&data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD));
}
/* Pulled from AN1760 describing 'indirect read'
*
* write_register(0x4, 0x00D8, addr)
* write_register(0x4, 0x00DA, 0x2)
* return (int8)(read_register(0x4, 0x00D9))
*
* 0x4 refers to memory map selector 4, which maps to MDIO_MMD_VENDOR_SPECIFIC2
*/
static int lan865x_indirect_read(const struct device *dev, uint16_t addr, uint16_t *value)
{
int ret;
ret = phy_mc_t1s_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2, LAN865X_REG_CFGPARAM_ADDR, addr);
if (ret) {
return ret;
}
ret = phy_mc_t1s_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2, LAN865X_REG_CFGPARAM_CTRL,
LAN865X_CFGPARAM_READ_ENABLE);
if (ret) {
return ret;
}
return phy_mc_t1s_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC2, LAN865X_REG_CFGPARAM_DATA,
value);
}
static int lan865x_calculate_offset(const struct device *dev, uint16_t address, int8_t *offset)
{
uint16_t value;
int ret;
ret = lan865x_indirect_read(dev, address, &value);
if (ret) {
return ret;
}
/* 5-bit signed value, sign extend */
value &= GENMASK(4, 0);
if (value & BIT(4)) {
*offset = (int8_t)((uint8_t)value - 0x20);
} else {
*offset = (int8_t)value;
}
return 0;
}
static void lan865x_update_cfgparam(uint32_t address, uint16_t cfgparam)
{
for (uint8_t i = 0; i < ARRAY_SIZE(lan865x_revb_config); i++) {
if (lan865x_revb_config[i].address == address) {
lan865x_revb_config[i].value = cfgparam;
}
}
}
static int lan865x_calculate_update_cfgparams(const struct device *dev)
{
uint16_t cfgparam;
int8_t offset1;
int8_t offset2;
int ret;
/* Calculate offset1 */
ret = lan865x_calculate_offset(dev, 0x04, &offset1);
if (ret) {
return ret;
}
/* Calculate offset2 */
ret = lan865x_calculate_offset(dev, 0x08, &offset2);
if (ret) {
return ret;
}
/* Calculate & update cfgparam1 for configuration */
cfgparam = (uint16_t)(((9 + offset1) & 0x3F) << 10) |
(uint16_t)(((14 + offset1) & 0x3F) << 4) | 0x03;
lan865x_update_cfgparam(0x0084, cfgparam);
/* Calculate & update cfgparam2 for configuration */
cfgparam = (uint16_t)(((40 + offset2) & 0x3F) << 10);
lan865x_update_cfgparam(0x008A, cfgparam);
/* Calculate & update cfgparam3 for configuration */
cfgparam = (uint16_t)(((5 + offset1) & 0x3F) << 8) | (uint16_t)((9 + offset1) & 0x3F);
lan865x_update_cfgparam(0x00AD, cfgparam);
/* Calculate & update cfgparam4 for configuration */
cfgparam = (uint16_t)(((9 + offset1) & 0x3F) << 8) | (uint16_t)((14 + offset1) & 0x3F);
lan865x_update_cfgparam(0x00AE, cfgparam);
/* Calculate & update cfgparam5 for configuration */
cfgparam = (uint16_t)(((17 + offset1) & 0x3F) << 8) | (uint16_t)((22 + offset1) & 0x3F);
lan865x_update_cfgparam(0x00AF, cfgparam);
return 0;
}
static int phy_mc_lan865x_revb_config_init(const struct device *dev)
{
int ret;
ret = lan865x_calculate_update_cfgparams(dev);
if (ret) {
return ret;
}
/* Configure lan865x initial settings */
for (int i = 0; i < ARRAY_SIZE(lan865x_revb_config); i++) {
ret = phy_mc_t1s_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2,
lan865x_revb_config[i].address,
lan865x_revb_config[i].value);
if (ret) {
return ret;
}
}
return 0;
}
/* LAN867x Rev.C1/C2 configuration settings are equal to the first 11 configuration settings and all
* the sqi fixup settings from LAN865x Rev.B0/B1. So the same fixup registers and values from
* LAN865x Rev.B0/B1 are used for LAN867x Rev.C1/C2 to avoid duplication.
* Refer the below links for the comparison.
* https://www.microchip.com/en-us/application-notes/an1760
* Revision F (DS60001760G - June 2024)
* https://www.microchip.com/en-us/application-notes/an1699
* Revision E (DS60001699F - June 2024)
*/
static int phy_mc_lan867x_revc_config_init(const struct device *dev)
{
int ret;
ret = lan865x_calculate_update_cfgparams(dev);
if (ret) {
return ret;
}
for (int i = 0; i < ARRAY_SIZE(lan865x_revb_config); i++) {
ret = phy_mc_t1s_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2,
lan865x_revb_config[i].address,
lan865x_revb_config[i].value);
if (ret) {
return ret;
}
/* LAN867x Rev.C1/C2 configuration settings are equal to the first 11 configuration
* settings and all the sqi fixup settings from LAN865x Rev.B0/B1. So the 8
* inbetween configuration settings are skipped.
*/
if (i == 10) {
i += 8;
}
}
return 0;
}
static int lan86xx_config_collision_detection(const struct device *dev, bool plca_enable)
{
uint16_t val;
uint16_t new;
int ret;
ret = phy_mc_t1s_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC2, LAN86XX_REG_COL_DET_CTRL0, &val);
if (ret) {
return ret;
}
if (plca_enable) {
new = (val & ~LAN86XX_COL_DET_MASK) | LAN86XX_DISABLE_COL_DET;
} else {
new = (val & ~LAN86XX_COL_DET_MASK) | LAN86XX_ENABLE_COL_DET;
}
if (new == val) {
return 0;
}
return phy_mc_t1s_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC2, LAN86XX_REG_COL_DET_CTRL0, new);
}
static int phy_mc_t1s_id(const struct device *dev, uint32_t *phy_id)
{
uint32_t value;
int ret;
ret = phy_mc_t1s_read(dev, MII_PHYID1R, &value);
if (ret) {
LOG_ERR("Failed MII_PHYID1R register read: %d\n", ret);
return ret;
}
*phy_id = value << 16;
ret = phy_mc_t1s_read(dev, MII_PHYID2R, &value);
if (ret) {
LOG_ERR("Failed MII_PHYID2R register read: %d\n", ret);
return ret;
}
*phy_id |= value;
return 0;
}
static int phy_mc_t1s_set_plca_cfg(const struct device *dev, struct phy_plca_cfg *plca_cfg)
{
int ret;
ret = genphy_set_plca_cfg(dev, plca_cfg);
if (ret) {
return ret;
}
return lan86xx_config_collision_detection(dev, plca_cfg->enable);
}
static int phy_mc_t1s_set_dt_plca(const struct device *dev)
{
const struct mc_t1s_config *cfg = dev->config;
struct phy_plca_cfg plca_cfg;
if (!cfg->plca->enable) {
return 0;
}
plca_cfg.enable = cfg->plca->enable;
plca_cfg.node_id = cfg->plca->node_id;
plca_cfg.node_count = cfg->plca->node_count;
plca_cfg.burst_count = cfg->plca->burst_count;
plca_cfg.burst_timer = cfg->plca->burst_timer;
plca_cfg.to_timer = cfg->plca->to_timer;
return phy_mc_t1s_set_plca_cfg(dev, &plca_cfg);
}
static int phy_mc_t1s_init(const struct device *dev)
{
struct mc_t1s_data *data = dev->data;
int ret;
data->dev = dev;
ret = phy_mc_t1s_id(dev, &data->phy_id);
if (ret) {
return ret;
}
switch (data->phy_id) {
case PHY_ID_LAN867X_REVC1:
case PHY_ID_LAN867X_REVC2:
ret = phy_mc_lan867x_revc_config_init(dev);
if (ret) {
LOG_ERR("PHY initial configuration error: %d\n", ret);
return ret;
}
break;
case PHY_ID_LAN865X_REVB:
ret = phy_mc_lan865x_revb_config_init(dev);
if (ret) {
LOG_ERR("PHY initial configuration error: %d\n", ret);
return ret;
}
break;
default:
LOG_ERR("Unsupported PHY ID: %x\n", data->phy_id);
return -ENODEV;
}
ret = phy_mc_t1s_set_dt_plca(dev);
if (ret) {
return ret;
}
k_work_init_delayable(&data->phy_monitor_work, phy_monitor_work_handler);
phy_monitor_work_handler(&data->phy_monitor_work.work);
return 0;
}
static DEVICE_API(ethphy, mc_t1s_phy_api) = {
.get_link = phy_mc_t1s_get_link,
.link_cb_set = phy_mc_t1s_link_cb_set,
.set_plca_cfg = phy_mc_t1s_set_plca_cfg,
.get_plca_cfg = genphy_get_plca_cfg,
.get_plca_sts = genphy_get_plca_sts,
.read = phy_mc_t1s_read,
.write = phy_mc_t1s_write,
.read_c45 = phy_mc_t1s_c45_read,
.write_c45 = phy_mc_t1s_c45_write,
};
#define MICROCHIP_T1S_PHY_INIT(n) \
static struct mc_t1s_plca_config mc_t1s_plca_##n##_config = { \
.enable = DT_INST_PROP(n, plca_enable), \
.node_id = DT_INST_PROP(n, plca_node_id), \
.node_count = DT_INST_PROP(n, plca_node_count), \
.burst_count = DT_INST_PROP(n, plca_burst_count), \
.burst_timer = DT_INST_PROP(n, plca_burst_timer), \
.to_timer = DT_INST_PROP(n, plca_to_timer), \
}; \
\
static const struct mc_t1s_config mc_t1s_##n##_config = { \
.phy_addr = DT_INST_REG_ADDR(n), \
.mdio = DEVICE_DT_GET(DT_INST_PARENT(n)), \
.plca = &mc_t1s_plca_##n##_config, \
}; \
\
static struct mc_t1s_data mc_t1s_##n##_data; \
\
DEVICE_DT_INST_DEFINE(n, &phy_mc_t1s_init, NULL, &mc_t1s_##n##_data, &mc_t1s_##n##_config, \
POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, &mc_t1s_phy_api);
DT_INST_FOREACH_STATUS_OKAY(MICROCHIP_T1S_PHY_INIT);