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