@@ -7,7 +7,7 @@ config AT803X_PHY
select QCOM_NET_PHYLIB
depends on REGULATOR
help
- Currently supports the AR8030, AR8031, AR8033, AR8035 model
+ Currently supports the AR8030, AR8031, AR8033, AR8035, IPQ5018 model
config QCA83XX_PHY
tristate "Qualcomm Atheros QCA833x PHYs"
@@ -7,19 +7,24 @@
* Author: Matus Ujhelyi <ujhelyi.m@gmail.com>
*/
-#include <linux/phy.h>
-#include <linux/module.h>
-#include <linux/string.h>
-#include <linux/netdevice.h>
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/etherdevice.h>
#include <linux/ethtool_netlink.h>
-#include <linux/bitfield.h>
-#include <linux/regulator/of_regulator.h>
-#include <linux/regulator/driver.h>
-#include <linux/regulator/consumer.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
#include <linux/of.h>
+#include <linux/phy.h>
#include <linux/phylink.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/reset.h>
#include <linux/sfp.h>
+#include <linux/string.h>
#include <dt-bindings/net/qca-ar803x.h>
#include "qcom.h"
@@ -96,6 +101,8 @@
#define ATH8035_PHY_ID 0x004dd072
#define AT8030_PHY_ID_MASK 0xffffffef
+#define IPQ5018_PHY_ID 0x004dd0c0
+
#define QCA9561_PHY_ID 0x004dd042
#define AT803X_PAGE_FIBER 0
@@ -108,6 +115,46 @@
/* disable hibernation mode */
#define AT803X_DISABLE_HIBERNATION_MODE BIT(2)
+#define IPQ5018_PHY_FIFO_CONTROL 0x19
+#define IPQ5018_PHY_FIFO_RESET GENMASK(1, 0)
+
+#define IPQ5018_PHY_DEBUG_EDAC 0x4380
+#define IPQ5018_PHY_MMD1_MDAC 0x8100
+#define IPQ5018_PHY_DAC_MASK GENMASK(15, 8)
+
+#define IPQ5018_PHY_MMD1_MSE_THRESH1 0x1000
+#define IPQ5018_PHY_MMD1_MSE_THRESH2 0x1001
+#define IPQ5018_PHY_MMD3_AZ_CTRL1 0x8008
+#define IPQ5018_PHY_MMD3_AZ_CTRL2 0x8009
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL3 0x8074
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL4 0x8075
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL5 0x8076
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL6 0x8077
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL7 0x8078
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL9 0x807a
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL13 0x807e
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL14 0x807f
+
+#define IPQ5018_PHY_MMD1_MSE_THRESH1_VAL 0xf1
+#define IPQ5018_PHY_MMD1_MSE_THRESH2_VAL 0x1f6
+#define IPQ5018_PHY_MMD3_AZ_CTRL1_VAL 0x7880
+#define IPQ5018_PHY_MMD3_AZ_CTRL2_VAL 0xc8
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL3_VAL 0xc040
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL4_VAL 0xa060
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL5_VAL 0xc040
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL6_VAL 0xa060
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL7_VAL 0xc24c
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL9_VAL 0xc060
+#define IPQ5018_PHY_MMD3_CDT_THRESH_CTRL13_VAL 0xb060
+#define IPQ5018_PHY_MMD3_NEAR_ECHO_THRESH_VAL 0x90b0
+
+#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE 0x1
+#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_MASK GENMASK(7, 4)
+#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_DEFAULT 0x50
+#define IPQ5018_PHY_DEBUG_ANA_DAC_FILTER 0xa080
+
+#define IPQ5018_TCSR_ETH_LDO_READY BIT(0)
+
MODULE_DESCRIPTION("Qualcomm Atheros AR803x PHY driver");
MODULE_AUTHOR("Matus Ujhelyi");
MODULE_LICENSE("GPL");
@@ -133,6 +180,14 @@ struct at803x_context {
u16 led_control;
};
+struct ipq5018_priv {
+ int num_clks;
+ struct clk_bulk_data *clks;
+ struct reset_control *rst;
+ u32 mdac;
+ u32 edac;
+};
+
static int at803x_write_page(struct phy_device *phydev, int page)
{
int mask;
@@ -987,6 +1042,142 @@ static int at8035_probe(struct phy_device *phydev)
return at8035_parse_dt(phydev);
}
+static inline int ipq5018_cmnblk_enable(struct device *dev)
+{
+ unsigned int offset;
+ struct regmap *tcsr;
+
+ tcsr = syscon_regmap_lookup_by_phandle_args(dev->of_node, "qca,eth-ldo-ready",
+ 1, &offset);
+ if (IS_ERR(tcsr))
+ return PTR_ERR(tcsr);
+
+ return regmap_set_bits(tcsr, offset, IPQ5018_TCSR_ETH_LDO_READY);
+}
+
+static int ipq5018_cable_test_start(struct phy_device *phydev)
+{
+ phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_MMD3_CDT_THRESH_CTRL3,
+ IPQ5018_PHY_MMD3_CDT_THRESH_CTRL3_VAL);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_MMD3_CDT_THRESH_CTRL4,
+ IPQ5018_PHY_MMD3_CDT_THRESH_CTRL4_VAL);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_MMD3_CDT_THRESH_CTRL5,
+ IPQ5018_PHY_MMD3_CDT_THRESH_CTRL5_VAL);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_MMD3_CDT_THRESH_CTRL6,
+ IPQ5018_PHY_MMD3_CDT_THRESH_CTRL6_VAL);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_MMD3_CDT_THRESH_CTRL7,
+ IPQ5018_PHY_MMD3_CDT_THRESH_CTRL7_VAL);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_MMD3_CDT_THRESH_CTRL9,
+ IPQ5018_PHY_MMD3_CDT_THRESH_CTRL9_VAL);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_MMD3_CDT_THRESH_CTRL13,
+ IPQ5018_PHY_MMD3_CDT_THRESH_CTRL13_VAL);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_MMD3_CDT_THRESH_CTRL3,
+ IPQ5018_PHY_MMD3_NEAR_ECHO_THRESH_VAL);
+
+ /* we do all the (time consuming) work later */
+ return 0;
+}
+
+static int ipq5018_config_init(struct phy_device *phydev)
+{
+ struct ipq5018_priv *priv = phydev->priv;
+ u16 val = 0;
+
+ /*
+ * set LDO efuse: first temporarily store ANA_DAC_FILTER value from
+ * debug register as it will be reset once the ANA_LDO_EFUSE register
+ * is written to
+ */
+ val = at803x_debug_reg_read(phydev, IPQ5018_PHY_DEBUG_ANA_DAC_FILTER);
+ at803x_debug_reg_mask(phydev, IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE,
+ IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_MASK,
+ IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_DEFAULT);
+ at803x_debug_reg_write(phydev, IPQ5018_PHY_DEBUG_ANA_DAC_FILTER, val);
+
+ /* set 8023AZ CTRL values */
+ phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_MMD3_AZ_CTRL1,
+ IPQ5018_PHY_MMD3_AZ_CTRL1_VAL);
+ phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_MMD3_AZ_CTRL2,
+ IPQ5018_PHY_MMD3_AZ_CTRL2_VAL);
+
+ /* set MSE threshold values */
+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MSE_THRESH1,
+ IPQ5018_PHY_MMD1_MSE_THRESH1_VAL);
+ phy_write_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MSE_THRESH2,
+ IPQ5018_PHY_MMD1_MSE_THRESH2_VAL);
+
+ if (priv->mdac && priv->edac) {
+ /* setting MDAC (Multi-level Digital-to-Analog Converter) in MMD1 */
+ phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MDAC,
+ IPQ5018_PHY_DAC_MASK, priv->mdac);
+
+ /* setting EDAC (Error-detection and Correction) in debug register */
+ at803x_debug_reg_mask(phydev, IPQ5018_PHY_DEBUG_EDAC,
+ IPQ5018_PHY_DAC_MASK, priv->edac);
+ }
+
+ return 0;
+}
+
+static void ipq5018_link_change_notify(struct phy_device *phydev)
+{
+ mdiobus_modify_changed(phydev->mdio.bus, phydev->mdio.addr,
+ IPQ5018_PHY_FIFO_CONTROL, IPQ5018_PHY_FIFO_RESET,
+ phydev->link ? IPQ5018_PHY_FIFO_RESET : 0);
+}
+
+static int ipq5018_probe(struct phy_device *phydev)
+{
+ struct device *dev = &phydev->mdio.dev;
+ struct ipq5018_priv *priv;
+ u32 mdac, edac = 0;
+ int ret, cnt;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ /* PHY DAC values are optional and only set in a PHY to PHY link architecture */
+ cnt = of_property_count_u32_elems(dev->of_node, "qca,dac");
+ if (cnt == 2) {
+ ret = of_property_read_u32_index(dev->of_node, "qca,dac", 0, &mdac);
+ if (!ret)
+ priv->mdac = mdac;
+
+ ret = of_property_read_u32_index(dev->of_node, "qca,dac", 1, &edac);
+ if (!ret)
+ priv->edac = edac;
+ }
+
+ ret = ipq5018_cmnblk_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to enable LDO controller to CMN BLK");
+
+ priv->num_clks = devm_clk_bulk_get_all(dev, &priv->clks);
+ if (priv->num_clks < 0)
+ return dev_err_probe(dev, priv->num_clks,
+ "failed to acquire clocks\n");
+
+ ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to enable clocks\n");
+
+ priv->rst = devm_reset_control_array_get_exclusive(dev);
+ if (IS_ERR_OR_NULL(priv->rst))
+ return dev_err_probe(dev, PTR_ERR(priv->rst),
+ "failed to acquire reset\n");
+
+ ret = reset_control_reset(priv->rst);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to reset\n");
+
+ phydev->priv = priv;
+
+ return 0;
+}
+
static struct phy_driver at803x_driver[] = {
{
/* Qualcomm Atheros AR8035 */
@@ -1078,6 +1269,19 @@ static struct phy_driver at803x_driver[] = {
.read_status = at803x_read_status,
.soft_reset = genphy_soft_reset,
.config_aneg = at803x_config_aneg,
+}, {
+ PHY_ID_MATCH_EXACT(IPQ5018_PHY_ID),
+ .name = "Qualcomm Atheros IPQ5018 internal PHY",
+ .flags = PHY_IS_INTERNAL | PHY_POLL_CABLE_TEST,
+ .probe = ipq5018_probe,
+ .config_init = ipq5018_config_init,
+ .link_change_notify = ipq5018_link_change_notify,
+ .read_status = at803x_read_status,
+ .config_intr = at803x_config_intr,
+ .handle_interrupt = at803x_handle_interrupt,
+ .cable_test_start = ipq5018_cable_test_start,
+ .cable_test_get_status = qca808x_cable_test_get_status,
+ .soft_reset = genphy_soft_reset,
}, {
/* Qualcomm Atheros QCA9561 */
PHY_ID_MATCH_EXACT(QCA9561_PHY_ID),
@@ -1104,6 +1308,7 @@ static const struct mdio_device_id __maybe_unused atheros_tbl[] = {
{ PHY_ID_MATCH_EXACT(ATH8032_PHY_ID) },
{ PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) },
{ PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) },
+ { PHY_ID_MATCH_EXACT(IPQ5018_PHY_ID) },
{ PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) },
{ }
};