blob: 920240d76c3d049f9b18144f3be2a4194b99f37c [file] [log] [blame]
// SPDX-License-Identifian8855_gsw_ider: GPL-2.0
/*
* Copyright (c) 2023 Airoha Inc.
* Author: Min Yao <min.yao@airoha.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/reset.h>
#include <linux/hrtimer.h>
#include <linux/mii.h>
#include <linux/of_mdio.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_net.h>
#include <linux/of_irq.h>
#include <linux/phy.h>
#include "an8855.h"
#include "an8855_swconfig.h"
#include "an8855_regs.h"
#include "an8855_nl.h"
/* AN8855 driver version */
#define ARHT_AN8855_SWCFG_DRIVER_VER "1.0.1-L5.4"
static u32 an8855_gsw_id;
struct list_head an8855_devs;
static DEFINE_MUTEX(an8855_devs_lock);
static struct an8855_sw_id *an8855_sw_ids[] = {
&an8855_id,
};
u32 an8855_reg_read(struct gsw_an8855 *gsw, u32 reg)
{
u32 high, low;
mutex_lock(&gsw->host_bus->mdio_lock);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x4);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x15,
((reg >> 16) & 0xFFFF));
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x16,
(reg & 0xFFFF));
low = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, 0x18);
high = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, 0x17);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x0);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0);
mutex_unlock(&gsw->host_bus->mdio_lock);
return (high << 16) | (low & 0xffff);
}
void an8855_reg_write(struct gsw_an8855 *gsw, u32 reg, u32 val)
{
mutex_lock(&gsw->host_bus->mdio_lock);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x4);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x11,
((reg >> 16) & 0xFFFF));
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x12,
(reg & 0xFFFF));
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x13,
((val >> 16) & 0xFFFF));
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x14,
(val & 0xFFFF));
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, 0x0);
gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, 0x0);
mutex_unlock(&gsw->host_bus->mdio_lock);
}
int an8855_mii_read(struct gsw_an8855 *gsw, int phy, int reg)
{
int val;
if (phy < AN8855_NUM_PHYS)
phy = (gsw->phy_base + phy) & AN8855_SMI_ADDR_MASK;
mutex_lock(&gsw->host_bus->mdio_lock);
val = gsw->host_bus->read(gsw->host_bus, phy, reg);
mutex_unlock(&gsw->host_bus->mdio_lock);
return val;
}
void an8855_mii_write(struct gsw_an8855 *gsw, int phy, int reg, u16 val)
{
if (phy < AN8855_NUM_PHYS)
phy = (gsw->phy_base + phy) & AN8855_SMI_ADDR_MASK;
mutex_lock(&gsw->host_bus->mdio_lock);
gsw->host_bus->write(gsw->host_bus, phy, reg, val);
mutex_unlock(&gsw->host_bus->mdio_lock);
}
int an8855_mmd_read(struct gsw_an8855 *gsw, int addr, int devad, u16 reg)
{
int val;
u32 regnum = MII_ADDR_C45 | (devad << 16) | reg;
if (addr < AN8855_NUM_PHYS)
addr = (gsw->phy_base + addr) & AN8855_SMI_ADDR_MASK;
mutex_lock(&gsw->host_bus->mdio_lock);
val = gsw->host_bus->read(gsw->host_bus, addr, regnum);
mutex_unlock(&gsw->host_bus->mdio_lock);
return val;
}
void an8855_mmd_write(struct gsw_an8855 *gsw, int addr, int devad, u16 reg,
u16 val)
{
u32 regnum = MII_ADDR_C45 | (devad << 16) | reg;
if (addr < AN8855_NUM_PHYS)
addr = (gsw->phy_base + addr) & AN8855_SMI_ADDR_MASK;
mutex_lock(&gsw->host_bus->mdio_lock);
gsw->host_bus->write(gsw->host_bus, addr, regnum, val);
mutex_unlock(&gsw->host_bus->mdio_lock);
}
static inline int an8855_get_duplex(const struct device_node *np)
{
return of_property_read_bool(np, "full-duplex");
}
static void an8855_load_port_cfg(struct gsw_an8855 *gsw)
{
struct device_node *port_np;
struct device_node *fixed_link_node;
struct an8855_port_cfg *port_cfg;
u32 port;
for_each_child_of_node(gsw->dev->of_node, port_np) {
if (!of_device_is_compatible(port_np, "airoha,an8855-port"))
continue;
if (!of_device_is_available(port_np))
continue;
if (of_property_read_u32(port_np, "reg", &port))
continue;
switch (port) {
case 5:
port_cfg = &gsw->port5_cfg;
break;
default:
continue;
}
if (port_cfg->enabled) {
dev_info(gsw->dev, "duplicated node for port%d\n",
port_cfg->phy_mode);
continue;
}
port_cfg->np = port_np;
port_cfg->phy_mode = of_get_phy_mode(port_np);
if (port_cfg->phy_mode < 0) {
dev_info(gsw->dev, "incorrect phy-mode %d\n", port);
continue;
}
fixed_link_node = of_get_child_by_name(port_np, "fixed-link");
if (fixed_link_node) {
u32 speed;
port_cfg->force_link = 1;
port_cfg->duplex = an8855_get_duplex(fixed_link_node);
if (of_property_read_u32(fixed_link_node, "speed",
&speed)) {
speed = 0;
continue;
}
of_node_put(fixed_link_node);
switch (speed) {
case 10:
port_cfg->speed = MAC_SPD_10;
break;
case 100:
port_cfg->speed = MAC_SPD_100;
break;
case 1000:
port_cfg->speed = MAC_SPD_1000;
break;
case 2500:
port_cfg->speed = MAC_SPD_2500;
break;
default:
dev_info(gsw->dev, "incorrect speed %d\n",
speed);
continue;
}
}
port_cfg->stag_on =
of_property_read_bool(port_cfg->np, "airoha,stag-on");
port_cfg->enabled = 1;
}
}
static void an8855_add_gsw(struct gsw_an8855 *gsw)
{
mutex_lock(&an8855_devs_lock);
gsw->id = an8855_gsw_id++;
INIT_LIST_HEAD(&gsw->list);
list_add_tail(&gsw->list, &an8855_devs);
mutex_unlock(&an8855_devs_lock);
}
static void an8855_remove_gsw(struct gsw_an8855 *gsw)
{
mutex_lock(&an8855_devs_lock);
list_del(&gsw->list);
mutex_unlock(&an8855_devs_lock);
}
struct gsw_an8855 *an8855_get_gsw(u32 id)
{
struct gsw_an8855 *dev;
mutex_lock(&an8855_devs_lock);
list_for_each_entry(dev, &an8855_devs, list) {
if (dev->id == id)
return dev;
}
mutex_unlock(&an8855_devs_lock);
return NULL;
}
struct gsw_an8855 *an8855_get_first_gsw(void)
{
struct gsw_an8855 *dev;
mutex_lock(&an8855_devs_lock);
list_for_each_entry(dev, &an8855_devs, list)
return dev;
mutex_unlock(&an8855_devs_lock);
return NULL;
}
void an8855_put_gsw(void)
{
mutex_unlock(&an8855_devs_lock);
}
void an8855_lock_gsw(void)
{
mutex_lock(&an8855_devs_lock);
}
static int an8855_hw_reset(struct gsw_an8855 *gsw)
{
struct device_node *np = gsw->dev->of_node;
int ret;
gsw->reset_pin = of_get_named_gpio(np, "reset-gpios", 0);
if (gsw->reset_pin < 0) {
dev_info(gsw->dev, "No reset pin of switch\n");
return 0;
}
ret = devm_gpio_request(gsw->dev, gsw->reset_pin, "an8855-reset");
if (ret) {
dev_info(gsw->dev, "Failed to request gpio %d\n",
gsw->reset_pin);
return ret;
}
gpio_direction_output(gsw->reset_pin, 0);
usleep_range(100000, 150000);
gpio_set_value(gsw->reset_pin, 1);
usleep_range(100000, 150000);
return 0;
}
static irqreturn_t an8855_irq_handler(int irq, void *dev)
{
struct gsw_an8855 *gsw = dev;
disable_irq_nosync(gsw->irq);
schedule_work(&gsw->irq_worker);
return IRQ_HANDLED;
}
static int an8855_probe(struct platform_device *pdev)
{
struct gsw_an8855 *gsw;
struct an8855_sw_id *sw;
struct device_node *np = pdev->dev.of_node;
struct device_node *mdio;
struct mii_bus *mdio_bus;
int ret = -EINVAL;
struct chip_rev rev;
struct an8855_mapping *map;
int i;
mdio = of_parse_phandle(np, "airoha,mdio", 0);
if (!mdio)
return -EINVAL;
mdio_bus = of_mdio_find_bus(mdio);
if (!mdio_bus)
return -EPROBE_DEFER;
gsw = devm_kzalloc(&pdev->dev, sizeof(struct gsw_an8855), GFP_KERNEL);
if (!gsw)
return -ENOMEM;
gsw->host_bus = mdio_bus;
gsw->dev = &pdev->dev;
dev_info(gsw->dev, "AN8855 Driver Version=%s\n",
ARHT_AN8855_SWCFG_DRIVER_VER);
/* Switch hard reset */
if (an8855_hw_reset(gsw)) {
dev_info(&pdev->dev, "reset switch fail.\n");
goto fail;
}
/* Fetch the SMI address first */
gsw->smi_addr = AN8855_DFL_SMI_ADDR;
if (of_property_read_u32(np, "airoha,smi-addr", &gsw->new_smi_addr))
gsw->new_smi_addr = AN8855_DFL_SMI_ADDR;
/* Get LAN/WAN port mapping */
map = an8855_find_mapping(np);
if (map) {
an8855_apply_mapping(gsw, map);
gsw->global_vlan_enable = 1;
dev_info(gsw->dev, "LAN/WAN VLAN setting=%s\n", map->name);
}
/* Load MAC port configurations */
an8855_load_port_cfg(gsw);
/* Check for valid switch and then initialize */
an8855_gsw_id = 0;
for (i = 0; i < ARRAY_SIZE(an8855_sw_ids); i++) {
if (!an8855_sw_ids[i]->detect(gsw, &rev)) {
sw = an8855_sw_ids[i];
gsw->name = rev.name;
gsw->model = sw->model;
dev_info(gsw->dev, "Switch is Airoha %s rev %d",
gsw->name, rev.rev);
/* Initialize the switch */
ret = sw->init(gsw);
if (ret)
goto fail;
break;
}
}
if (i >= ARRAY_SIZE(an8855_sw_ids)) {
dev_err(gsw->dev, "No an8855 switch found\n");
goto fail;
}
gsw->irq = platform_get_irq(pdev, 0);
if (gsw->irq >= 0) {
ret = devm_request_irq(gsw->dev, gsw->irq, an8855_irq_handler,
0, dev_name(gsw->dev), gsw);
if (ret) {
dev_err(gsw->dev, "Failed to request irq %d\n",
gsw->irq);
goto fail;
}
INIT_WORK(&gsw->irq_worker, an8855_irq_worker);
}
platform_set_drvdata(pdev, gsw);
an8855_add_gsw(gsw);
an8855_gsw_nl_init();
an8855_swconfig_init(gsw);
if (sw->post_init)
sw->post_init(gsw);
if (gsw->irq >= 0)
an8855_irq_enable(gsw);
return 0;
fail:
devm_kfree(&pdev->dev, gsw);
return ret;
}
static int an8855_remove(struct platform_device *pdev)
{
struct gsw_an8855 *gsw = platform_get_drvdata(pdev);
if (gsw->irq >= 0)
cancel_work_sync(&gsw->irq_worker);
if (gsw->reset_pin >= 0)
devm_gpio_free(&pdev->dev, gsw->reset_pin);
#ifdef CONFIG_SWCONFIG
an8855_swconfig_destroy(gsw);
#endif
an8855_gsw_nl_exit();
an8855_remove_gsw(gsw);
platform_set_drvdata(pdev, NULL);
return 0;
}
static const struct of_device_id an8855_ids[] = {
{.compatible = "airoha,an8855"},
{},
};
MODULE_DEVICE_TABLE(of, an8855_ids);
static struct platform_driver an8855_driver = {
.probe = an8855_probe,
.remove = an8855_remove,
.driver = {
.name = "an8855",
.of_match_table = an8855_ids,
},
};
static int __init an8855_init(void)
{
int ret;
INIT_LIST_HEAD(&an8855_devs);
ret = platform_driver_register(&an8855_driver);
return ret;
}
module_init(an8855_init);
static void __exit an8855_exit(void)
{
platform_driver_unregister(&an8855_driver);
}
module_exit(an8855_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Min Yao <min.yao@airoha.com>");
MODULE_VERSION(ARHT_AN8855_SWCFG_DRIVER_VER);
MODULE_DESCRIPTION("Driver for Airoha AN8855 Gigabit Switch");