1 /*
2  * Copyright (c) 2022 Grant Ramsay <grant.ramsay@hotmail.com>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT espressif_esp32_mdio
8 
9 #include <soc.h>
10 #include <zephyr/drivers/clock_control.h>
11 #include <zephyr/drivers/mdio.h>
12 #include <zephyr/drivers/pinctrl.h>
13 #include <zephyr/kernel.h>
14 #include <zephyr/logging/log.h>
15 
16 #include <esp_mac.h>
17 #include <hal/emac_hal.h>
18 #include <hal/emac_ll.h>
19 
20 LOG_MODULE_REGISTER(mdio_esp32, CONFIG_MDIO_LOG_LEVEL);
21 
22 #define PHY_OPERATION_TIMEOUT_US 1000
23 
24 struct mdio_esp32_dev_data {
25 	struct k_sem sem;
26 	emac_hal_context_t hal;
27 };
28 
29 struct mdio_esp32_dev_config {
30 	const struct pinctrl_dev_config *pcfg;
31 };
32 
mdio_transfer(const struct device * dev,uint8_t prtad,uint8_t regad,bool write,uint16_t data_in,uint16_t * data_out)33 static int mdio_transfer(const struct device *dev, uint8_t prtad, uint8_t regad,
34 			 bool write, uint16_t data_in, uint16_t *data_out)
35 {
36 	struct mdio_esp32_dev_data *const dev_data = dev->data;
37 
38 	k_sem_take(&dev_data->sem, K_FOREVER);
39 
40 	if (emac_ll_is_mii_busy(dev_data->hal.mac_regs)) {
41 		LOG_ERR("phy busy");
42 		k_sem_give(&dev_data->sem);
43 		return -EBUSY;
44 	}
45 
46 	if (write) {
47 		emac_ll_set_phy_data(dev_data->hal.mac_regs, data_in);
48 	}
49 	emac_hal_set_phy_cmd(&dev_data->hal, prtad, regad, write);
50 
51 	/* Poll until operation complete */
52 	bool success = false;
53 
54 	for (uint32_t t_us = 0; t_us < PHY_OPERATION_TIMEOUT_US; t_us += 100) {
55 		k_sleep(K_USEC(100));
56 		if (!emac_ll_is_mii_busy(dev_data->hal.mac_regs)) {
57 			success = true;
58 			break;
59 		}
60 	}
61 	if (!success) {
62 		LOG_ERR("phy timeout");
63 		k_sem_give(&dev_data->sem);
64 		return -ETIMEDOUT;
65 	}
66 
67 	if (!write && data_out != NULL) {
68 		*data_out = emac_ll_get_phy_data(dev_data->hal.mac_regs);
69 	}
70 
71 	k_sem_give(&dev_data->sem);
72 
73 	return 0;
74 }
75 
mdio_esp32_read(const struct device * dev,uint8_t prtad,uint8_t regad,uint16_t * data)76 static int mdio_esp32_read(const struct device *dev, uint8_t prtad, uint8_t regad,
77 			 uint16_t *data)
78 {
79 	return mdio_transfer(dev, prtad, regad, false, 0, data);
80 
81 }
82 
mdio_esp32_write(const struct device * dev,uint8_t prtad,uint8_t regad,uint16_t data)83 static int mdio_esp32_write(const struct device *dev, uint8_t prtad,
84 			  uint8_t regad, uint16_t data)
85 {
86 	return mdio_transfer(dev, prtad, regad, true, data, NULL);
87 }
88 
mdio_esp32_bus_enable(const struct device * dev)89 static void mdio_esp32_bus_enable(const struct device *dev)
90 {
91 	ARG_UNUSED(dev);
92 }
93 
mdio_esp32_bus_disable(const struct device * dev)94 static void mdio_esp32_bus_disable(const struct device *dev)
95 {
96 	ARG_UNUSED(dev);
97 }
98 
mdio_esp32_initialize(const struct device * dev)99 static int mdio_esp32_initialize(const struct device *dev)
100 {
101 	const struct mdio_esp32_dev_config *const cfg = dev->config;
102 	struct mdio_esp32_dev_data *const dev_data = dev->data;
103 	int res;
104 
105 	k_sem_init(&dev_data->sem, 1, 1);
106 
107 	res = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
108 	if (res != 0) {
109 		goto err;
110 	}
111 
112 	const struct device *clock_dev =
113 		DEVICE_DT_GET(DT_CLOCKS_CTLR(DT_NODELABEL(mdio)));
114 	clock_control_subsys_t clock_subsys =
115 		(clock_control_subsys_t)DT_CLOCKS_CELL(DT_NODELABEL(mdio), offset);
116 
117 	res = clock_control_on(clock_dev, clock_subsys);
118 	if (res != 0) {
119 		goto err;
120 	}
121 
122 	/* Only the mac registers are required for MDIO */
123 	dev_data->hal.mac_regs = &EMAC_MAC;
124 
125 	/* Init MDIO clock */
126 	emac_hal_set_csr_clock_range(&dev_data->hal, esp_clk_apb_freq());
127 
128 	return 0;
129 
130 err:
131 	return res;
132 }
133 
134 static const struct mdio_driver_api mdio_esp32_driver_api = {
135 	.read = mdio_esp32_read,
136 	.write = mdio_esp32_write,
137 	.bus_enable = mdio_esp32_bus_enable,
138 	.bus_disable = mdio_esp32_bus_disable,
139 };
140 
141 #define MDIO_ESP32_CONFIG(n)						\
142 static const struct mdio_esp32_dev_config mdio_esp32_dev_config_##n = {	\
143 	.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),			\
144 };
145 
146 #define MDIO_ESP32_DEVICE(n)						\
147 	PINCTRL_DT_INST_DEFINE(n);					\
148 	MDIO_ESP32_CONFIG(n);						\
149 	static struct mdio_esp32_dev_data mdio_esp32_dev_data##n;	\
150 	DEVICE_DT_INST_DEFINE(n,					\
151 			      &mdio_esp32_initialize,			\
152 			      NULL,					\
153 			      &mdio_esp32_dev_data##n,			\
154 			      &mdio_esp32_dev_config_##n, POST_KERNEL,	\
155 			      CONFIG_MDIO_INIT_PRIORITY,		\
156 			      &mdio_esp32_driver_api);
157 
158 DT_INST_FOREACH_STATUS_OKAY(MDIO_ESP32_DEVICE)
159