blob: 659fdbf80aa7e1dcc610502e0ed228684ffb10b8 [file] [log] [blame]
/*
* Copyright 2023 NXP
* Copyright 2023 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_tja1103
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/sys/util.h>
#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>
LOG_MODULE_REGISTER(phy_tja1103, CONFIG_PHY_LOG_LEVEL);
/* PHYs out of reset check retry delay */
#define TJA1103_AWAIT_DELAY_POLL_US 15000U
/* Number of retries for PHYs out of reset check */
#define TJA1103_AWAIT_RETRY_COUNT 200U
/* TJA1103 PHY identifier */
#define TJA1103_ID 0x1BB013
/* MMD30 - Device status register */
#define TJA1103_DEVICE_CONTROL (0x0040U)
#define TJA1103_DEVICE_CONTROL_GLOBAL_CFG_EN BIT(14)
#define TJA1103_DEVICE_CONTROL_SUPER_CFG_EN BIT(13)
/* Shared - PHY control register */
#define TJA1103_PHY_CONTROL (0x8100U)
#define TJA1103_PHY_CONTROL_CFG_EN BIT(14)
/* Shared - PHY status register */
#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)
{
const struct phy_tja1103_config *const cfg = dev->config;
return mdio_read(cfg->mdio, cfg->phy_addr, reg, val);
}
static inline int phy_tja1103_c22_write(const struct device *dev, uint16_t reg, uint16_t val)
{
const struct phy_tja1103_config *const cfg = dev->config;
return mdio_write(cfg->mdio, cfg->phy_addr, reg, val);
}
static inline int phy_tja1103_c45_write(const struct device *dev, uint16_t devad, uint16_t reg,
uint16_t val)
{
const struct phy_tja1103_config *cfg = dev->config;
return mdio_write_c45(cfg->mdio, cfg->phy_addr, devad, reg, val);
}
static inline int phy_tja1103_c45_read(const struct device *dev, uint16_t devad, uint16_t reg,
uint16_t *val)
{
const struct phy_tja1103_config *cfg = dev->config;
return mdio_read_c45(cfg->mdio, cfg->phy_addr, devad, reg, val);
}
static int phy_tja1103_reg_read(const struct device *dev, uint16_t reg_addr, uint32_t *data)
{
const struct phy_tja1103_config *cfg = dev->config;
int ret;
mdio_bus_enable(cfg->mdio);
ret = phy_tja1103_c22_read(dev, reg_addr, (uint16_t *)data);
mdio_bus_disable(cfg->mdio);
return ret;
}
static int phy_tja1103_reg_write(const struct device *dev, uint16_t reg_addr, uint32_t data)
{
const struct phy_tja1103_config *cfg = dev->config;
int ret;
mdio_bus_enable(cfg->mdio);
ret = phy_tja1103_c22_write(dev, reg_addr, (uint16_t)data);
mdio_bus_disable(cfg->mdio);
return ret;
}
static int phy_tja1103_id(const struct device *dev, uint32_t *phy_id)
{
uint16_t val;
if (phy_tja1103_c22_read(dev, MII_PHYID1R, &val) < 0) {
return -EIO;
}
*phy_id = (val & UINT16_MAX) << 16;
if (phy_tja1103_c22_read(dev, MII_PHYID2R, &val) < 0) {
return -EIO;
}
*phy_id |= (val & UINT16_MAX);
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;
const struct phy_tja1103_config *const cfg = dev->config;
int rc = 0;
k_sem_take(&data->sem, K_FOREVER);
/* 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 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)
{
ARG_UNUSED(dev);
if (adv_speeds & LINK_FULL_100BASE_T) {
return 0;
}
return -ENOTSUP;
}
static int phy_tja1103_init(const struct device *dev)
{
const struct phy_tja1103_config *const cfg = dev->config;
struct phy_tja1103_data *const data = dev->data;
uint32_t phy_id = 0;
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)));
if (ret < 0) {
LOG_ERR("Unable to obtain PHY ID for device 0x%x", cfg->phy_addr);
return -ENODEV;
}
/* enable config registers */
ret = phy_tja1103_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_DEVICE_CONTROL,
TJA1103_DEVICE_CONTROL_GLOBAL_CFG_EN |
TJA1103_DEVICE_CONTROL_SUPER_CFG_EN);
if (ret < 0) {
return ret;
}
ret = phy_tja1103_c45_write(dev, MDIO_MMD_VENDOR_SPECIFIC1, TJA1103_PHY_CONTROL,
TJA1103_PHY_CONTROL_CFG_EN);
if (ret < 0) {
return ret;
}
ret = phy_tja1103_c45_read(dev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL, &val);
if (ret < 0) {
return ret;
}
/* Change master/slave mode if need */
if (cfg->master_slave == 1) {
val |= MDIO_PMA_PMD_BT1_CTRL_CFG_MST;
} else if (cfg->master_slave == 2) {
val &= ~MDIO_PMA_PMD_BT1_CTRL_CFG_MST;
}
ret = phy_tja1103_c45_write(dev, MDIO_MMD_PMAPMD, MDIO_PMA_PMD_BT1_CTRL, val);
if (ret < 0) {
return ret;
}
/* 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);
return ret;
}
static int phy_tja1103_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data)
{
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 = {
.get_link = phy_tja1103_get_link_state,
.cfg_link = phy_tja1103_cfg_link,
.link_cb_set = phy_tja1103_link_cb_set,
.read = phy_tja1103_reg_read,
.write = phy_tja1103_reg_write,
};
#define TJA1103_INITIALIZE(n) \
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, \
&phy_tja1103_api);
DT_INST_FOREACH_STATUS_OKAY(TJA1103_INITIALIZE)