1 /*
2  * DM8806 Stand-alone Ethernet PHY with RMII
3  *
4  * Copyright (c) 2024 Robert Slawinski <robert.slawinski1@gmail.com>
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  */
8 
9 #define DT_DRV_COMPAT davicom_dm8806_phy
10 
11 #include <zephyr/logging/log.h>
12 LOG_MODULE_REGISTER(eth_dm8806_phy, CONFIG_ETHERNET_LOG_LEVEL);
13 
14 #include <stdio.h>
15 #include <sys/types.h>
16 #include <zephyr/kernel.h>
17 #include <zephyr/net/phy.h>
18 #include <zephyr/drivers/mdio.h>
19 #include <zephyr/drivers/gpio.h>
20 
21 #include "phy_dm8806_priv.h"
22 
23 struct phy_dm8806_config {
24 	const struct device *mdio;
25 	uint8_t phy_addr;
26 	uint8_t switch_addr;
27 	struct gpio_dt_spec gpio_rst;
28 	struct gpio_dt_spec gpio_int;
29 	bool mii;
30 };
31 
32 struct phy_dm8806_data {
33 	const struct device *dev;
34 	struct phy_link_state state;
35 	phy_callback_t link_speed_chenge_cb;
36 	void *cb_data;
37 	struct gpio_callback gpio_cb;
38 #ifdef CONFIG_PHY_DM8806_TRIGGER
39 	K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_PHY_DM8806_THREAD_STACK_SIZE);
40 	struct k_thread thread;
41 	struct k_sem gpio_sem;
42 #endif
43 };
44 
phy_dm8806_gpio_callback(const struct device * dev,struct gpio_callback * cb,uint32_t pins)45 static void phy_dm8806_gpio_callback(const struct device *dev, struct gpio_callback *cb,
46 				     uint32_t pins)
47 {
48 	ARG_UNUSED(pins);
49 	struct phy_dm8806_data *drv_data = CONTAINER_OF(cb, struct phy_dm8806_data, gpio_cb);
50 	const struct phy_dm8806_config *cfg = drv_data->dev->config;
51 
52 	gpio_pin_interrupt_configure_dt(&cfg->gpio_int, GPIO_INT_DISABLE);
53 	k_sem_give(&drv_data->gpio_sem);
54 }
55 
phy_dm8806_thread_cb(const struct device * dev,struct phy_link_state * state,void * cb_data)56 static void phy_dm8806_thread_cb(const struct device *dev, struct phy_link_state *state,
57 				 void *cb_data)
58 {
59 	uint16_t data;
60 	struct phy_dm8806_data *drv_data = dev->data;
61 	const struct phy_dm8806_config *cfg = dev->config;
62 
63 	if (drv_data->link_speed_chenge_cb != NULL) {
64 		drv_data->link_speed_chenge_cb(dev, state, cb_data);
65 	}
66 	/* Clear the interrupt flag, by writing "1" to LNKCHG bit of Interrupt Status
67 	 * Register (318h)
68 	 */
69 	mdio_read(cfg->mdio, INT_STAT_PHY_ADDR, INT_STAT_REG_ADDR, &data);
70 	data |= 0x1;
71 	mdio_write(cfg->mdio, INT_STAT_PHY_ADDR, INT_STAT_REG_ADDR, data);
72 	gpio_pin_interrupt_configure_dt(&cfg->gpio_int, GPIO_INT_EDGE_TO_ACTIVE);
73 }
74 
phy_dm8806_thread(void * p1,void * p2,void * p3)75 static void phy_dm8806_thread(void *p1, void *p2, void *p3)
76 {
77 	struct phy_dm8806_data *drv_data = p1;
78 	void *cb_data = p2;
79 	struct phy_link_state *state = p3;
80 
81 	while (1) {
82 		k_sem_take(&drv_data->gpio_sem, K_FOREVER);
83 		phy_dm8806_thread_cb(drv_data->dev, state, cb_data);
84 	}
85 }
86 
phy_dm8806_port_init(const struct device * dev)87 int phy_dm8806_port_init(const struct device *dev)
88 {
89 	int res;
90 	const struct phy_dm8806_config *cfg = dev->config;
91 
92 	res = gpio_pin_configure_dt(&cfg->gpio_rst, (GPIO_OUTPUT_INACTIVE | GPIO_PULL_UP));
93 	if (res != 0) {
94 		LOG_ERR("Failed to configure gpio reset pin for PHY DM886 as an output");
95 		return res;
96 	}
97 	/* Hardware reset of the PHY DM8806 */
98 	gpio_pin_set_dt(&cfg->gpio_rst, true);
99 	if (res != 0) {
100 		LOG_ERR("Failed to assert gpio reset pin of the PHY DM886 to physical 0");
101 		return res;
102 	}
103 	/* According to DM8806 datasheet (DM8806-DAVICOM.pdf), low active state on
104 	 * the reset pin must remain minimum 10ms to perform hardware reset.
105 	 */
106 	k_msleep(10);
107 	res = gpio_pin_set_dt(&cfg->gpio_rst, false);
108 	if (res != 0) {
109 		LOG_ERR("Failed to assert gpio reset pin of the PHY DM886 to physical 1");
110 		return res;
111 	}
112 
113 	return res;
114 }
115 
phy_dm8806_init_interrupt(const struct device * dev)116 int phy_dm8806_init_interrupt(const struct device *dev)
117 {
118 	int res = 0;
119 	uint16_t data;
120 	struct phy_dm8806_data *drv_data = dev->data;
121 	void *cb_data = drv_data->cb_data;
122 	const struct phy_dm8806_config *cfg = dev->config;
123 
124 	/* Configure Davicom PHY DM8806 interrupts:
125 	 * Activate global interrupt by writing "1" to LNKCHG of Interrupt Mask
126 	 * And Control Register (319h)
127 	 */
128 	res = mdio_read(cfg->mdio, INT_MASK_CTRL_PHY_ADDR, INT_MASK_CTRL_REG_ADDR, &data);
129 	if (res) {
130 		LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", res);
131 		return res;
132 	}
133 	data |= 0x1;
134 	res = mdio_write(cfg->mdio, INT_MASK_CTRL_PHY_ADDR, INT_MASK_CTRL_REG_ADDR, data);
135 	if (res) {
136 		LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", res);
137 		return res;
138 	}
139 
140 	/* Activate interrupt per Ethernet port by writing "1" to LNK_EN0~3
141 	 * of WoL Control Register (2BBh)
142 	 */
143 	res = mdio_read(cfg->mdio, WOLL_CTRL_REG_PHY_ADDR, WOLL_CTRL_REG_REG_ADDR, &data);
144 	if (res) {
145 		LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", res);
146 		return res;
147 	}
148 	data |= 0xF;
149 	res = mdio_write(cfg->mdio, WOLL_CTRL_REG_PHY_ADDR, WOLL_CTRL_REG_REG_ADDR, data);
150 	if (res) {
151 		LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", res);
152 		return res;
153 	}
154 
155 	/* Configure external interrupts:
156 	 * Configure interrupt pin to recognize the rising edge on the Davicom
157 	 * PHY DM8806 as external interrupt
158 	 */
159 	if (device_is_ready(cfg->gpio_int.port) != true) {
160 		LOG_ERR("gpio_int gpio not ready");
161 		return -ENODEV;
162 	}
163 	drv_data->dev = dev;
164 	res = gpio_pin_configure_dt(&cfg->gpio_int, GPIO_INPUT);
165 	if (res != 0) {
166 		LOG_ERR("Failed to configure gpio interrupt pin for PHY DM886 as an input");
167 		return res;
168 	}
169 	/* Assign callback function to be fired by Davicom PHY DM8806 external
170 	 * interrupt pin
171 	 */
172 	gpio_init_callback(&drv_data->gpio_cb, phy_dm8806_gpio_callback, BIT(cfg->gpio_int.pin));
173 	res = gpio_add_callback(cfg->gpio_int.port, &drv_data->gpio_cb);
174 	if (res != 0) {
175 		LOG_ERR("Failed to set PHY DM886 gpio callback");
176 		return res;
177 	}
178 	k_sem_init(&drv_data->gpio_sem, 0, K_SEM_MAX_LIMIT);
179 	k_thread_create(&drv_data->thread, drv_data->thread_stack,
180 			CONFIG_PHY_DM8806_THREAD_STACK_SIZE, phy_dm8806_thread, drv_data, cb_data,
181 			NULL, K_PRIO_COOP(CONFIG_PHY_DM8806_THREAD_PRIORITY), 0, K_NO_WAIT);
182 	/* Configure GPIO interrupt to be triggered on pin state change to logical
183 	 * level 1 asserted by Davicom PHY DM8806 interrupt Pin
184 	 */
185 	gpio_pin_interrupt_configure_dt(&cfg->gpio_int, GPIO_INT_EDGE_TO_ACTIVE);
186 	if (res != 0) {
187 		LOG_ERR("Failed to configure PHY DM886 gpio interrupt pin trigger for "
188 			"active edge");
189 		return res;
190 	}
191 
192 	return 0;
193 }
194 
phy_dm8806_init(const struct device * dev)195 static int phy_dm8806_init(const struct device *dev)
196 {
197 	int ret;
198 	uint16_t val;
199 	const struct phy_dm8806_config *cfg = dev->config;
200 
201 	/* Configure reset pin for Davicom PHY DM8806 to be able to generate reset
202 	 * signal
203 	 */
204 	ret = phy_dm8806_port_init(dev);
205 	if (ret != 0) {
206 		LOG_ERR("Failed to reset PHY DM8806 ");
207 		return ret;
208 	}
209 
210 	ret = mdio_read(cfg->mdio, PHY_ADDRESS_18H, PORT5_MAC_CONTROL, &val);
211 	if (ret) {
212 		LOG_ERR("Failed to read PORT5_MAC_CONTROL: %i", ret);
213 		return ret;
214 	}
215 
216 	/* Activate default working mode*/
217 	val |= (P5_50M_INT_CLK_SOURCE | P5_50M_CLK_OUT_ENABLE | P5_EN_FORCE);
218 	val &= (P5_SPEED_100M | P5_FULL_DUPLEX | P5_FORCE_LINK_ON);
219 
220 	ret = mdio_write(cfg->mdio, PHY_ADDRESS_18H, PORT5_MAC_CONTROL, val);
221 	if (ret) {
222 		LOG_ERR("Failed to write PORT5_MAC_CONTROL, %i", ret);
223 		return ret;
224 	}
225 
226 	ret = mdio_read(cfg->mdio, PHY_ADDRESS_18H, IRQ_LED_CONTROL, &val);
227 	if (ret) {
228 		LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", ret);
229 		return ret;
230 	}
231 
232 	/* Activate LED blinking mode indicator mode 0. */
233 	val &= LED_MODE_0;
234 	ret = mdio_write(cfg->mdio, PHY_ADDRESS_18H, IRQ_LED_CONTROL, val);
235 	if (ret) {
236 		LOG_ERR("Failed to write IRQ_LED_CONTROL, %i", ret);
237 		return ret;
238 	}
239 
240 #ifdef CONFIG_PHY_DM8806_TRIGGER
241 	ret = phy_dm8806_init_interrupt(dev);
242 	if (ret != 0) {
243 		LOG_ERR("Failed to configure interrupt fot PHY DM8806");
244 		return ret;
245 	}
246 #endif
247 	return 0;
248 }
249 
phy_dm8806_get_link_state(const struct device * dev,struct phy_link_state * state)250 static int phy_dm8806_get_link_state(const struct device *dev, struct phy_link_state *state)
251 {
252 	int ret;
253 	uint16_t status;
254 	uint16_t data;
255 	const struct phy_dm8806_config *cfg = dev->config;
256 
257 #ifdef CONFIG_PHY_DM8806_TRIGGER
258 	ret = mdio_read(cfg->mdio, 0x18, 0x18, &data);
259 	if (ret) {
260 		LOG_ERR("Failed to read IRQ_LED_CONTROL, %i", ret);
261 		return ret;
262 	}
263 #endif
264 	/* Read data from Switch Per-Port Register. */
265 	ret = mdio_read(cfg->mdio, cfg->switch_addr, PORTX_SWITCH_STATUS, &data);
266 	if (ret) {
267 		LOG_ERR("Failes to read data drom DM8806 Switch Per-Port Registers area");
268 		return ret;
269 	}
270 	/* Extract speed and duplex status from Switch Per-Port Register: Per Port
271 	 * Status Data Register
272 	 */
273 	status = data;
274 	status >>= SPEED_AND_DUPLEX_OFFSET;
275 	switch (status & SPEED_AND_DUPLEX_MASK) {
276 	case SPEED_10MBPS_HALF_DUPLEX:
277 		state->speed = LINK_HALF_10BASE_T;
278 		break;
279 	case SPEED_10MBPS_FULL_DUPLEX:
280 		state->speed = LINK_FULL_10BASE_T;
281 		break;
282 	case SPEED_100MBPS_HALF_DUPLEX:
283 		state->speed = LINK_HALF_100BASE_T;
284 		break;
285 	case SPEED_100MBPS_FULL_DUPLEX:
286 		state->speed = LINK_FULL_100BASE_T;
287 		break;
288 	}
289 	/* Extract link status from Switch Per-Port Register: Per Port Status Data
290 	 * Register
291 	 */
292 	status = data;
293 	if (status & LINK_STATUS_MASK) {
294 		state->is_up = true;
295 	} else {
296 		state->is_up = false;
297 	}
298 	return ret;
299 }
300 
phy_dm8806_cfg_link(const struct device * dev,enum phy_link_speed adv_speeds)301 static int phy_dm8806_cfg_link(const struct device *dev, enum phy_link_speed adv_speeds)
302 {
303 	uint8_t ret;
304 	uint16_t data;
305 	uint16_t req_speed;
306 	const struct phy_dm8806_config *cfg = dev->config;
307 
308 	req_speed = adv_speeds;
309 	switch (req_speed) {
310 	case LINK_HALF_10BASE_T:
311 		req_speed = MODE_10_BASET_HALF_DUPLEX;
312 		break;
313 
314 	case LINK_FULL_10BASE_T:
315 		req_speed = MODE_10_BASET_FULL_DUPLEX;
316 		break;
317 
318 	case LINK_HALF_100BASE_T:
319 		req_speed = MODE_100_BASET_HALF_DUPLEX;
320 		break;
321 
322 	case LINK_FULL_100BASE_T:
323 		req_speed = MODE_100_BASET_FULL_DUPLEX;
324 		break;
325 	}
326 
327 	/* Power down */
328 	ret = mdio_read(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, &data);
329 	if (ret) {
330 		LOG_ERR("Failes to read data drom DM8806");
331 		return ret;
332 	}
333 	k_busy_wait(500);
334 	data |= POWER_DOWN;
335 	ret = mdio_write(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, data);
336 	if (ret) {
337 		LOG_ERR("Failed to write data to DM8806");
338 		return ret;
339 	}
340 	k_busy_wait(500);
341 
342 	/* Turn off the auto-negotiation process. */
343 	ret = mdio_read(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, &data);
344 	if (ret) {
345 		LOG_ERR("Failed to write data to DM8806");
346 		return ret;
347 	}
348 	k_busy_wait(500);
349 	data &= ~(AUTO_NEGOTIATION);
350 	ret = mdio_write(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, data);
351 	if (ret) {
352 		LOG_ERR("Failed to write data to DM8806");
353 		return ret;
354 	}
355 	k_busy_wait(500);
356 
357 	/* Change the link speed. */
358 	ret = mdio_read(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, &data);
359 	if (ret) {
360 		LOG_ERR("Failed to read data from DM8806");
361 		return ret;
362 	}
363 	k_busy_wait(500);
364 	data &= ~(LINK_SPEED | DUPLEX_MODE);
365 	data |= req_speed;
366 	ret = mdio_write(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, data);
367 	if (ret) {
368 		LOG_ERR("Failed to write data to DM8806");
369 		return ret;
370 	}
371 	k_busy_wait(500);
372 
373 	/* Power up ethernet port*/
374 	ret = mdio_read(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, &data);
375 	if (ret) {
376 		LOG_ERR("Failes to read data drom DM8806");
377 		return ret;
378 	}
379 	k_busy_wait(500);
380 	data &= ~(POWER_DOWN);
381 	ret = mdio_write(cfg->mdio, cfg->phy_addr, PORTX_PHY_CONTROL_REGISTER, data);
382 	if (ret) {
383 		LOG_ERR("Failed to write data to DM8806");
384 		return ret;
385 	}
386 	k_busy_wait(500);
387 	return -ENOTSUP;
388 }
389 
phy_dm8806_reg_read(const struct device * dev,uint16_t reg_addr,uint32_t * data)390 static int phy_dm8806_reg_read(const struct device *dev, uint16_t reg_addr, uint32_t *data)
391 {
392 	int res;
393 	const struct phy_dm8806_config *cfg = dev->config;
394 
395 	res = mdio_read(cfg->mdio, cfg->switch_addr, reg_addr, (uint16_t *)data);
396 	if (res) {
397 		LOG_ERR("Failed to read data from DM8806");
398 		return res;
399 	}
400 	return res;
401 }
402 
phy_dm8806_reg_write(const struct device * dev,uint16_t reg_addr,uint32_t data)403 static int phy_dm8806_reg_write(const struct device *dev, uint16_t reg_addr, uint32_t data)
404 {
405 	int res;
406 	const struct phy_dm8806_config *cfg = dev->config;
407 
408 	res = mdio_write(cfg->mdio, cfg->switch_addr, reg_addr, data);
409 	if (res) {
410 		LOG_ERR("Failed to write data to DM8806");
411 		return res;
412 	}
413 	return res;
414 }
415 
phy_dm8806_link_cb_set(const struct device * dev,phy_callback_t cb,void * user_data)416 static int phy_dm8806_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data)
417 {
418 	int res = 0;
419 	struct phy_dm8806_data *data = dev->data;
420 	const struct phy_dm8806_config *cfg = dev->config;
421 
422 	res = gpio_pin_interrupt_configure_dt(&cfg->gpio_int, GPIO_INT_DISABLE);
423 	if (res != 0) {
424 		LOG_WRN("Failed to disable DM8806 interrupt: %i", res);
425 		return res;
426 	}
427 	data->link_speed_chenge_cb = cb;
428 	data->cb_data = user_data;
429 	gpio_pin_interrupt_configure_dt(&cfg->gpio_int, GPIO_INT_EDGE_TO_ACTIVE);
430 	if (res != 0) {
431 		LOG_WRN("Failed to enable DM8806 interrupt: %i", res);
432 		return res;
433 	}
434 
435 	return res;
436 }
437 
438 static DEVICE_API(ethphy, phy_dm8806_api) = {
439 	.get_link = phy_dm8806_get_link_state,
440 	.cfg_link = phy_dm8806_cfg_link,
441 #ifdef CONFIG_PHY_DM8806_TRIGGER
442 	.link_cb_set = phy_dm8806_link_cb_set,
443 #endif
444 	.read = phy_dm8806_reg_read,
445 	.write = phy_dm8806_reg_write,
446 };
447 
448 #define DM8806_PHY_DEFINE_CONFIG(n)                                                                \
449 	static const struct phy_dm8806_config phy_dm8806_config_##n = {                            \
450 		.mdio = DEVICE_DT_GET(DT_INST_BUS(n)),                                             \
451 		.phy_addr = DT_INST_REG_ADDR(n),                                                   \
452 		.switch_addr = DT_PROP(DT_NODELABEL(dm8806_phy##n), reg_switch),                   \
453 		.gpio_int = GPIO_DT_SPEC_INST_GET(n, interrupt_gpio),                              \
454 		.gpio_rst = GPIO_DT_SPEC_INST_GET(n, reset_gpio),                                  \
455 	}
456 
457 #define DM8806_PHY_INITIALIZE(n)                                                                   \
458 	DM8806_PHY_DEFINE_CONFIG(n);                                                               \
459 	static struct phy_dm8806_data phy_dm8806_data_##n = {                                      \
460 		.gpio_sem = Z_SEM_INITIALIZER(phy_dm8806_data_##n.gpio_sem, 1, 1),                 \
461 	};                                                                                         \
462 	DEVICE_DT_INST_DEFINE(n, phy_dm8806_init, NULL, &phy_dm8806_data_##n,                      \
463 			      &phy_dm8806_config_##n, POST_KERNEL, CONFIG_PHY_INIT_PRIORITY,       \
464 			      &phy_dm8806_api);
465 
466 DT_INST_FOREACH_STATUS_OKAY(DM8806_PHY_INITIALIZE)
467