blob: a6e9b7f951d481c28b44428ce29cd04b4bd64680 [file] [log] [blame]
/*
* Copyright 2023 NXP
* Copyright 2023 CogniPilot Foundation
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_tja11xx
#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_tja11xx, CONFIG_PHY_LOG_LEVEL);
/* Extended control register */
#define TJA11XX_EXTENDED_CONTROL 0x0017U
/* Configuration register 1 */
#define TJA11XX_CONFIGURATION_1 0x0018U
struct phy_tja11xx_config {
const struct device *mdio;
uint8_t phy_addr;
};
struct phy_tja11xx_data {
const struct device *dev;
struct phy_link_state state;
struct k_sem sem;
phy_callback_t cb;
void *cb_data;
struct k_work_delayable monitor_work;
};
static inline int phy_tja11xx_c22_read(const struct device *dev, uint16_t reg, uint16_t *val)
{
const struct phy_tja11xx_config *const cfg = dev->config;
return mdio_read(cfg->mdio, cfg->phy_addr, reg, val);
}
static inline int phy_tja11xx_c22_write(const struct device *dev, uint16_t reg, uint16_t val)
{
const struct phy_tja11xx_config *const cfg = dev->config;
return mdio_write(cfg->mdio, cfg->phy_addr, reg, val);
}
static int phy_tja11xx_reg_read(const struct device *dev, uint16_t reg_addr, uint32_t *data)
{
const struct phy_tja11xx_config *cfg = dev->config;
int ret;
mdio_bus_enable(cfg->mdio);
ret = phy_tja11xx_c22_read(dev, reg_addr, (uint16_t *)data);
mdio_bus_disable(cfg->mdio);
return ret;
}
static int phy_tja11xx_reg_write(const struct device *dev, uint16_t reg_addr, uint32_t data)
{
const struct phy_tja11xx_config *cfg = dev->config;
int ret;
mdio_bus_enable(cfg->mdio);
ret = phy_tja11xx_c22_write(dev, reg_addr, (uint16_t)data);
mdio_bus_disable(cfg->mdio);
return ret;
}
static int update_link_state(const struct device *dev)
{
struct phy_tja11xx_data *const data = dev->data;
bool link_up;
uint16_t val;
if (phy_tja11xx_c22_read(dev, MII_BMSR, &val) < 0) {
return -EIO;
}
link_up = (val & MII_BMSR_LINK_STATUS) != 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_tja11xx_get_link_state(const struct device *dev, struct phy_link_state *state)
{
struct phy_tja11xx_data *const data = dev->data;
int rc = 0;
k_sem_take(&data->sem, K_FOREVER);
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_tja11xx_data *const data = dev->data;
struct phy_link_state state;
if (data->cb == NULL) {
return;
}
/* Send callback only on link state change */
if (phy_tja11xx_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_tja11xx_data *const data =
CONTAINER_OF(dwork, struct phy_tja11xx_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_tja11xx_cfg_irq_poll(const struct device *dev)
{
struct phy_tja11xx_data *const data = dev->data;
k_work_init_delayable(&data->monitor_work, monitor_work_handler);
monitor_work_handler(&data->monitor_work.work);
}
static int phy_tja11xx_init(const struct device *dev)
{
struct phy_tja11xx_data *const data = dev->data;
int ret;
data->dev = dev;
data->cb = NULL;
data->state.is_up = false;
data->state.speed = LINK_FULL_100BASE;
ret = phy_tja11xx_reg_write(dev, TJA11XX_EXTENDED_CONTROL, 0x1804);
if (ret < 0) {
return ret;
}
ret = phy_tja11xx_reg_write(dev, MII_BMCR, 0x2100);
if (ret < 0) {
return ret;
}
ret = phy_tja11xx_reg_write(dev, TJA11XX_CONFIGURATION_1, 0x8A00);
if (ret < 0) {
return ret;
}
ret = phy_tja11xx_reg_write(dev, TJA11XX_EXTENDED_CONTROL, 0x9804);
if (ret < 0) {
return ret;
}
phy_tja11xx_cfg_irq_poll(dev);
return ret;
}
static int phy_tja11xx_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data)
{
struct phy_tja11xx_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 DEVICE_API(ethphy, phy_tja11xx_api) = {
.get_link = phy_tja11xx_get_link_state,
.link_cb_set = phy_tja11xx_link_cb_set,
.read = phy_tja11xx_reg_read,
.write = phy_tja11xx_reg_write,
};
#define TJA11xx_INITIALIZE(n) \
static const struct phy_tja11xx_config phy_tja11xx_config_##n = { \
.phy_addr = DT_INST_REG_ADDR(n), \
.mdio = DEVICE_DT_GET(DT_INST_BUS(n)) \
}; \
static struct phy_tja11xx_data phy_tja11xx_data_##n = { \
.sem = Z_SEM_INITIALIZER(phy_tja11xx_data_##n.sem, 1, 1), \
}; \
DEVICE_DT_INST_DEFINE(n, &phy_tja11xx_init, NULL, &phy_tja11xx_data_##n, \
&phy_tja11xx_config_##n, POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, \
&phy_tja11xx_api);
DT_INST_FOREACH_STATUS_OKAY(TJA11xx_INITIALIZE)