Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /* Qualcomm IPQ8064 MDIO interface driver
0003  *
0004  * Copyright (C) 2019 Christian Lamparter <chunkeey@gmail.com>
0005  * Copyright (C) 2020 Ansuel Smith <ansuelsmth@gmail.com>
0006  */
0007 
0008 #include <linux/delay.h>
0009 #include <linux/kernel.h>
0010 #include <linux/module.h>
0011 #include <linux/of_mdio.h>
0012 #include <linux/of_address.h>
0013 #include <linux/platform_device.h>
0014 #include <linux/regmap.h>
0015 
0016 /* MII address register definitions */
0017 #define MII_ADDR_REG_ADDR           0x10
0018 #define MII_BUSY                BIT(0)
0019 #define MII_WRITE               BIT(1)
0020 #define MII_CLKRANGE(x)             ((x) << 2)
0021 #define MII_CLKRANGE_60_100M            MII_CLKRANGE(0)
0022 #define MII_CLKRANGE_100_150M           MII_CLKRANGE(1)
0023 #define MII_CLKRANGE_20_35M         MII_CLKRANGE(2)
0024 #define MII_CLKRANGE_35_60M         MII_CLKRANGE(3)
0025 #define MII_CLKRANGE_150_250M           MII_CLKRANGE(4)
0026 #define MII_CLKRANGE_250_300M           MII_CLKRANGE(5)
0027 #define MII_CLKRANGE_MASK           GENMASK(4, 2)
0028 #define MII_REG_SHIFT               6
0029 #define MII_REG_MASK                GENMASK(10, 6)
0030 #define MII_ADDR_SHIFT              11
0031 #define MII_ADDR_MASK               GENMASK(15, 11)
0032 
0033 #define MII_DATA_REG_ADDR           0x14
0034 
0035 #define MII_MDIO_DELAY_USEC         (1000)
0036 #define MII_MDIO_RETRY_MSEC         (10)
0037 
0038 struct ipq8064_mdio {
0039     struct regmap *base; /* NSS_GMAC0_BASE */
0040 };
0041 
0042 static int
0043 ipq8064_mdio_wait_busy(struct ipq8064_mdio *priv)
0044 {
0045     u32 busy;
0046 
0047     return regmap_read_poll_timeout(priv->base, MII_ADDR_REG_ADDR, busy,
0048                     !(busy & MII_BUSY), MII_MDIO_DELAY_USEC,
0049                     MII_MDIO_RETRY_MSEC * USEC_PER_MSEC);
0050 }
0051 
0052 static int
0053 ipq8064_mdio_read(struct mii_bus *bus, int phy_addr, int reg_offset)
0054 {
0055     u32 miiaddr = MII_BUSY | MII_CLKRANGE_250_300M;
0056     struct ipq8064_mdio *priv = bus->priv;
0057     u32 ret_val;
0058     int err;
0059 
0060     /* Reject clause 45 */
0061     if (reg_offset & MII_ADDR_C45)
0062         return -EOPNOTSUPP;
0063 
0064     miiaddr |= ((phy_addr << MII_ADDR_SHIFT) & MII_ADDR_MASK) |
0065            ((reg_offset << MII_REG_SHIFT) & MII_REG_MASK);
0066 
0067     regmap_write(priv->base, MII_ADDR_REG_ADDR, miiaddr);
0068     usleep_range(10, 13);
0069 
0070     err = ipq8064_mdio_wait_busy(priv);
0071     if (err)
0072         return err;
0073 
0074     regmap_read(priv->base, MII_DATA_REG_ADDR, &ret_val);
0075     return (int)ret_val;
0076 }
0077 
0078 static int
0079 ipq8064_mdio_write(struct mii_bus *bus, int phy_addr, int reg_offset, u16 data)
0080 {
0081     u32 miiaddr = MII_WRITE | MII_BUSY | MII_CLKRANGE_250_300M;
0082     struct ipq8064_mdio *priv = bus->priv;
0083 
0084     /* Reject clause 45 */
0085     if (reg_offset & MII_ADDR_C45)
0086         return -EOPNOTSUPP;
0087 
0088     regmap_write(priv->base, MII_DATA_REG_ADDR, data);
0089 
0090     miiaddr |= ((phy_addr << MII_ADDR_SHIFT) & MII_ADDR_MASK) |
0091            ((reg_offset << MII_REG_SHIFT) & MII_REG_MASK);
0092 
0093     regmap_write(priv->base, MII_ADDR_REG_ADDR, miiaddr);
0094 
0095     /* For the specific reg 31 extra time is needed or the next
0096      * read will produce garbage data.
0097      */
0098     if (reg_offset == 31)
0099         usleep_range(30, 43);
0100     else
0101         usleep_range(10, 13);
0102 
0103     return ipq8064_mdio_wait_busy(priv);
0104 }
0105 
0106 static const struct regmap_config ipq8064_mdio_regmap_config = {
0107     .reg_bits = 32,
0108     .reg_stride = 4,
0109     .val_bits = 32,
0110     .can_multi_write = false,
0111     /* the mdio lock is used by any user of this mdio driver */
0112     .disable_locking = true,
0113 
0114     .cache_type = REGCACHE_NONE,
0115 };
0116 
0117 static int
0118 ipq8064_mdio_probe(struct platform_device *pdev)
0119 {
0120     struct device_node *np = pdev->dev.of_node;
0121     struct ipq8064_mdio *priv;
0122     struct resource res;
0123     struct mii_bus *bus;
0124     void __iomem *base;
0125     int ret;
0126 
0127     if (of_address_to_resource(np, 0, &res))
0128         return -ENOMEM;
0129 
0130     base = devm_ioremap(&pdev->dev, res.start, resource_size(&res));
0131     if (!base)
0132         return -ENOMEM;
0133 
0134     bus = devm_mdiobus_alloc_size(&pdev->dev, sizeof(*priv));
0135     if (!bus)
0136         return -ENOMEM;
0137 
0138     bus->name = "ipq8064_mdio_bus";
0139     bus->read = ipq8064_mdio_read;
0140     bus->write = ipq8064_mdio_write;
0141     snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&pdev->dev));
0142     bus->parent = &pdev->dev;
0143 
0144     priv = bus->priv;
0145     priv->base = devm_regmap_init_mmio(&pdev->dev, base,
0146                        &ipq8064_mdio_regmap_config);
0147     if (IS_ERR(priv->base))
0148         return PTR_ERR(priv->base);
0149 
0150     ret = of_mdiobus_register(bus, np);
0151     if (ret)
0152         return ret;
0153 
0154     platform_set_drvdata(pdev, bus);
0155     return 0;
0156 }
0157 
0158 static int
0159 ipq8064_mdio_remove(struct platform_device *pdev)
0160 {
0161     struct mii_bus *bus = platform_get_drvdata(pdev);
0162 
0163     mdiobus_unregister(bus);
0164 
0165     return 0;
0166 }
0167 
0168 static const struct of_device_id ipq8064_mdio_dt_ids[] = {
0169     { .compatible = "qcom,ipq8064-mdio" },
0170     { }
0171 };
0172 MODULE_DEVICE_TABLE(of, ipq8064_mdio_dt_ids);
0173 
0174 static struct platform_driver ipq8064_mdio_driver = {
0175     .probe = ipq8064_mdio_probe,
0176     .remove = ipq8064_mdio_remove,
0177     .driver = {
0178         .name = "ipq8064-mdio",
0179         .of_match_table = ipq8064_mdio_dt_ids,
0180     },
0181 };
0182 
0183 module_platform_driver(ipq8064_mdio_driver);
0184 
0185 MODULE_DESCRIPTION("Qualcomm IPQ8064 MDIO interface driver");
0186 MODULE_AUTHOR("Christian Lamparter <chunkeey@gmail.com>");
0187 MODULE_AUTHOR("Ansuel Smith <ansuelsmth@gmail.com>");
0188 MODULE_LICENSE("GPL");