| /* |
| * Copyright 2024 NXP |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nxp_enet_qos_mdio |
| |
| #include <zephyr/net/mdio.h> |
| #include <zephyr/drivers/mdio.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/ethernet/eth_nxp_enet_qos.h> |
| #include <zephyr/sys/util.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(mdio_nxp_enet_qos, CONFIG_MDIO_LOG_LEVEL); |
| |
| struct nxp_enet_qos_mdio_config { |
| const struct device *enet_dev; |
| }; |
| |
| struct nxp_enet_qos_mdio_data { |
| struct k_mutex mdio_mutex; |
| }; |
| |
| struct mdio_transaction { |
| enum mdio_opcode op; |
| union { |
| uint16_t write_data; |
| uint16_t *read_data; |
| }; |
| uint8_t portaddr; |
| uint8_t regaddr; |
| enet_qos_t *base; |
| struct k_mutex *mdio_bus_mutex; |
| }; |
| |
| static bool check_busy(enet_qos_t *base) |
| { |
| uint32_t val = base->MAC_MDIO_ADDRESS; |
| |
| /* Return the busy bit */ |
| return ENET_QOS_REG_GET(MAC_MDIO_ADDRESS, GB, val); |
| } |
| |
| static int do_transaction(struct mdio_transaction *mdio) |
| { |
| enet_qos_t *base = mdio->base; |
| uint8_t goc_1_code; |
| int ret; |
| |
| k_mutex_lock(mdio->mdio_bus_mutex, K_FOREVER); |
| |
| if (mdio->op == MDIO_OP_C22_WRITE) { |
| base->MAC_MDIO_DATA = |
| /* Prepare the data to be written */ |
| ENET_QOS_REG_PREP(MAC_MDIO_DATA, GD, mdio->write_data); |
| goc_1_code = 0b0; |
| } else if (mdio->op == MDIO_OP_C22_READ) { |
| goc_1_code = 0b1; |
| } else { |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| base->MAC_MDIO_ADDRESS = |
| /* OP command */ |
| ENET_QOS_REG_PREP(MAC_MDIO_ADDRESS, GOC_1, goc_1_code) | |
| ENET_QOS_REG_PREP(MAC_MDIO_ADDRESS, GOC_0, 0b1) | |
| /* PHY address */ |
| ENET_QOS_REG_PREP(MAC_MDIO_ADDRESS, PA, mdio->portaddr) | |
| /* Register address */ |
| ENET_QOS_REG_PREP(MAC_MDIO_ADDRESS, RDA, mdio->regaddr); |
| |
| base->MAC_MDIO_ADDRESS = |
| /* Start the transaction */ |
| ENET_QOS_REG_PREP(MAC_MDIO_ADDRESS, GB, 0b1); |
| |
| |
| ret = -ETIMEDOUT; |
| for (int i = CONFIG_MDIO_NXP_ENET_QOS_RECHECK_COUNT; i > 0; i--) { |
| if (!check_busy(base)) { |
| ret = 0; |
| break; |
| } |
| k_busy_wait(CONFIG_MDIO_NXP_ENET_QOS_RECHECK_TIME); |
| } |
| |
| if (ret) { |
| LOG_ERR("MDIO transaction timed out"); |
| goto done; |
| } |
| |
| if (mdio->op == MDIO_OP_C22_READ) { |
| uint32_t val = mdio->base->MAC_MDIO_DATA; |
| |
| *mdio->read_data = |
| /* Decipher the read data */ |
| ENET_QOS_REG_GET(MAC_MDIO_DATA, GD, val); |
| } |
| |
| done: |
| k_mutex_unlock(mdio->mdio_bus_mutex); |
| |
| return ret; |
| } |
| |
| static int nxp_enet_qos_mdio_read(const struct device *dev, |
| uint8_t portaddr, uint8_t regaddr, |
| uint16_t *read_data) |
| { |
| const struct nxp_enet_qos_mdio_config *config = dev->config; |
| struct nxp_enet_qos_mdio_data *data = dev->data; |
| enet_qos_t *base = ENET_QOS_MODULE_CFG(config->enet_dev)->base; |
| struct mdio_transaction mdio_read = { |
| .op = MDIO_OP_C22_READ, |
| .read_data = read_data, |
| .portaddr = portaddr, |
| .regaddr = regaddr, |
| .base = base, |
| .mdio_bus_mutex = &data->mdio_mutex, |
| }; |
| |
| return do_transaction(&mdio_read); |
| } |
| |
| static int nxp_enet_qos_mdio_write(const struct device *dev, |
| uint8_t portaddr, uint8_t regaddr, |
| uint16_t write_data) |
| { |
| const struct nxp_enet_qos_mdio_config *config = dev->config; |
| struct nxp_enet_qos_mdio_data *data = dev->data; |
| enet_qos_t *base = ENET_QOS_MODULE_CFG(config->enet_dev)->base; |
| struct mdio_transaction mdio_write = { |
| .op = MDIO_OP_C22_WRITE, |
| .write_data = write_data, |
| .portaddr = portaddr, |
| .regaddr = regaddr, |
| .base = base, |
| .mdio_bus_mutex = &data->mdio_mutex, |
| }; |
| |
| return do_transaction(&mdio_write); |
| } |
| |
| static const struct mdio_driver_api nxp_enet_qos_mdio_api = { |
| .read = nxp_enet_qos_mdio_read, |
| .write = nxp_enet_qos_mdio_write, |
| }; |
| |
| static int nxp_enet_qos_mdio_init(const struct device *dev) |
| { |
| const struct nxp_enet_qos_mdio_config *mdio_config = dev->config; |
| struct nxp_enet_qos_mdio_data *data = dev->data; |
| const struct nxp_enet_qos_config *config = ENET_QOS_MODULE_CFG(mdio_config->enet_dev); |
| uint32_t enet_module_clk_rate; |
| int ret, divider; |
| |
| ret = k_mutex_init(&data->mdio_mutex); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = clock_control_get_rate(config->clock_dev, config->clock_subsys, |
| &enet_module_clk_rate); |
| if (ret) { |
| return ret; |
| } |
| |
| enet_module_clk_rate /= 1000000; |
| if (enet_module_clk_rate >= 20 && enet_module_clk_rate < 35) { |
| divider = 2; |
| } else if (enet_module_clk_rate < 60) { |
| divider = 3; |
| } else if (enet_module_clk_rate < 100) { |
| divider = 0; |
| } else if (enet_module_clk_rate < 150) { |
| divider = 1; |
| } else if (enet_module_clk_rate < 250) { |
| divider = 4; |
| } else { |
| LOG_ERR("ENET QOS clk rate does not allow MDIO"); |
| return -ENOTSUP; |
| } |
| |
| config->base->MAC_MDIO_ADDRESS = |
| /* Configure the MDIO clock range / divider */ |
| ENET_QOS_REG_PREP(MAC_MDIO_ADDRESS, CR, divider); |
| |
| return 0; |
| } |
| |
| #define NXP_ENET_QOS_MDIO_INIT(inst) \ |
| \ |
| static const struct nxp_enet_qos_mdio_config \ |
| nxp_enet_qos_mdio_cfg_##inst = { \ |
| .enet_dev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \ |
| }; \ |
| \ |
| static struct nxp_enet_qos_mdio_data nxp_enet_qos_mdio_data_##inst; \ |
| \ |
| DEVICE_DT_INST_DEFINE(inst, &nxp_enet_qos_mdio_init, NULL, \ |
| &nxp_enet_qos_mdio_data_##inst, \ |
| &nxp_enet_qos_mdio_cfg_##inst, \ |
| POST_KERNEL, CONFIG_MDIO_INIT_PRIORITY, \ |
| &nxp_enet_qos_mdio_api); \ |
| |
| DT_INST_FOREACH_STATUS_OKAY(NXP_ENET_QOS_MDIO_INIT) |