X-Git-Url: http://git.openwrt.org/?a=blobdiff_plain;f=target%2Flinux%2Fgeneric%2Ffiles%2Fdrivers%2Fnet%2Fphy%2Far8327.c;h=925730a8393d081c0ca3f807e5bed486edc95de4;hb=7ccfa826eebfdc514e537ad25f75b76bb2554a60;hp=0971f80d50f2fd16b64cf9aa858f5352b00f2f34;hpb=bdc0750191660af3ecefbc52327978af1cd93407;p=openwrt%2Fopenwrt.git diff --git a/target/linux/generic/files/drivers/net/phy/ar8327.c b/target/linux/generic/files/drivers/net/phy/ar8327.c index 0971f80d50..925730a839 100644 --- a/target/linux/generic/files/drivers/net/phy/ar8327.c +++ b/target/linux/generic/files/drivers/net/phy/ar8327.c @@ -1,7 +1,7 @@ /* * ar8327.c: AR8216 switch driver * - * Copyright (C) 2009 Felix Fietkau + * Copyright (C) 2009 Felix Fietkau * Copyright (C) 2011-2012 Gabor Juhos * * This program is free software; you can redistribute it and/or @@ -25,6 +25,7 @@ #include #include #include +#include #include "ar8216.h" #include "ar8327.h" @@ -138,13 +139,10 @@ ar8327_phy_fixup(struct ar8xxx_priv *priv, int phy) break; case 2: - ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x3c); - ar8xxx_phy_mmd_write(priv, phy, 0x4007, 0x0); + ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x3c, 0x0); /* fallthrough */ case 4: - ar8xxx_phy_mmd_write(priv, phy, 0x3, 0x800d); - ar8xxx_phy_mmd_write(priv, phy, 0x4003, 0x803f); - + ar8xxx_phy_mmd_write(priv, phy, 0x3, 0x800d, 0x803f); ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x6860); ar8xxx_phy_dbg_write(priv, phy, 0x5, 0x2c46); ar8xxx_phy_dbg_write(priv, phy, 0x3c, 0x6000); @@ -225,9 +223,7 @@ ar8327_led_work_func(struct work_struct *work) aled = container_of(work, struct ar8327_led, led_work); - spin_lock(&aled->lock); pattern = aled->pattern; - spin_unlock(&aled->lock); ar8327_set_led_pattern(aled->sw_priv, aled->led_num, pattern); @@ -310,9 +306,7 @@ ar8327_led_enable_hw_mode_show(struct device *dev, struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); ssize_t ret = 0; - spin_lock(&aled->lock); - ret += sprintf(buf, "%d\n", aled->enable_hw_mode); - spin_unlock(&aled->lock); + ret += scnprintf(buf, PAGE_SIZE, "%d\n", aled->enable_hw_mode); return ret; } @@ -507,11 +501,19 @@ ar8327_hw_config_pdata(struct ar8xxx_priv *priv, data->port6_status = ar8327_get_port_init_status(&pdata->port6_cfg); t = ar8327_get_pad_cfg(pdata->pad0_cfg); - if (chip_is_ar8337(priv)) - t |= AR8337_PAD_MAC06_EXCHANGE_EN; - + if (chip_is_ar8337(priv) && !pdata->pad0_cfg->mac06_exchange_dis) + t |= AR8337_PAD_MAC06_EXCHANGE_EN; ar8xxx_write(priv, AR8327_REG_PAD0_MODE, t); + t = ar8327_get_pad_cfg(pdata->pad5_cfg); + if (chip_is_ar8337(priv)) { + /* + * Workaround: RGMII RX delay setting needs to be + * always specified for AR8337 to avoid port 5 + * RX hang on high traffic / flood conditions + */ + t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; + } ar8xxx_write(priv, AR8327_REG_PAD5_MODE, t); t = ar8327_get_pad_cfg(pdata->pad6_cfg); ar8xxx_write(priv, AR8327_REG_PAD6_MODE, t); @@ -676,6 +678,39 @@ ar8327_init_globals(struct ar8xxx_priv *priv) /* Disable EEE on all phy's due to stability issues */ for (i = 0; i < AR8XXX_NUM_PHYS; i++) data->eee[i] = false; + + if (chip_is_ar8337(priv)) { + /* Update HOL registers with values suggested by QCA switch team */ + for (i = 0; i < AR8327_NUM_PORTS; i++) { + if (i == AR8216_PORT_CPU || i == 5 || i == 6) { + t = 0x3 << AR8327_PORT_HOL_CTRL0_EG_PRI0_BUF_S; + t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI1_BUF_S; + t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI2_BUF_S; + t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI3_BUF_S; + t |= 0x6 << AR8327_PORT_HOL_CTRL0_EG_PRI4_BUF_S; + t |= 0x8 << AR8327_PORT_HOL_CTRL0_EG_PRI5_BUF_S; + t |= 0x1e << AR8327_PORT_HOL_CTRL0_EG_PORT_BUF_S; + } else { + t = 0x3 << AR8327_PORT_HOL_CTRL0_EG_PRI0_BUF_S; + t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI1_BUF_S; + t |= 0x6 << AR8327_PORT_HOL_CTRL0_EG_PRI2_BUF_S; + t |= 0x8 << AR8327_PORT_HOL_CTRL0_EG_PRI3_BUF_S; + t |= 0x19 << AR8327_PORT_HOL_CTRL0_EG_PORT_BUF_S; + } + ar8xxx_write(priv, AR8327_REG_PORT_HOL_CTRL0(i), t); + + t = 0x6 << AR8327_PORT_HOL_CTRL1_ING_BUF_S; + t |= AR8327_PORT_HOL_CTRL1_EG_PRI_BUF_EN; + t |= AR8327_PORT_HOL_CTRL1_EG_PORT_BUF_EN; + t |= AR8327_PORT_HOL_CTRL1_WRED_EN; + ar8xxx_rmw(priv, AR8327_REG_PORT_HOL_CTRL1(i), + AR8327_PORT_HOL_CTRL1_ING_BUF | + AR8327_PORT_HOL_CTRL1_EG_PRI_BUF_EN | + AR8327_PORT_HOL_CTRL1_EG_PORT_BUF_EN | + AR8327_PORT_HOL_CTRL1_WRED_EN, + t); + } + } } static void @@ -691,12 +726,20 @@ ar8327_init_port(struct ar8xxx_priv *priv, int port) else t = AR8216_PORT_STATUS_LINK_AUTO; - ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); + if (port != AR8216_PORT_CPU && port != 6) { + /*hw limitation:if configure mac when there is traffic, + port MAC may work abnormal. Need disable lan&wan mac at fisrt*/ + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), 0); + msleep(100); + t |= AR8216_PORT_STATUS_FLOW_CONTROL; + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); + } else { + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); + } + ar8xxx_write(priv, AR8327_REG_PORT_HEADER(port), 0); - t = 1 << AR8327_PORT_VLAN0_DEF_SVID_S; - t |= 1 << AR8327_PORT_VLAN0_DEF_CVID_S; - ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), t); + ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), 0); t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S; ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t); @@ -709,7 +752,43 @@ ar8327_init_port(struct ar8xxx_priv *priv, int port) static u32 ar8327_read_port_status(struct ar8xxx_priv *priv, int port) { - return ar8xxx_read(priv, AR8327_REG_PORT_STATUS(port)); + u32 t; + + t = ar8xxx_read(priv, AR8327_REG_PORT_STATUS(port)); + /* map the flow control autoneg result bits to the flow control bits + * used in forced mode to allow ar8216_read_port_link detect + * flow control properly if autoneg is used + */ + if (t & AR8216_PORT_STATUS_LINK_UP && + t & AR8216_PORT_STATUS_LINK_AUTO) { + t &= ~(AR8216_PORT_STATUS_TXFLOW | AR8216_PORT_STATUS_RXFLOW); + if (t & AR8327_PORT_STATUS_TXFLOW_AUTO) + t |= AR8216_PORT_STATUS_TXFLOW; + if (t & AR8327_PORT_STATUS_RXFLOW_AUTO) + t |= AR8216_PORT_STATUS_RXFLOW; + } + + return t; +} + +static u32 +ar8327_read_port_eee_status(struct ar8xxx_priv *priv, int port) +{ + int phy; + u16 t; + + if (port >= priv->dev.ports) + return 0; + + if (port == 0 || port == 6) + return 0; + + phy = port - 1; + + /* EEE Ability Auto-negotiation Result */ + t = ar8xxx_phy_mmd_read(priv, phy, 0x7, 0x8000); + + return mmd_eee_adv_to_ethtool_adv_t(t); } static int @@ -721,11 +800,69 @@ ar8327_atu_flush(struct ar8xxx_priv *priv) AR8327_ATU_FUNC_BUSY, 0); if (!ret) ar8xxx_write(priv, AR8327_REG_ATU_FUNC, - AR8327_ATU_FUNC_OP_FLUSH); + AR8327_ATU_FUNC_OP_FLUSH | + AR8327_ATU_FUNC_BUSY); return ret; } +static int +ar8327_atu_flush_port(struct ar8xxx_priv *priv, int port) +{ + u32 t; + int ret; + + ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_BUSY, 0); + if (!ret) { + t = (port << AR8327_ATU_PORT_NUM_S); + t |= AR8327_ATU_FUNC_OP_FLUSH_PORT; + t |= AR8327_ATU_FUNC_BUSY; + ar8xxx_write(priv, AR8327_REG_ATU_FUNC, t); + } + + return ret; +} + +static int +ar8327_get_port_igmp(struct ar8xxx_priv *priv, int port) +{ + u32 fwd_ctrl, frame_ack; + + fwd_ctrl = (BIT(port) << AR8327_FWD_CTRL1_IGMP_S); + frame_ack = ((AR8327_FRAME_ACK_CTRL_IGMP_MLD | + AR8327_FRAME_ACK_CTRL_IGMP_JOIN | + AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << + AR8327_FRAME_ACK_CTRL_S(port)); + + return (ar8xxx_read(priv, AR8327_REG_FWD_CTRL1) & + fwd_ctrl) == fwd_ctrl && + (ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL(port)) & + frame_ack) == frame_ack; +} + +static void +ar8327_set_port_igmp(struct ar8xxx_priv *priv, int port, int enable) +{ + int reg_frame_ack = AR8327_REG_FRAME_ACK_CTRL(port); + u32 val_frame_ack = (AR8327_FRAME_ACK_CTRL_IGMP_MLD | + AR8327_FRAME_ACK_CTRL_IGMP_JOIN | + AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << + AR8327_FRAME_ACK_CTRL_S(port); + + if (enable) { + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, + BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S, + BIT(port) << AR8327_FWD_CTRL1_IGMP_S); + ar8xxx_reg_set(priv, reg_frame_ack, val_frame_ack); + } else { + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, + BIT(port) << AR8327_FWD_CTRL1_IGMP_S, + BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S); + ar8xxx_reg_clear(priv, reg_frame_ack, val_frame_ack); + } +} + static void ar8327_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) { @@ -932,6 +1069,78 @@ ar8327_sw_get_eee(struct switch_dev *dev, return 0; } +static void +ar8327_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) +{ + int timeout = 20; + + while (ar8xxx_mii_read32(priv, r2, r1) & AR8327_ATU_FUNC_BUSY && --timeout) + udelay(10); + + if (!timeout) + pr_err("ar8327: timeout waiting for atu to become ready\n"); +} + +static void ar8327_get_arl_entry(struct ar8xxx_priv *priv, + struct arl_entry *a, u32 *status, enum arl_op op) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r2, page; + u16 r1_data0, r1_data1, r1_data2, r1_func; + u32 t, val0, val1, val2; + int i; + + split_addr(AR8327_REG_ATU_DATA0, &r1_data0, &r2, &page); + r2 |= 0x10; + + r1_data1 = (AR8327_REG_ATU_DATA1 >> 1) & 0x1e; + r1_data2 = (AR8327_REG_ATU_DATA2 >> 1) & 0x1e; + r1_func = (AR8327_REG_ATU_FUNC >> 1) & 0x1e; + + switch (op) { + case AR8XXX_ARL_INITIALIZE: + /* all ATU registers are on the same page + * therefore set page only once + */ + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + + ar8327_wait_atu_ready(priv, r2, r1_func); + + ar8xxx_mii_write32(priv, r2, r1_data0, 0); + ar8xxx_mii_write32(priv, r2, r1_data1, 0); + ar8xxx_mii_write32(priv, r2, r1_data2, 0); + break; + case AR8XXX_ARL_GET_NEXT: + ar8xxx_mii_write32(priv, r2, r1_func, + AR8327_ATU_FUNC_OP_GET_NEXT | + AR8327_ATU_FUNC_BUSY); + ar8327_wait_atu_ready(priv, r2, r1_func); + + val0 = ar8xxx_mii_read32(priv, r2, r1_data0); + val1 = ar8xxx_mii_read32(priv, r2, r1_data1); + val2 = ar8xxx_mii_read32(priv, r2, r1_data2); + + *status = val2 & AR8327_ATU_STATUS; + if (!*status) + break; + + i = 0; + t = AR8327_ATU_PORT0; + while (!(val1 & t) && ++i < AR8327_NUM_PORTS) + t <<= 1; + + a->port = i; + a->mac[0] = (val0 & AR8327_ATU_ADDR0) >> AR8327_ATU_ADDR0_S; + a->mac[1] = (val0 & AR8327_ATU_ADDR1) >> AR8327_ATU_ADDR1_S; + a->mac[2] = (val0 & AR8327_ATU_ADDR2) >> AR8327_ATU_ADDR2_S; + a->mac[3] = (val0 & AR8327_ATU_ADDR3) >> AR8327_ATU_ADDR3_S; + a->mac[4] = (val1 & AR8327_ATU_ADDR4) >> AR8327_ATU_ADDR4_S; + a->mac[5] = (val1 & AR8327_ATU_ADDR5) >> AR8327_ATU_ADDR5_S; + break; + } +} + static int ar8327_sw_hw_apply(struct switch_dev *dev) { @@ -955,6 +1164,110 @@ ar8327_sw_hw_apply(struct switch_dev *dev) return 0; } +int +ar8327_sw_get_port_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->reg_mutex); + val->value.i = ar8327_get_port_igmp(priv, port); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_set_port_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->reg_mutex); + ar8327_set_port_igmp(priv, port, val->value.i); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_get_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int port; + + for (port = 0; port < dev->ports; port++) { + val->port_vlan = port; + if (ar8327_sw_get_port_igmp_snooping(dev, attr, val) || + !val->value.i) + break; + } + + return 0; +} + +int +ar8327_sw_set_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int port; + + for (port = 0; port < dev->ports; port++) { + val->port_vlan = port; + if (ar8327_sw_set_port_igmp_snooping(dev, attr, val)) + break; + } + + return 0; +} + +int +ar8327_sw_get_igmp_v3(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u32 val_reg; + + mutex_lock(&priv->reg_mutex); + val_reg = ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL1); + val->value.i = ((val_reg & AR8327_FRAME_ACK_CTRL_IGMP_V3_EN) != 0); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_set_igmp_v3(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + if (val->value.i) + ar8xxx_reg_set(priv, AR8327_REG_FRAME_ACK_CTRL1, + AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); + else + ar8xxx_reg_clear(priv, AR8327_REG_FRAME_ACK_CTRL1, + AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + static const struct switch_attr ar8327_sw_attr_globals[] = { { .type = SWITCH_TYPE_INT, @@ -1002,6 +1315,42 @@ static const struct switch_attr ar8327_sw_attr_globals[] = { .get = ar8xxx_sw_get_mirror_source_port, .max = AR8327_NUM_PORTS - 1 }, + { + .type = SWITCH_TYPE_INT, + .name = "arl_age_time", + .description = "ARL age time (secs)", + .set = ar8xxx_sw_set_arl_age_time, + .get = ar8xxx_sw_get_arl_age_time, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "arl_table", + .description = "Get ARL table", + .set = NULL, + .get = ar8xxx_sw_get_arl_table, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush ARL table", + .set = ar8xxx_sw_set_flush_arl_table, + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_snooping", + .description = "Enable IGMP Snooping", + .set = ar8327_sw_set_igmp_snooping, + .get = ar8327_sw_get_igmp_snooping, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_v3", + .description = "Enable IGMPv3 support", + .set = ar8327_sw_set_igmp_v3, + .get = ar8327_sw_get_igmp_v3, + .max = 1 + }, }; static const struct switch_attr ar8327_sw_attr_port[] = { @@ -1026,6 +1375,20 @@ static const struct switch_attr ar8327_sw_attr_port[] = { .get = ar8327_sw_get_eee, .max = 1, }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush port's ARL table entries", + .set = ar8xxx_sw_set_flush_port_arl_table, + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_snooping", + .description = "Enable port's IGMP Snooping", + .set = ar8327_sw_set_port_igmp_snooping, + .get = ar8327_sw_get_port_igmp_snooping, + .max = 1 + }, }; static const struct switch_dev_ops ar8327_sw_ops = { @@ -1062,6 +1425,7 @@ const struct ar8xxx_chip ar8327_chip = { .reg_port_stats_start = 0x1000, .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8327_REG_ARL_CTRL, .hw_init = ar8327_hw_init, .cleanup = ar8327_cleanup, @@ -1069,11 +1433,13 @@ const struct ar8xxx_chip ar8327_chip = { .init_port = ar8327_init_port, .setup_port = ar8327_setup_port, .read_port_status = ar8327_read_port_status, + .read_port_eee_status = ar8327_read_port_eee_status, .atu_flush = ar8327_atu_flush, + .atu_flush_port = ar8327_atu_flush_port, .vtu_flush = ar8327_vtu_flush, .vtu_load_vlan = ar8327_vtu_load_vlan, - .phy_fixup = ar8327_phy_fixup, .set_mirror_regs = ar8327_set_mirror_regs, + .get_arl_entry = ar8327_get_arl_entry, .sw_hw_apply = ar8327_sw_hw_apply, .num_mibs = ARRAY_SIZE(ar8236_mibs), @@ -1093,6 +1459,7 @@ const struct ar8xxx_chip ar8337_chip = { .reg_port_stats_start = 0x1000, .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8327_REG_ARL_CTRL, .hw_init = ar8327_hw_init, .cleanup = ar8327_cleanup, @@ -1100,15 +1467,17 @@ const struct ar8xxx_chip ar8337_chip = { .init_port = ar8327_init_port, .setup_port = ar8327_setup_port, .read_port_status = ar8327_read_port_status, + .read_port_eee_status = ar8327_read_port_eee_status, .atu_flush = ar8327_atu_flush, + .atu_flush_port = ar8327_atu_flush_port, .vtu_flush = ar8327_vtu_flush, .vtu_load_vlan = ar8327_vtu_load_vlan, .phy_fixup = ar8327_phy_fixup, .set_mirror_regs = ar8327_set_mirror_regs, + .get_arl_entry = ar8327_get_arl_entry, .sw_hw_apply = ar8327_sw_hw_apply, .num_mibs = ARRAY_SIZE(ar8236_mibs), .mib_decs = ar8236_mibs, .mib_func = AR8327_REG_MIB_FUNC }; -