drivers: eth: phy: tja1103: Handle link change
drivers: eth: phy: tja1103: Handle link change These changes enable - TJA1103 driver to gracefully handle Link connect or disconnect events between Ethernet PHY and its link partner and notify it to the upper network layers Signed-off-by: Sumit Batra <sumit.batra@nxp.com>
This commit is contained in:
parent
bbc1087e83
commit
286a3ce37f
@ -9,7 +9,7 @@
|
||||
&pinctrl {
|
||||
eirq0_default: eirq0_default {
|
||||
group1 {
|
||||
pinmux = <PTD15_EIRQ31>, <PTA18_EIRQ0>, <PTA25_EIRQ5>;
|
||||
pinmux = <PTD15_EIRQ31>, <PTA18_EIRQ0>, <PTA25_EIRQ5>, <PTD5_EIRQ13>;
|
||||
input-enable;
|
||||
};
|
||||
};
|
||||
|
||||
@ -443,6 +443,7 @@
|
||||
pinctrl-names = "default";
|
||||
phy-connection-type = "rmii";
|
||||
local-mac-address = [02 04 9f aa bb cc];
|
||||
phy-handle = <&phy>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
@ -455,6 +456,7 @@
|
||||
compatible = "nxp,tja1103";
|
||||
status = "okay";
|
||||
reg = <0x12>;
|
||||
int-gpios = <&gpiod_l 5 GPIO_ACTIVE_LOW>;
|
||||
master-slave = "slave";
|
||||
};
|
||||
};
|
||||
|
||||
@ -11,6 +11,7 @@ module-dep = LOG
|
||||
module-str = Log level for Ethernet PHY driver
|
||||
module-help = Sets log level for Ethernet PHY Device Drivers.
|
||||
source "subsys/net/Kconfig.template.log_config.net"
|
||||
source "drivers/ethernet/phy/Kconfig.tja1103"
|
||||
|
||||
config PHY_INIT_PRIORITY
|
||||
int "Ethernet PHY driver init priority"
|
||||
@ -38,14 +39,6 @@ config PHY_ADIN2111
|
||||
help
|
||||
Enable ADIN2111 PHY driver.
|
||||
|
||||
config PHY_TJA1103
|
||||
bool "TJA1103 PHY driver"
|
||||
default y
|
||||
depends on DT_HAS_NXP_TJA1103_ENABLED
|
||||
depends on MDIO
|
||||
help
|
||||
Enable TJA1103 PHY driver.
|
||||
|
||||
config PHY_MICROCHIP_KSZ8081
|
||||
bool "Microchip KSZ8081 Phy Driver"
|
||||
default y
|
||||
|
||||
30
drivers/ethernet/phy/Kconfig.tja1103
Normal file
30
drivers/ethernet/phy/Kconfig.tja1103
Normal file
@ -0,0 +1,30 @@
|
||||
# NXP PHY TJA1103 driver configuration options
|
||||
|
||||
# Copyright 2024 NXP
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
menuconfig PHY_TJA1103
|
||||
bool "TJA1103 PHY driver"
|
||||
default y
|
||||
depends on DT_HAS_NXP_TJA1103_ENABLED
|
||||
depends on MDIO
|
||||
help
|
||||
Enable TJA1103 PHY driver.
|
||||
|
||||
if PHY_TJA1103
|
||||
|
||||
config PHY_TJA1103_IRQ_THREAD_STACK_SIZE
|
||||
int "Stack size for a thread that processes TJA1103 IRQ"
|
||||
default 2048
|
||||
help
|
||||
Size of the stack used for internal thread which is ran to
|
||||
process raised INT IRQ.
|
||||
|
||||
config PHY_TJA1103_IRQ_THREAD_PRIO
|
||||
int "Priority for internal incoming packet handler"
|
||||
default 2
|
||||
help
|
||||
Priority level for internal thread which is ran for TJA1103
|
||||
INT IRQ processing.
|
||||
|
||||
endif # PHY_TJA1103
|
||||
@ -16,6 +16,7 @@
|
||||
#include <zephyr/net/phy.h>
|
||||
#include <zephyr/net/mii.h>
|
||||
#include <zephyr/net/mdio.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/mdio.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
@ -40,15 +41,39 @@ LOG_MODULE_REGISTER(phy_tja1103, CONFIG_PHY_LOG_LEVEL);
|
||||
#define TJA1103_PHY_STATUS (0x8102U)
|
||||
#define TJA1103_PHY_STATUS_LINK_STAT BIT(2)
|
||||
|
||||
/* Shared - PHY functional IRQ masked status register */
|
||||
#define TJA1103_PHY_FUNC_IRQ_MSTATUS (0x80A2)
|
||||
#define TJA1103_PHY_FUNC_IRQ_LINK_EVENT BIT(1)
|
||||
#define TJA1103_PHY_FUNC_IRQ_LINK_AVAIL BIT(2)
|
||||
/* Shared -PHY functional IRQ source & enable registers */
|
||||
#define TJA1103_PHY_FUNC_IRQ_ACK (0x80A0)
|
||||
#define TJA1103_PHY_FUNC_IRQ_EN (0x80A1)
|
||||
#define TJA1103_PHY_FUNC_IRQ_LINK_EVENT_EN BIT(1)
|
||||
#define TJA1103_PHY_FUNC_IRQ_LINK_AVAIL_EN BIT(2)
|
||||
/* Always accessible reg for NMIs */
|
||||
#define TJA1103_ALWAYS_ACCESSIBLE (0x801F)
|
||||
#define TJA1103_ALWAYS_ACCESSIBLE_FUSA_PASS_IRQ BIT(4)
|
||||
|
||||
struct phy_tja1103_config {
|
||||
const struct device *mdio;
|
||||
struct gpio_dt_spec gpio_interrupt;
|
||||
uint8_t phy_addr;
|
||||
uint8_t master_slave;
|
||||
};
|
||||
|
||||
struct phy_tja1103_data {
|
||||
const struct device *dev;
|
||||
struct phy_link_state state;
|
||||
struct k_sem sem;
|
||||
struct k_sem offload_sem;
|
||||
phy_callback_t cb;
|
||||
struct gpio_callback phy_tja1103_int_callback;
|
||||
void *cb_data;
|
||||
|
||||
K_KERNEL_STACK_MEMBER(irq_thread_stack, CONFIG_PHY_TJA1103_IRQ_THREAD_STACK_SIZE);
|
||||
struct k_thread irq_thread;
|
||||
|
||||
struct k_work_delayable monitor_work;
|
||||
};
|
||||
|
||||
static inline int phy_tja1103_c22_read(const struct device *dev, uint16_t reg, uint16_t *val)
|
||||
@ -128,23 +153,189 @@ static int phy_tja1103_id(const struct device *dev, uint32_t *phy_id)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int update_link_state(const struct device *dev)
|
||||
{
|
||||
struct phy_tja1103_data *const data = dev->data;
|
||||
bool link_up;
|
||||
uint16_t val;
|
||||
|
||||
if (phy_tja1103_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_PHY_STATUS, &val) < 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
link_up = (val & TJA1103_PHY_STATUS_LINK_STAT) != 0;
|
||||
|
||||
/* Let workqueue re-schedule and re-check if the
|
||||
* link status is unchanged this time
|
||||
*/
|
||||
if (data->state.is_up == link_up) {
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
data->state.is_up = link_up;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int phy_tja1103_get_link_state(const struct device *dev, struct phy_link_state *state)
|
||||
{
|
||||
struct phy_tja1103_data *const data = dev->data;
|
||||
uint16_t val;
|
||||
const struct phy_tja1103_config *const cfg = dev->config;
|
||||
int rc = 0;
|
||||
|
||||
k_sem_take(&data->sem, K_FOREVER);
|
||||
|
||||
if (phy_tja1103_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_PHY_STATUS, &val) >= 0) {
|
||||
|
||||
data->state.is_up = (val & TJA1103_PHY_STATUS_LINK_STAT) != 0;
|
||||
|
||||
memcpy(state, &data->state, sizeof(struct phy_link_state));
|
||||
/* If Interrupt is configured then the workqueue will not
|
||||
* update the link state periodically so do it explicitly
|
||||
*/
|
||||
if (cfg->gpio_interrupt.port != NULL) {
|
||||
rc = update_link_state(dev);
|
||||
}
|
||||
|
||||
memcpy(state, &data->state, sizeof(struct phy_link_state));
|
||||
|
||||
k_sem_give(&data->sem);
|
||||
|
||||
return 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void invoke_link_cb(const struct device *dev)
|
||||
{
|
||||
struct phy_tja1103_data *const data = dev->data;
|
||||
struct phy_link_state state;
|
||||
|
||||
if (data->cb == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Send callback only on link state change */
|
||||
if (phy_tja1103_get_link_state(dev, &state) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
data->cb(dev, &state, data->cb_data);
|
||||
}
|
||||
|
||||
static void monitor_work_handler(struct k_work *work)
|
||||
{
|
||||
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
||||
struct phy_tja1103_data *const data =
|
||||
CONTAINER_OF(dwork, struct phy_tja1103_data, monitor_work);
|
||||
const struct device *dev = data->dev;
|
||||
int rc;
|
||||
|
||||
k_sem_take(&data->sem, K_FOREVER);
|
||||
|
||||
rc = update_link_state(dev);
|
||||
|
||||
k_sem_give(&data->sem);
|
||||
|
||||
/* If link state has changed and a callback is set, invoke callback */
|
||||
if (rc == 0) {
|
||||
invoke_link_cb(dev);
|
||||
}
|
||||
|
||||
/* Submit delayed work */
|
||||
k_work_reschedule(&data->monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD));
|
||||
}
|
||||
|
||||
static void phy_tja1103_irq_offload_thread(void *p1, void *p2, void *p3)
|
||||
{
|
||||
ARG_UNUSED(p2);
|
||||
ARG_UNUSED(p3);
|
||||
|
||||
const struct device *dev = p1;
|
||||
struct phy_tja1103_data *const data = dev->data;
|
||||
uint16_t irq;
|
||||
|
||||
for (;;) {
|
||||
/* await trigger from ISR */
|
||||
k_sem_take(&data->offload_sem, K_FOREVER);
|
||||
|
||||
if (phy_tja1103_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1,
|
||||
TJA1103_PHY_FUNC_IRQ_MSTATUS, &irq) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Handling Link related Functional IRQs */
|
||||
if (irq & (TJA1103_PHY_FUNC_IRQ_LINK_EVENT | TJA1103_PHY_FUNC_IRQ_LINK_AVAIL)) {
|
||||
/* Send callback to MAC on link status changed */
|
||||
invoke_link_cb(dev);
|
||||
|
||||
/* Ack the assered link related interrupts */
|
||||
phy_tja1103_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1,
|
||||
TJA1103_PHY_FUNC_IRQ_ACK, irq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void phy_tja1103_handle_irq(const struct device *port, struct gpio_callback *cb,
|
||||
uint32_t pins)
|
||||
{
|
||||
ARG_UNUSED(pins);
|
||||
ARG_UNUSED(port);
|
||||
|
||||
struct phy_tja1103_data *const data =
|
||||
CONTAINER_OF(cb, struct phy_tja1103_data, phy_tja1103_int_callback);
|
||||
|
||||
/* Trigger BH before leaving the ISR */
|
||||
k_sem_give(&data->offload_sem);
|
||||
}
|
||||
|
||||
static void phy_tja1103_cfg_irq_poll(const struct device *dev)
|
||||
{
|
||||
struct phy_tja1103_data *const data = dev->data;
|
||||
const struct phy_tja1103_config *const cfg = dev->config;
|
||||
int ret;
|
||||
|
||||
if (cfg->gpio_interrupt.port != NULL) {
|
||||
if (!gpio_is_ready_dt(&cfg->gpio_interrupt)) {
|
||||
LOG_ERR("Interrupt GPIO device %s is not ready",
|
||||
cfg->gpio_interrupt.port->name);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&cfg->gpio_interrupt, GPIO_INPUT);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to configure interrupt GPIO, %d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_init_callback(&(data->phy_tja1103_int_callback), phy_tja1103_handle_irq,
|
||||
BIT(cfg->gpio_interrupt.pin));
|
||||
|
||||
/* Add callback structure to global syslist */
|
||||
ret = gpio_add_callback(cfg->gpio_interrupt.port, &data->phy_tja1103_int_callback);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to add INT callback, %d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = phy_tja1103_c45_write(
|
||||
dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_PHY_FUNC_IRQ_EN,
|
||||
(TJA1103_PHY_FUNC_IRQ_LINK_EVENT_EN | TJA1103_PHY_FUNC_IRQ_LINK_AVAIL_EN));
|
||||
if (ret < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ret = gpio_pin_interrupt_configure_dt(&cfg->gpio_interrupt, GPIO_INT_EDGE_FALLING);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to enable INT, %d", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* PHY initialized, IRQ configured, now initialize the BH handler */
|
||||
k_thread_create(&data->irq_thread, data->irq_thread_stack,
|
||||
CONFIG_PHY_TJA1103_IRQ_THREAD_STACK_SIZE,
|
||||
phy_tja1103_irq_offload_thread, (void *)dev, NULL, NULL,
|
||||
CONFIG_PHY_TJA1103_IRQ_THREAD_PRIO, K_ESSENTIAL, K_NO_WAIT);
|
||||
k_thread_name_set(&data->irq_thread, "phy_tja1103_irq_offload");
|
||||
|
||||
} else {
|
||||
k_work_init_delayable(&data->monitor_work, monitor_work_handler);
|
||||
|
||||
monitor_work_handler(&data->monitor_work.work);
|
||||
}
|
||||
}
|
||||
|
||||
static int phy_tja1103_cfg_link(const struct device *dev, enum phy_link_speed adv_speeds)
|
||||
@ -166,12 +357,14 @@ static int phy_tja1103_init(const struct device *dev)
|
||||
uint16_t val;
|
||||
int ret;
|
||||
|
||||
data->dev = dev;
|
||||
data->cb = NULL;
|
||||
data->state.is_up = false;
|
||||
data->state.speed = LINK_FULL_100BASE_T;
|
||||
|
||||
ret = WAIT_FOR(!phy_tja1103_id(dev, &phy_id) && phy_id == TJA1103_ID,
|
||||
TJA1103_AWAIT_RETRY_COUNT * TJA1103_AWAIT_DELAY_POLL_US,
|
||||
k_sleep(K_USEC(TJA1103_AWAIT_DELAY_POLL_US)));
|
||||
TJA1103_AWAIT_RETRY_COUNT * TJA1103_AWAIT_DELAY_POLL_US,
|
||||
k_sleep(K_USEC(TJA1103_AWAIT_DELAY_POLL_US)));
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Unable to obtain PHY ID for device 0x%x", cfg->phy_addr);
|
||||
return -ENODEV;
|
||||
@ -208,19 +401,38 @@ static int phy_tja1103_init(const struct device *dev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Wait for settings to go in affect before checking link */
|
||||
k_sleep(K_MSEC(400));
|
||||
/* Check always accesible register for handling NMIs */
|
||||
ret = phy_tja1103_c45_read(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_ALWAYS_ACCESSIBLE, &val);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Ack Fusa Pass Interrupt if Startup Self Test Passed successfully */
|
||||
if (val & TJA1103_ALWAYS_ACCESSIBLE_FUSA_PASS_IRQ) {
|
||||
ret = phy_tja1103_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1,
|
||||
TJA1103_ALWAYS_ACCESSIBLE,
|
||||
TJA1103_ALWAYS_ACCESSIBLE_FUSA_PASS_IRQ);
|
||||
}
|
||||
|
||||
/* Configure interrupt or poll mode for reporting link changes */
|
||||
phy_tja1103_cfg_irq_poll(dev);
|
||||
|
||||
phy_tja1103_get_link_state(dev, &data->state);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int phy_tja1103_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
ARG_UNUSED(cb);
|
||||
ARG_UNUSED(user_data);
|
||||
return -ENOTSUP;
|
||||
struct phy_tja1103_data *const data = dev->data;
|
||||
|
||||
data->cb = cb;
|
||||
data->cb_data = user_data;
|
||||
|
||||
/* Invoke the callback to notify the caller of the current
|
||||
* link status.
|
||||
*/
|
||||
invoke_link_cb(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ethphy_driver_api phy_tja1103_api = {
|
||||
@ -235,10 +447,12 @@ static const struct ethphy_driver_api phy_tja1103_api = {
|
||||
static const struct phy_tja1103_config phy_tja1103_config_##n = { \
|
||||
.phy_addr = DT_INST_REG_ADDR(n), \
|
||||
.mdio = DEVICE_DT_GET(DT_INST_BUS(n)), \
|
||||
.gpio_interrupt = GPIO_DT_SPEC_INST_GET_OR(n, int_gpios, {0}), \
|
||||
.master_slave = DT_INST_ENUM_IDX(n, master_slave), \
|
||||
}; \
|
||||
static struct phy_tja1103_data phy_tja1103_data_##n = { \
|
||||
.sem = Z_SEM_INITIALIZER(phy_tja1103_data_##n.sem, 1, 1), \
|
||||
.offload_sem = Z_SEM_INITIALIZER(phy_tja1103_data_##n.offload_sem, 0, 1), \
|
||||
}; \
|
||||
DEVICE_DT_INST_DEFINE(n, &phy_tja1103_init, NULL, &phy_tja1103_data_##n, \
|
||||
&phy_tja1103_config_##n, POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, \
|
||||
|
||||
@ -12,6 +12,11 @@ properties:
|
||||
required: true
|
||||
description: PHY address
|
||||
|
||||
int-gpios:
|
||||
type: phandle-array
|
||||
description:
|
||||
interrupt GPIO for PHY. Will be pulled high in its default state.
|
||||
|
||||
master-slave:
|
||||
type: string
|
||||
required: true
|
||||
|
||||
Loading…
Reference in New Issue
Block a user