| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2018 MediaTek Inc. |
| * Author: Weijie Gao <weijie.gao@mediatek.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 "mt753x.h" |
| #include "mt753x_swconfig.h" |
| #include "mt753x_regs.h" |
| #include "mt753x_nl.h" |
| #include "mt7530.h" |
| #include "mt7531.h" |
| |
| static u32 mt753x_id; |
| struct list_head mt753x_devs; |
| static DEFINE_MUTEX(mt753x_devs_lock); |
| |
| static struct mt753x_sw_id *mt753x_sw_ids[] = { |
| &mt7530_id, |
| &mt7531_id, |
| &mt7988_id, |
| }; |
| |
| u32 mt753x_reg_read(struct gsw_mt753x *gsw, u32 reg) |
| { |
| u32 high, low; |
| |
| if (gsw->direct_access) |
| return __raw_readl(gsw->base + reg); |
| |
| mutex_lock(&gsw->host_bus->mdio_lock); |
| gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, |
| (reg & MT753X_REG_PAGE_ADDR_M) >> |
| MT753X_REG_PAGE_ADDR_S); |
| |
| low = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, |
| (reg & MT753X_REG_ADDR_M) >> |
| MT753X_REG_ADDR_S); |
| |
| high = gsw->host_bus->read(gsw->host_bus, gsw->smi_addr, 0x10); |
| |
| mutex_unlock(&gsw->host_bus->mdio_lock); |
| |
| return (high << 16) | (low & 0xffff); |
| } |
| |
| void mt753x_reg_write(struct gsw_mt753x *gsw, u32 reg, u32 val) |
| { |
| if (gsw->direct_access) { |
| __raw_writel(val, gsw->base + reg); |
| } else { |
| mutex_lock(&gsw->host_bus->mdio_lock); |
| gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x1f, |
| (reg & MT753X_REG_PAGE_ADDR_M) >> |
| MT753X_REG_PAGE_ADDR_S); |
| |
| gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, |
| (reg & MT753X_REG_ADDR_M) >> |
| MT753X_REG_ADDR_S, |
| val & 0xffff); |
| |
| gsw->host_bus->write(gsw->host_bus, gsw->smi_addr, 0x10, |
| val >> 16); |
| |
| mutex_unlock(&gsw->host_bus->mdio_lock); |
| } |
| } |
| |
| /* Indirect MDIO clause 22/45 access */ |
| static int mt753x_mii_rw(struct gsw_mt753x *gsw, int phy, int reg, u16 data, |
| u32 cmd, u32 st) |
| { |
| ktime_t timeout; |
| u32 val, timeout_us; |
| int ret = 0; |
| |
| timeout_us = 100000; |
| timeout = ktime_add_us(ktime_get(), timeout_us); |
| while (1) { |
| val = mt753x_reg_read(gsw, PHY_IAC); |
| |
| if ((val & PHY_ACS_ST) == 0) |
| break; |
| |
| if (ktime_compare(ktime_get(), timeout) > 0) |
| return -ETIMEDOUT; |
| } |
| |
| val = (st << MDIO_ST_S) | ((cmd << MDIO_CMD_S) & MDIO_CMD_M) | |
| ((phy << MDIO_PHY_ADDR_S) & MDIO_PHY_ADDR_M) | |
| ((reg << MDIO_REG_ADDR_S) & MDIO_REG_ADDR_M); |
| |
| if (cmd == MDIO_CMD_WRITE || cmd == MDIO_CMD_ADDR) |
| val |= data & MDIO_RW_DATA_M; |
| |
| mt753x_reg_write(gsw, PHY_IAC, val | PHY_ACS_ST); |
| |
| timeout_us = 100000; |
| timeout = ktime_add_us(ktime_get(), timeout_us); |
| while (1) { |
| val = mt753x_reg_read(gsw, PHY_IAC); |
| |
| if ((val & PHY_ACS_ST) == 0) |
| break; |
| |
| if (ktime_compare(ktime_get(), timeout) > 0) |
| return -ETIMEDOUT; |
| } |
| |
| if (cmd == MDIO_CMD_READ || cmd == MDIO_CMD_READ_C45) { |
| val = mt753x_reg_read(gsw, PHY_IAC); |
| ret = val & MDIO_RW_DATA_M; |
| } |
| |
| return ret; |
| } |
| |
| int mt753x_mii_read(struct gsw_mt753x *gsw, int phy, int reg) |
| { |
| int val; |
| |
| if (phy < MT753X_NUM_PHYS) |
| phy = (gsw->phy_base + phy) & MT753X_SMI_ADDR_MASK; |
| |
| mutex_lock(&gsw->mii_lock); |
| val = mt753x_mii_rw(gsw, phy, reg, 0, MDIO_CMD_READ, MDIO_ST_C22); |
| mutex_unlock(&gsw->mii_lock); |
| |
| return val; |
| } |
| |
| void mt753x_mii_write(struct gsw_mt753x *gsw, int phy, int reg, u16 val) |
| { |
| if (phy < MT753X_NUM_PHYS) |
| phy = (gsw->phy_base + phy) & MT753X_SMI_ADDR_MASK; |
| |
| mutex_lock(&gsw->mii_lock); |
| mt753x_mii_rw(gsw, phy, reg, val, MDIO_CMD_WRITE, MDIO_ST_C22); |
| mutex_unlock(&gsw->mii_lock); |
| } |
| |
| int mt753x_mmd_read(struct gsw_mt753x *gsw, int addr, int devad, u16 reg) |
| { |
| int val; |
| |
| if (addr < MT753X_NUM_PHYS) |
| addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK; |
| |
| mutex_lock(&gsw->mii_lock); |
| mt753x_mii_rw(gsw, addr, devad, reg, MDIO_CMD_ADDR, MDIO_ST_C45); |
| val = mt753x_mii_rw(gsw, addr, devad, 0, MDIO_CMD_READ_C45, |
| MDIO_ST_C45); |
| mutex_unlock(&gsw->mii_lock); |
| |
| return val; |
| } |
| |
| void mt753x_mmd_write(struct gsw_mt753x *gsw, int addr, int devad, u16 reg, |
| u16 val) |
| { |
| if (addr < MT753X_NUM_PHYS) |
| addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK; |
| |
| mutex_lock(&gsw->mii_lock); |
| mt753x_mii_rw(gsw, addr, devad, reg, MDIO_CMD_ADDR, MDIO_ST_C45); |
| mt753x_mii_rw(gsw, addr, devad, val, MDIO_CMD_WRITE, MDIO_ST_C45); |
| mutex_unlock(&gsw->mii_lock); |
| } |
| |
| int mt753x_mmd_ind_read(struct gsw_mt753x *gsw, int addr, int devad, u16 reg) |
| { |
| u16 val; |
| |
| if (addr < MT753X_NUM_PHYS) |
| addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK; |
| |
| mutex_lock(&gsw->mii_lock); |
| |
| mt753x_mii_rw(gsw, addr, MII_MMD_ACC_CTL_REG, |
| (MMD_ADDR << MMD_CMD_S) | |
| ((devad << MMD_DEVAD_S) & MMD_DEVAD_M), |
| MDIO_CMD_WRITE, MDIO_ST_C22); |
| |
| mt753x_mii_rw(gsw, addr, MII_MMD_ADDR_DATA_REG, reg, MDIO_CMD_WRITE, |
| MDIO_ST_C22); |
| |
| mt753x_mii_rw(gsw, addr, MII_MMD_ACC_CTL_REG, |
| (MMD_DATA << MMD_CMD_S) | |
| ((devad << MMD_DEVAD_S) & MMD_DEVAD_M), |
| MDIO_CMD_WRITE, MDIO_ST_C22); |
| |
| val = mt753x_mii_rw(gsw, addr, MII_MMD_ADDR_DATA_REG, 0, MDIO_CMD_READ, |
| MDIO_ST_C22); |
| |
| mutex_unlock(&gsw->mii_lock); |
| |
| return val; |
| } |
| |
| void mt753x_mmd_ind_write(struct gsw_mt753x *gsw, int addr, int devad, u16 reg, |
| u16 val) |
| { |
| if (addr < MT753X_NUM_PHYS) |
| addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK; |
| |
| mutex_lock(&gsw->mii_lock); |
| |
| mt753x_mii_rw(gsw, addr, MII_MMD_ACC_CTL_REG, |
| (MMD_ADDR << MMD_CMD_S) | |
| ((devad << MMD_DEVAD_S) & MMD_DEVAD_M), |
| MDIO_CMD_WRITE, MDIO_ST_C22); |
| |
| mt753x_mii_rw(gsw, addr, MII_MMD_ADDR_DATA_REG, reg, MDIO_CMD_WRITE, |
| MDIO_ST_C22); |
| |
| mt753x_mii_rw(gsw, addr, MII_MMD_ACC_CTL_REG, |
| (MMD_DATA << MMD_CMD_S) | |
| ((devad << MMD_DEVAD_S) & MMD_DEVAD_M), |
| MDIO_CMD_WRITE, MDIO_ST_C22); |
| |
| mt753x_mii_rw(gsw, addr, MII_MMD_ADDR_DATA_REG, val, MDIO_CMD_WRITE, |
| MDIO_ST_C22); |
| |
| mutex_unlock(&gsw->mii_lock); |
| } |
| |
| static inline int mt753x_get_duplex(const struct device_node *np) |
| { |
| return of_property_read_bool(np, "full-duplex"); |
| } |
| |
| static void mt753x_load_port_cfg(struct gsw_mt753x *gsw) |
| { |
| struct device_node *port_np; |
| struct device_node *fixed_link_node; |
| struct mt753x_port_cfg *port_cfg; |
| u32 port; |
| |
| for_each_child_of_node(gsw->dev->of_node, port_np) { |
| if (!of_device_is_compatible(port_np, "mediatek,mt753x-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; |
| case 6: |
| port_cfg = &gsw->port6_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 = mt753x_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->ssc_on = |
| of_property_read_bool(port_cfg->np, "mediatek,ssc-on"); |
| port_cfg->stag_on = |
| of_property_read_bool(port_cfg->np, "mediatek,stag-on"); |
| port_cfg->enabled = 1; |
| } |
| } |
| |
| void mt753x_tr_write(struct gsw_mt753x *gsw, int addr, u8 ch, u8 node, u8 daddr, |
| u32 data) |
| { |
| ktime_t timeout; |
| u32 timeout_us; |
| u32 val; |
| |
| if (addr < MT753X_NUM_PHYS) |
| addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK; |
| |
| gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, PHY_TR_PAGE); |
| |
| val = gsw->mii_read(gsw, addr, PHY_TR_CTRL); |
| |
| timeout_us = 100000; |
| timeout = ktime_add_us(ktime_get(), timeout_us); |
| while (1) { |
| val = gsw->mii_read(gsw, addr, PHY_TR_CTRL); |
| |
| if (!!(val & PHY_TR_PKT_XMT_STA)) |
| break; |
| |
| if (ktime_compare(ktime_get(), timeout) > 0) |
| goto out; |
| } |
| |
| gsw->mii_write(gsw, addr, PHY_TR_LOW_DATA, PHY_TR_LOW_VAL(data)); |
| gsw->mii_write(gsw, addr, PHY_TR_HIGH_DATA, PHY_TR_HIGH_VAL(data)); |
| val = PHY_TR_PKT_XMT_STA | (PHY_TR_WRITE << PHY_TR_WR_S) | |
| (ch << PHY_TR_CH_ADDR_S) | (node << PHY_TR_NODE_ADDR_S) | |
| (daddr << PHY_TR_DATA_ADDR_S); |
| gsw->mii_write(gsw, addr, PHY_TR_CTRL, val); |
| |
| timeout_us = 100000; |
| timeout = ktime_add_us(ktime_get(), timeout_us); |
| while (1) { |
| val = gsw->mii_read(gsw, addr, PHY_TR_CTRL); |
| |
| if (!!(val & PHY_TR_PKT_XMT_STA)) |
| break; |
| |
| if (ktime_compare(ktime_get(), timeout) > 0) |
| goto out; |
| } |
| out: |
| gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, 0); |
| } |
| |
| int mt753x_tr_read(struct gsw_mt753x *gsw, int addr, u8 ch, u8 node, u8 daddr) |
| { |
| ktime_t timeout; |
| u32 timeout_us; |
| u32 val; |
| u8 val_h; |
| |
| if (addr < MT753X_NUM_PHYS) |
| addr = (gsw->phy_base + addr) & MT753X_SMI_ADDR_MASK; |
| |
| gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, PHY_TR_PAGE); |
| |
| val = gsw->mii_read(gsw, addr, PHY_TR_CTRL); |
| |
| timeout_us = 100000; |
| timeout = ktime_add_us(ktime_get(), timeout_us); |
| while (1) { |
| val = gsw->mii_read(gsw, addr, PHY_TR_CTRL); |
| |
| if (!!(val & PHY_TR_PKT_XMT_STA)) |
| break; |
| |
| if (ktime_compare(ktime_get(), timeout) > 0) { |
| gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, 0); |
| return -ETIMEDOUT; |
| } |
| } |
| |
| val = PHY_TR_PKT_XMT_STA | (PHY_TR_READ << PHY_TR_WR_S) | |
| (ch << PHY_TR_CH_ADDR_S) | (node << PHY_TR_NODE_ADDR_S) | |
| (daddr << PHY_TR_DATA_ADDR_S); |
| gsw->mii_write(gsw, addr, PHY_TR_CTRL, val); |
| |
| timeout_us = 100000; |
| timeout = ktime_add_us(ktime_get(), timeout_us); |
| while (1) { |
| val = gsw->mii_read(gsw, addr, PHY_TR_CTRL); |
| |
| if (!!(val & PHY_TR_PKT_XMT_STA)) |
| break; |
| |
| if (ktime_compare(ktime_get(), timeout) > 0) { |
| gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, 0); |
| return -ETIMEDOUT; |
| } |
| } |
| |
| val = gsw->mii_read(gsw, addr, PHY_TR_LOW_DATA); |
| val_h = gsw->mii_read(gsw, addr, PHY_TR_HIGH_DATA); |
| val |= (val_h << 16); |
| |
| gsw->mii_write(gsw, addr, PHY_CL22_PAGE_CTRL, 0); |
| |
| return val; |
| } |
| |
| static void mt753x_add_gsw(struct gsw_mt753x *gsw) |
| { |
| mutex_lock(&mt753x_devs_lock); |
| gsw->id = mt753x_id++; |
| INIT_LIST_HEAD(&gsw->list); |
| list_add_tail(&gsw->list, &mt753x_devs); |
| mutex_unlock(&mt753x_devs_lock); |
| } |
| |
| static void mt753x_remove_gsw(struct gsw_mt753x *gsw) |
| { |
| mutex_lock(&mt753x_devs_lock); |
| list_del(&gsw->list); |
| mutex_unlock(&mt753x_devs_lock); |
| } |
| |
| struct gsw_mt753x *mt753x_get_gsw(u32 id) |
| { |
| struct gsw_mt753x *dev; |
| |
| mutex_lock(&mt753x_devs_lock); |
| |
| list_for_each_entry(dev, &mt753x_devs, list) { |
| if (dev->id == id) |
| return dev; |
| } |
| |
| mutex_unlock(&mt753x_devs_lock); |
| |
| return NULL; |
| } |
| |
| struct gsw_mt753x *mt753x_get_first_gsw(void) |
| { |
| struct gsw_mt753x *dev; |
| |
| mutex_lock(&mt753x_devs_lock); |
| |
| list_for_each_entry(dev, &mt753x_devs, list) |
| return dev; |
| |
| mutex_unlock(&mt753x_devs_lock); |
| |
| return NULL; |
| } |
| |
| void mt753x_put_gsw(void) |
| { |
| mutex_unlock(&mt753x_devs_lock); |
| } |
| |
| void mt753x_lock_gsw(void) |
| { |
| mutex_lock(&mt753x_devs_lock); |
| } |
| |
| static int mt753x_hw_reset(struct gsw_mt753x *gsw) |
| { |
| struct device_node *np = gsw->dev->of_node; |
| struct reset_control *rstc; |
| int mcm; |
| int ret; |
| |
| mcm = of_property_read_bool(np, "mediatek,mcm"); |
| if (mcm) { |
| rstc = devm_reset_control_get(gsw->dev, "mcm"); |
| ret = IS_ERR(rstc); |
| if (IS_ERR(rstc)) { |
| dev_err(gsw->dev, "Missing reset ctrl of switch\n"); |
| return ret; |
| } |
| |
| reset_control_assert(rstc); |
| msleep(30); |
| reset_control_deassert(rstc); |
| |
| gsw->reset_pin = -1; |
| return 0; |
| } |
| |
| 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, "mt753x-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); |
| msleep(30); |
| gpio_set_value(gsw->reset_pin, 1); |
| msleep(500); |
| |
| return 0; |
| } |
| |
| static int mt753x_mdio_read(struct mii_bus *bus, int addr, int reg) |
| { |
| struct gsw_mt753x *gsw = bus->priv; |
| |
| return gsw->mii_read(gsw, addr, reg); |
| } |
| |
| static int mt753x_mdio_write(struct mii_bus *bus, int addr, int reg, u16 val) |
| { |
| struct gsw_mt753x *gsw = bus->priv; |
| |
| gsw->mii_write(gsw, addr, reg, val); |
| |
| return 0; |
| } |
| |
| static const struct net_device_ops mt753x_dummy_netdev_ops = {}; |
| |
| static void mt753x_phy_link_handler(struct net_device *dev) |
| { |
| struct mt753x_phy *phy = container_of(dev, struct mt753x_phy, netdev); |
| struct phy_device *phydev = phy->phydev; |
| struct gsw_mt753x *gsw = phy->gsw; |
| u32 port = phy - gsw->phys; |
| |
| if (phydev->link) { |
| dev_info(gsw->dev, |
| "Port %d Link is Up - %s/%s - flow control %s\n", port, |
| phy_speed_to_str(phydev->speed), |
| (phydev->duplex == DUPLEX_FULL) ? "Full" : "Half", |
| phydev->pause ? "rx/tx" : "off"); |
| } else { |
| dev_info(gsw->dev, "Port %d Link is Down\n", port); |
| } |
| } |
| |
| static void mt753x_connect_internal_phys(struct gsw_mt753x *gsw, |
| struct device_node *mii_np) |
| { |
| struct device_node *phy_np; |
| struct mt753x_phy *phy; |
| phy_interface_t iface; |
| u32 phyad; |
| |
| if (!mii_np) |
| return; |
| |
| for_each_child_of_node(mii_np, phy_np) { |
| if (of_property_read_u32(phy_np, "reg", &phyad)) |
| continue; |
| |
| if (phyad >= MT753X_NUM_PHYS) |
| continue; |
| |
| iface = of_get_phy_mode(phy_np); |
| if (iface < 0) { |
| dev_info(gsw->dev, "incorrect phy-mode %d for PHY %d\n", |
| iface, phyad); |
| continue; |
| } |
| |
| phy = &gsw->phys[phyad]; |
| phy->gsw = gsw; |
| |
| init_dummy_netdev(&phy->netdev); |
| phy->netdev.netdev_ops = &mt753x_dummy_netdev_ops; |
| |
| phy->phydev = of_phy_connect(&phy->netdev, phy_np, |
| mt753x_phy_link_handler, 0, iface); |
| if (!phy->phydev) { |
| dev_info(gsw->dev, "could not connect to PHY %d\n", |
| phyad); |
| continue; |
| } |
| |
| phy_start(phy->phydev); |
| } |
| } |
| |
| static void mt753x_disconnect_internal_phys(struct gsw_mt753x *gsw) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(gsw->phys); i++) { |
| if (gsw->phys[i].phydev) { |
| phy_stop(gsw->phys[i].phydev); |
| phy_disconnect(gsw->phys[i].phydev); |
| gsw->phys[i].phydev = NULL; |
| } |
| } |
| } |
| |
| static int mt753x_mdio_register(struct gsw_mt753x *gsw) |
| { |
| struct device_node *mii_np; |
| int i, ret; |
| |
| mii_np = of_get_child_by_name(gsw->dev->of_node, "mdio-bus"); |
| if (mii_np && !of_device_is_available(mii_np)) { |
| ret = -ENODEV; |
| goto err_put_node; |
| } |
| |
| gsw->gphy_bus = devm_mdiobus_alloc(gsw->dev); |
| if (!gsw->gphy_bus) { |
| ret = -ENOMEM; |
| goto err_put_node; |
| } |
| |
| gsw->gphy_bus->name = "mt753x_mdio"; |
| gsw->gphy_bus->read = mt753x_mdio_read; |
| gsw->gphy_bus->write = mt753x_mdio_write; |
| gsw->gphy_bus->priv = gsw; |
| gsw->gphy_bus->parent = gsw->dev; |
| gsw->gphy_bus->phy_mask = BIT(MT753X_NUM_PHYS) - 1; |
| // gsw->gphy_bus->irq = gsw->phy_irqs; |
| |
| for (i = 0; i < PHY_MAX_ADDR; i++) |
| gsw->gphy_bus->irq[i] = PHY_POLL; |
| |
| if (mii_np) |
| snprintf(gsw->gphy_bus->id, MII_BUS_ID_SIZE, "%s@%s", |
| mii_np->name, gsw->dev->of_node->name); |
| else |
| snprintf(gsw->gphy_bus->id, MII_BUS_ID_SIZE, "mdio@%s", |
| gsw->dev->of_node->name); |
| |
| ret = of_mdiobus_register(gsw->gphy_bus, mii_np); |
| |
| if (ret) { |
| gsw->gphy_bus = NULL; |
| } else { |
| if (gsw->phy_status_poll) |
| mt753x_connect_internal_phys(gsw, mii_np); |
| } |
| |
| err_put_node: |
| if (mii_np) |
| of_node_put(mii_np); |
| |
| return ret; |
| } |
| |
| static irqreturn_t mt753x_irq_handler(int irq, void *dev) |
| { |
| struct gsw_mt753x *gsw = dev; |
| |
| disable_irq_nosync(gsw->irq); |
| |
| schedule_work(&gsw->irq_worker); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int mt753x_probe(struct platform_device *pdev) |
| { |
| struct gsw_mt753x *gsw; |
| struct mt753x_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 mt753x_mapping *map; |
| int i; |
| |
| mdio = of_parse_phandle(np, "mediatek,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_mt753x), GFP_KERNEL); |
| if (!gsw) |
| return -ENOMEM; |
| |
| gsw->host_bus = mdio_bus; |
| gsw->dev = &pdev->dev; |
| mutex_init(&gsw->mii_lock); |
| |
| /* Switch hard reset */ |
| if (mt753x_hw_reset(gsw)) { |
| dev_info(&pdev->dev, "reset switch fail.\n"); |
| goto fail; |
| } |
| |
| /* Fetch the SMI address dirst */ |
| if (of_property_read_u32(np, "mediatek,smi-addr", &gsw->smi_addr)) |
| gsw->smi_addr = MT753X_DFL_SMI_ADDR; |
| |
| /* Get LAN/WAN port mapping */ |
| map = mt753x_find_mapping(np); |
| if (map) { |
| mt753x_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 */ |
| mt753x_load_port_cfg(gsw); |
| |
| /* Check for valid switch and then initialize */ |
| for (i = 0; i < ARRAY_SIZE(mt753x_sw_ids); i++) { |
| if (!mt753x_sw_ids[i]->detect(gsw, &rev)) { |
| sw = mt753x_sw_ids[i]; |
| |
| gsw->name = rev.name; |
| gsw->model = sw->model; |
| |
| dev_info(gsw->dev, "Switch is MediaTek %s rev %d", |
| gsw->name, rev.rev); |
| |
| /* Initialize the switch */ |
| ret = sw->init(gsw); |
| if (ret) |
| goto fail; |
| |
| break; |
| } |
| } |
| |
| if (i >= ARRAY_SIZE(mt753x_sw_ids)) { |
| dev_err(gsw->dev, "No mt753x switch found\n"); |
| goto fail; |
| } |
| |
| gsw->irq = platform_get_irq(pdev, 0); |
| if (gsw->irq >= 0) { |
| ret = devm_request_irq(gsw->dev, gsw->irq, mt753x_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, mt753x_irq_worker); |
| } |
| |
| platform_set_drvdata(pdev, gsw); |
| |
| gsw->phy_status_poll = |
| of_property_read_bool(gsw->dev->of_node, "mediatek,phy-poll"); |
| |
| mt753x_add_gsw(gsw); |
| #if 1 //XDXD |
| mt753x_mdio_register(gsw); |
| #endif |
| |
| mt753x_nl_init(); |
| |
| mt753x_swconfig_init(gsw); |
| |
| if (sw->post_init) |
| sw->post_init(gsw); |
| |
| if (gsw->irq >= 0) |
| mt753x_irq_enable(gsw); |
| |
| return 0; |
| |
| fail: |
| devm_kfree(&pdev->dev, gsw); |
| |
| return ret; |
| } |
| |
| static int mt753x_remove(struct platform_device *pdev) |
| { |
| struct gsw_mt753x *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 |
| mt753x_swconfig_destroy(gsw); |
| #endif |
| |
| #if 1 //XDXD |
| mt753x_disconnect_internal_phys(gsw); |
| |
| mdiobus_unregister(gsw->gphy_bus); |
| #endif |
| |
| mt753x_nl_exit(); |
| |
| mt753x_remove_gsw(gsw); |
| |
| platform_set_drvdata(pdev, NULL); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id mt753x_ids[] = { |
| { .compatible = "mediatek,mt753x" }, |
| {}, |
| }; |
| |
| MODULE_DEVICE_TABLE(of, mt753x_ids); |
| |
| static struct platform_driver mt753x_driver = { |
| .probe = mt753x_probe, |
| .remove = mt753x_remove, |
| .driver = { |
| .name = "mt753x", |
| .of_match_table = mt753x_ids, |
| }, |
| }; |
| |
| static int __init mt753x_init(void) |
| { |
| int ret; |
| |
| INIT_LIST_HEAD(&mt753x_devs); |
| ret = platform_driver_register(&mt753x_driver); |
| |
| return ret; |
| } |
| module_init(mt753x_init); |
| |
| static void __exit mt753x_exit(void) |
| { |
| platform_driver_unregister(&mt753x_driver); |
| } |
| module_exit(mt753x_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Weijie Gao <weijie.gao@mediatek.com>"); |
| MODULE_DESCRIPTION("Driver for MediaTek MT753x Gigabit Switch"); |