Primary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

197 lines
4.9 KiB

/*
* Copyright (c) 2022 Grant Ramsay <grant.ramsay@hotmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT espressif_esp32_mdio
#include <soc.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/mdio.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <esp_mac.h>
#include <hal/emac_hal.h>
#include <hal/emac_ll.h>
#include <soc/rtc.h>
#include <clk_ctrl_os.h>
LOG_MODULE_REGISTER(mdio_esp32, CONFIG_MDIO_LOG_LEVEL);
#define PHY_OPERATION_TIMEOUT_US 1000
struct mdio_esp32_dev_data {
struct k_sem sem;
emac_hal_context_t hal;
};
struct mdio_esp32_dev_config {
const struct pinctrl_dev_config *pcfg;
};
static int mdio_transfer(const struct device *dev, uint8_t prtad, uint8_t regad,
bool write, uint16_t data_in, uint16_t *data_out)
{
struct mdio_esp32_dev_data *const dev_data = dev->data;
k_sem_take(&dev_data->sem, K_FOREVER);
if (emac_ll_is_mii_busy(dev_data->hal.mac_regs)) {
LOG_ERR("phy busy");
k_sem_give(&dev_data->sem);
return -EBUSY;
}
if (write) {
emac_ll_set_phy_data(dev_data->hal.mac_regs, data_in);
}
emac_hal_set_phy_cmd(&dev_data->hal, prtad, regad, write);
/* Poll until operation complete */
bool success = false;
for (uint32_t t_us = 0; t_us < PHY_OPERATION_TIMEOUT_US; t_us += 100) {
k_sleep(K_USEC(100));
if (!emac_ll_is_mii_busy(dev_data->hal.mac_regs)) {
success = true;
break;
}
}
if (!success) {
LOG_ERR("phy timeout");
k_sem_give(&dev_data->sem);
return -ETIMEDOUT;
}
if (!write && data_out != NULL) {
*data_out = emac_ll_get_phy_data(dev_data->hal.mac_regs);
}
k_sem_give(&dev_data->sem);
return 0;
}
static int mdio_esp32_read(const struct device *dev, uint8_t prtad, uint8_t regad,
uint16_t *data)
{
return mdio_transfer(dev, prtad, regad, false, 0, data);
}
static int mdio_esp32_write(const struct device *dev, uint8_t prtad,
uint8_t regad, uint16_t data)
{
return mdio_transfer(dev, prtad, regad, true, data, NULL);
}
#if DT_INST_NODE_HAS_PROP(0, ref_clk_output_gpios)
static int emac_config_apll_clock(void)
{
uint32_t expt_freq = MHZ(50);
uint32_t real_freq = 0;
esp_err_t ret = periph_rtc_apll_freq_set(expt_freq, &real_freq);
if (ret == ESP_ERR_INVALID_ARG) {
LOG_ERR("Set APLL clock coefficients failed");
return -EIO;
}
if (ret == ESP_ERR_INVALID_STATE) {
LOG_INF("APLL is occupied already, it is working at %d Hz", real_freq);
}
/* If the difference of real APLL frequency
* is not within 50 ppm, i.e. 2500 Hz,
* the APLL is unavailable
*/
if (abs((int)real_freq - (int)expt_freq) > 2500) {
LOG_ERR("The APLL is working at an unusable frequency");
return -EIO;
}
return 0;
}
#endif
static int mdio_esp32_initialize(const struct device *dev)
{
const struct mdio_esp32_dev_config *const cfg = dev->config;
struct mdio_esp32_dev_data *const dev_data = dev->data;
int res;
k_sem_init(&dev_data->sem, 1, 1);
res = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
if (res != 0) {
goto err;
}
const struct device *clock_dev =
DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_NODELABEL(mdio)));
clock_control_subsys_t clock_subsys =
(clock_control_subsys_t)DT_CLOCKS_CELL(DT_NODELABEL(mdio), offset);
/* clock is shared, so do not bail out if already enabled */
res = clock_control_on(clock_dev, clock_subsys);
if (res < 0 && res != -EALREADY) {
goto err;
}
/* Only the mac registers are required for MDIO */
dev_data->hal.mac_regs = &EMAC_MAC;
#if DT_INST_NODE_HAS_PROP(0, ref_clk_output_gpios)
emac_hal_init(&dev_data->hal, NULL, NULL, NULL);
emac_hal_iomux_init_rmii();
BUILD_ASSERT(DT_INST_GPIO_PIN(0, ref_clk_output_gpios) == 0 ||
DT_INST_GPIO_PIN(0, ref_clk_output_gpios) == 16 ||
DT_INST_GPIO_PIN(0, ref_clk_output_gpios) == 17,
"Only GPIO0/16/17 are allowed as a GPIO REF_CLK source!");
int ref_clk_gpio = DT_INST_GPIO_PIN(0, ref_clk_output_gpios);
emac_hal_iomux_rmii_clk_output(ref_clk_gpio);
emac_ll_clock_enable_rmii_output(dev_data->hal.ext_regs);
periph_rtc_apll_acquire();
res = emac_config_apll_clock();
if (res != 0) {
goto err;
}
rtc_clk_apll_enable(true);
#endif
/* Init MDIO clock */
emac_hal_set_csr_clock_range(&dev_data->hal, esp_clk_apb_freq());
return 0;
err:
return res;
}
static DEVICE_API(mdio, mdio_esp32_driver_api) = {
.read = mdio_esp32_read,
.write = mdio_esp32_write,
};
#define MDIO_ESP32_CONFIG(n) \
static const struct mdio_esp32_dev_config mdio_esp32_dev_config_##n = { \
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
};
#define MDIO_ESP32_DEVICE(n) \
PINCTRL_DT_INST_DEFINE(n); \
MDIO_ESP32_CONFIG(n); \
static struct mdio_esp32_dev_data mdio_esp32_dev_data##n; \
DEVICE_DT_INST_DEFINE(n, \
&mdio_esp32_initialize, \
NULL, \
&mdio_esp32_dev_data##n, \
&mdio_esp32_dev_config_##n, POST_KERNEL, \
CONFIG_MDIO_INIT_PRIORITY, \
&mdio_esp32_driver_api);
DT_INST_FOREACH_STATUS_OKAY(MDIO_ESP32_DEVICE)