blob: dfbfdb47127ca65a074d11f17ee394ad2401a6da [file] [log] [blame]
/*
* Copyright (c) 2016-2017, 2019-2020, The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_address.h>
#include <linux/clk.h>
#include <linux/of_mdio.h>
#include <linux/reset.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#define MDIO_CTRL_0_REG 0x40
#define MDIO_CTRL_1_REG 0x44
#define MDIO_CTRL_2_REG 0x48
#define MDIO_CTRL_3_REG 0x4c
#define MDIO_CTRL_4_REG 0x50
#define MDIO_CTRL_4_ACCESS_BUSY BIT(16)
#define MDIO_CTRL_4_ACCESS_START BIT(8)
#define MDIO_CTRL_4_ACCESS_CODE_READ 0
#define MDIO_CTRL_4_ACCESS_CODE_WRITE 1
#define MDIO_CTRL_4_ACCESS_CODE_C45_ADDR 0
#define MDIO_CTRL_4_ACCESS_CODE_C45_WRITE 1
#define MDIO_CTRL_4_ACCESS_CODE_C45_READ 2
#define CTRL_0_REG_DEFAULT_VALUE 0x1500F
#define CTRL_0_REG_C45_DEFAULT_VALUE 0x1510F
#define QCA_MDIO_RETRY 1000
#define QCA_MDIO_DELAY 10
#define QCA_MAX_PHY_RESET 3
#define QCA_MDIO_CLK_RATE 100000000
#define TCSR_LDO_ADDR 0x19475C4
#define GCC_GEPHY_ADDR 0x1856004
#define REG_SIZE 4
struct qca_mdio_data {
struct mii_bus *mii_bus;
struct clk *mdio_clk;
void __iomem *membase;
int phy_irq[PHY_MAX_ADDR];
};
static int qca_mdio_wait_busy(struct qca_mdio_data *am)
{
int i;
for (i = 0; i < QCA_MDIO_RETRY; i++) {
unsigned int busy;
busy = readl(am->membase + MDIO_CTRL_4_REG);
busy &= MDIO_CTRL_4_ACCESS_BUSY;
if (!busy)
return 0;
udelay(QCA_MDIO_DELAY);
}
pr_err("%s: MDIO operation timed out\n", am->mii_bus->name);
return -ETIMEDOUT;
}
static int qca_mdio_read(struct mii_bus *bus, int mii_id, int regnum)
{
struct qca_mdio_data *am = bus->priv;
int value = 0;
unsigned int cmd = 0;
if (qca_mdio_wait_busy(am))
return 0xffff;
if (regnum & MII_ADDR_C45) {
unsigned int mmd = (regnum >> 16) & 0x1F;
unsigned int reg = regnum & 0xFFFF;
writel(CTRL_0_REG_C45_DEFAULT_VALUE,
am->membase + MDIO_CTRL_0_REG);
/* issue the phy address and mmd */
writel((mii_id << 8) | mmd, am->membase + MDIO_CTRL_1_REG);
/* issue reg */
writel(reg, am->membase + MDIO_CTRL_2_REG);
cmd = MDIO_CTRL_4_ACCESS_START |
MDIO_CTRL_4_ACCESS_CODE_C45_ADDR;
} else {
writel(CTRL_0_REG_DEFAULT_VALUE, am->membase + MDIO_CTRL_0_REG);
/* issue the phy address and reg */
writel((mii_id << 8) | regnum, am->membase + MDIO_CTRL_1_REG);
cmd = MDIO_CTRL_4_ACCESS_START | MDIO_CTRL_4_ACCESS_CODE_READ;
}
/* issue command */
writel(cmd, am->membase + MDIO_CTRL_4_REG);
/* Wait complete */
if (qca_mdio_wait_busy(am))
return 0xffff;
if (regnum & MII_ADDR_C45) {
cmd = MDIO_CTRL_4_ACCESS_START |
MDIO_CTRL_4_ACCESS_CODE_C45_READ;
writel(cmd, am->membase + MDIO_CTRL_4_REG);
if (qca_mdio_wait_busy(am))
return 0xffff;
}
/* Read data */
value = readl(am->membase + MDIO_CTRL_3_REG);
return value;
}
static int qca_mdio_write(struct mii_bus *bus, int mii_id, int regnum,
u16 value)
{
struct qca_mdio_data *am = bus->priv;
unsigned int cmd = 0;
if (qca_mdio_wait_busy(am))
return 0xffff;
if (regnum & MII_ADDR_C45) {
unsigned int mmd = (regnum >> 16) & 0x1F;
unsigned int reg = regnum & 0xFFFF;
writel(CTRL_0_REG_C45_DEFAULT_VALUE,
am->membase + MDIO_CTRL_0_REG);
/* issue the phy address and mmd */
writel((mii_id << 8) | mmd, am->membase + MDIO_CTRL_1_REG);
/* issue reg */
writel(reg, am->membase + MDIO_CTRL_2_REG);
cmd = MDIO_CTRL_4_ACCESS_START |
MDIO_CTRL_4_ACCESS_CODE_C45_ADDR;
writel(cmd, am->membase + MDIO_CTRL_4_REG);
if (qca_mdio_wait_busy(am))
return -ETIMEDOUT;
} else {
writel(CTRL_0_REG_DEFAULT_VALUE, am->membase + MDIO_CTRL_0_REG);
/* issue the phy address and reg */
writel((mii_id << 8) | regnum, am->membase + MDIO_CTRL_1_REG);
}
/* issue write data */
writel(value, am->membase + MDIO_CTRL_2_REG);
/* issue write command */
if (regnum & MII_ADDR_C45)
cmd = MDIO_CTRL_4_ACCESS_START |
MDIO_CTRL_4_ACCESS_CODE_C45_WRITE;
else
cmd = MDIO_CTRL_4_ACCESS_START | MDIO_CTRL_4_ACCESS_CODE_WRITE;
writel(cmd, am->membase + MDIO_CTRL_4_REG);
/* Wait write complete */
if (qca_mdio_wait_busy(am))
return -ETIMEDOUT;
return 0;
}
static int qca_phy_gpio_set(struct platform_device *pdev, int number)
{
int ret;
ret = gpio_request(number, "phy-reset-gpio");
if (ret) {
dev_err(&pdev->dev, "Can't get phy-reset-gpio %d\n", ret);
return ret;
}
ret = gpio_direction_output(number, 0x0);
if (ret) {
dev_err(&pdev->dev,
"Can't set direction for phy-reset-gpio %d\n", ret);
goto phy_reset_out;
}
usleep_range(100000, 110000);
gpio_set_value(number, 0x01);
usleep_range(100000, 110000);
phy_reset_out:
gpio_free(number);
return ret;
}
static int qca_phy_reset(struct platform_device *pdev)
{
struct device_node *mdio_node = pdev->dev.of_node;
int phy_reset_gpio_number;
int ret, i;
for (i = 0; i < QCA_MAX_PHY_RESET; i++) {
ret = of_get_named_gpio(mdio_node, "phy-reset-gpio", i);
if (ret < 0) {
dev_info(&pdev->dev, "Could not find phy-reset-gpio\n");
return 0;
}
phy_reset_gpio_number = ret;
ret = qca_phy_gpio_set(pdev, phy_reset_gpio_number);
if (ret)
return ret;
}
return 0;
}
static void qca_tcsr_ldo_rdy_set(bool ready)
{
void __iomem *tcsr_base = NULL;
u32 val;
tcsr_base = ioremap_nocache(TCSR_LDO_ADDR, REG_SIZE);
if (!tcsr_base)
return;
val = readl(tcsr_base);
if (ready)
val |= 1;
else
val &= ~1;
writel(val, tcsr_base);
usleep_range(100000, 110000);
iounmap(tcsr_base);
}
static int qca_mdio_probe(struct platform_device *pdev)
{
struct qca_mdio_data *am;
struct resource *res;
int ret, i;
struct reset_control *rst = ERR_PTR(-EINVAL);
if (of_machine_is_compatible("qcom,ipq5018")) {
qca_tcsr_ldo_rdy_set(true);
rst = of_reset_control_get(pdev->dev.of_node, "gephy_mdc_rst");
if (!IS_ERR(rst)) {
reset_control_deassert(rst);
usleep_range(100000, 110000);
}
}
ret = qca_phy_reset(pdev);
if (ret)
dev_err(&pdev->dev, "Could not find reset gpio\n");
am = devm_kzalloc(&pdev->dev, sizeof(*am), GFP_KERNEL);
if (!am)
return -ENOMEM;
am->mdio_clk = devm_clk_get(&pdev->dev, "gcc_mdio_ahb_clk");
if (!IS_ERR(am->mdio_clk)) {
ret = clk_set_rate(am->mdio_clk, QCA_MDIO_CLK_RATE);
if (ret)
goto err_out;
ret = clk_prepare_enable(am->mdio_clk);
if (ret)
goto err_out;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "no iomem resource found\n");
ret = -ENXIO;
goto err_disable_clk;
}
am->membase = devm_ioremap_resource(&pdev->dev, res);
if (!am->membase) {
dev_err(&pdev->dev, "unable to ioremap registers\n");
ret = -ENOMEM;
goto err_disable_clk;
}
am->mii_bus = mdiobus_alloc();
if (!am->mii_bus) {
ret = -ENOMEM;
goto err_disable_clk;
}
writel(CTRL_0_REG_DEFAULT_VALUE, am->membase + MDIO_CTRL_0_REG);
am->mii_bus->name = "qca_mdio";
am->mii_bus->read = &qca_mdio_read;
am->mii_bus->write = &qca_mdio_write;
am->mii_bus->priv = am;
am->mii_bus->parent = &pdev->dev;
snprintf(am->mii_bus->id, MII_BUS_ID_SIZE, "%s", dev_name(&pdev->dev));
for (i = 0; i < PHY_MAX_ADDR; i++) {
am->phy_irq[i] = PHY_POLL;
am->mii_bus->irq[i] = PHY_POLL;
}
platform_set_drvdata(pdev, am);
ret = of_mdiobus_register(am->mii_bus, pdev->dev.of_node);
if (ret)
goto err_free_bus;
dev_info(&pdev->dev, "qca-mdio driver was registered\n");
if (of_machine_is_compatible("qcom,ipq5018")) {
qca_tcsr_ldo_rdy_set(false);
if (!IS_ERR(rst))
reset_control_assert(rst);
}
return 0;
err_free_bus:
mdiobus_free(am->mii_bus);
err_disable_clk:
if (!IS_ERR(am->mdio_clk))
clk_disable_unprepare(am->mdio_clk);
err_out:
if (of_machine_is_compatible("qcom,ipq5018")) {
qca_tcsr_ldo_rdy_set(false);
if (!IS_ERR(rst))
reset_control_assert(rst);
}
return ret;
}
static int qca_mdio_remove(struct platform_device *pdev)
{
struct qca_mdio_data *am = platform_get_drvdata(pdev);
if (am) {
if (!IS_ERR(am->mdio_clk))
clk_disable_unprepare(am->mdio_clk);
mdiobus_unregister(am->mii_bus);
mdiobus_free(am->mii_bus);
platform_set_drvdata(pdev, NULL);
}
return 0;
}
static const struct of_device_id qca_mdio_dt_ids[] = {
{ .compatible = "qcom,ipq40xx-mdio" },
{ .compatible = "qcom,qca-mdio" },
{ }
};
MODULE_DEVICE_TABLE(of, qca_mdio_dt_ids);
static struct platform_driver qca_mdio_driver = {
.probe = qca_mdio_probe,
.remove = qca_mdio_remove,
.driver = {
.name = "qca-mdio",
.of_match_table = qca_mdio_dt_ids,
},
};
module_platform_driver(qca_mdio_driver);
MODULE_DESCRIPTION("QCA MDIO interface driver");
MODULE_LICENSE("Dual BSD/GPL");