diff options
Diffstat (limited to 'drivers/phy/hisilicon')
-rw-r--r-- | drivers/phy/hisilicon/Kconfig | 40 | ||||
-rw-r--r-- | drivers/phy/hisilicon/Makefile | 4 | ||||
-rw-r--r-- | drivers/phy/hisilicon/phy-hi6220-usb.c | 168 | ||||
-rw-r--r-- | drivers/phy/hisilicon/phy-hisi-inno-usb2.c | 197 | ||||
-rw-r--r-- | drivers/phy/hisilicon/phy-histb-combphy.c | 289 | ||||
-rw-r--r-- | drivers/phy/hisilicon/phy-hix5hd2-sata.c | 191 |
6 files changed, 889 insertions, 0 deletions
diff --git a/drivers/phy/hisilicon/Kconfig b/drivers/phy/hisilicon/Kconfig new file mode 100644 index 000000000..b40ee54a1 --- /dev/null +++ b/drivers/phy/hisilicon/Kconfig @@ -0,0 +1,40 @@ +# +# Phy drivers for Hisilicon platforms +# +config PHY_HI6220_USB + tristate "hi6220 USB PHY support" + depends on (ARCH_HISI && ARM64) || COMPILE_TEST + depends on HAS_IOMEM + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the HISILICON HI6220 USB PHY. + + To compile this driver as a module, choose M here. + +config PHY_HISTB_COMBPHY + tristate "HiSilicon STB SoCs COMBPHY support" + depends on (ARCH_HISI && ARM64) || COMPILE_TEST + select GENERIC_PHY + select MFD_SYSCON + help + Enable this to support the HISILICON STB SoCs COMBPHY. + If unsure, say N. + +config PHY_HISI_INNO_USB2 + tristate "HiSilicon INNO USB2 PHY support" + depends on (ARCH_HISI && ARM64) || COMPILE_TEST + select GENERIC_PHY + select MFD_SYSCON + help + Support for INNO USB2 PHY on HiSilicon SoCs. This Phy supports + USB 1.5Mb/s, USB 12Mb/s, USB 480Mb/s speeds. It supports one + USB host port to accept one USB device. + +config PHY_HIX5HD2_SATA + tristate "HIX5HD2 SATA PHY Driver" + depends on ARCH_HIX5HD2 && OF && HAS_IOMEM + select GENERIC_PHY + select MFD_SYSCON + help + Support for SATA PHY on Hisilicon hix5hd2 Soc. diff --git a/drivers/phy/hisilicon/Makefile b/drivers/phy/hisilicon/Makefile new file mode 100644 index 000000000..f662a4fe1 --- /dev/null +++ b/drivers/phy/hisilicon/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_PHY_HI6220_USB) += phy-hi6220-usb.o +obj-$(CONFIG_PHY_HISTB_COMBPHY) += phy-histb-combphy.o +obj-$(CONFIG_PHY_HISI_INNO_USB2) += phy-hisi-inno-usb2.o +obj-$(CONFIG_PHY_HIX5HD2_SATA) += phy-hix5hd2-sata.o diff --git a/drivers/phy/hisilicon/phy-hi6220-usb.c b/drivers/phy/hisilicon/phy-hi6220-usb.c new file mode 100644 index 000000000..398c1021d --- /dev/null +++ b/drivers/phy/hisilicon/phy-hi6220-usb.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2015 Linaro Ltd. + * Copyright (c) 2015 Hisilicon Limited. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> + +#define SC_PERIPH_CTRL4 0x00c + +#define CTRL4_PICO_SIDDQ BIT(6) +#define CTRL4_PICO_OGDISABLE BIT(8) +#define CTRL4_PICO_VBUSVLDEXT BIT(10) +#define CTRL4_PICO_VBUSVLDEXTSEL BIT(11) +#define CTRL4_OTG_PHY_SEL BIT(21) + +#define SC_PERIPH_CTRL5 0x010 + +#define CTRL5_USBOTG_RES_SEL BIT(3) +#define CTRL5_PICOPHY_ACAENB BIT(4) +#define CTRL5_PICOPHY_BC_MODE BIT(5) +#define CTRL5_PICOPHY_CHRGSEL BIT(6) +#define CTRL5_PICOPHY_VDATSRCEND BIT(7) +#define CTRL5_PICOPHY_VDATDETENB BIT(8) +#define CTRL5_PICOPHY_DCDENB BIT(9) +#define CTRL5_PICOPHY_IDDIG BIT(10) + +#define SC_PERIPH_CTRL8 0x018 +#define SC_PERIPH_RSTEN0 0x300 +#define SC_PERIPH_RSTDIS0 0x304 + +#define RST0_USBOTG_BUS BIT(4) +#define RST0_POR_PICOPHY BIT(5) +#define RST0_USBOTG BIT(6) +#define RST0_USBOTG_32K BIT(7) + +#define EYE_PATTERN_PARA 0x7053348c + +struct hi6220_priv { + struct regmap *reg; + struct device *dev; +}; + +static void hi6220_phy_init(struct hi6220_priv *priv) +{ + struct regmap *reg = priv->reg; + u32 val, mask; + + val = RST0_USBOTG_BUS | RST0_POR_PICOPHY | + RST0_USBOTG | RST0_USBOTG_32K; + mask = val; + regmap_update_bits(reg, SC_PERIPH_RSTEN0, mask, val); + regmap_update_bits(reg, SC_PERIPH_RSTDIS0, mask, val); +} + +static int hi6220_phy_setup(struct hi6220_priv *priv, bool on) +{ + struct regmap *reg = priv->reg; + u32 val, mask; + int ret; + + if (on) { + val = CTRL5_USBOTG_RES_SEL | CTRL5_PICOPHY_ACAENB; + mask = val | CTRL5_PICOPHY_BC_MODE; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL5, mask, val); + if (ret) + goto out; + + val = CTRL4_PICO_VBUSVLDEXT | CTRL4_PICO_VBUSVLDEXTSEL | + CTRL4_OTG_PHY_SEL; + mask = val | CTRL4_PICO_SIDDQ | CTRL4_PICO_OGDISABLE; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val); + if (ret) + goto out; + + ret = regmap_write(reg, SC_PERIPH_CTRL8, EYE_PATTERN_PARA); + if (ret) + goto out; + } else { + val = CTRL4_PICO_SIDDQ; + mask = val; + ret = regmap_update_bits(reg, SC_PERIPH_CTRL4, mask, val); + if (ret) + goto out; + } + + return 0; +out: + dev_err(priv->dev, "failed to setup phy ret: %d\n", ret); + return ret; +} + +static int hi6220_phy_start(struct phy *phy) +{ + struct hi6220_priv *priv = phy_get_drvdata(phy); + + return hi6220_phy_setup(priv, true); +} + +static int hi6220_phy_exit(struct phy *phy) +{ + struct hi6220_priv *priv = phy_get_drvdata(phy); + + return hi6220_phy_setup(priv, false); +} + +static const struct phy_ops hi6220_phy_ops = { + .init = hi6220_phy_start, + .exit = hi6220_phy_exit, + .owner = THIS_MODULE, +}; + +static int hi6220_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct phy *phy; + struct hi6220_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->reg = syscon_regmap_lookup_by_phandle(dev->of_node, + "hisilicon,peripheral-syscon"); + if (IS_ERR(priv->reg)) { + dev_err(dev, "no hisilicon,peripheral-syscon\n"); + return PTR_ERR(priv->reg); + } + + hi6220_phy_init(priv); + + phy = devm_phy_create(dev, NULL, &hi6220_phy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id hi6220_phy_of_match[] = { + {.compatible = "hisilicon,hi6220-usb-phy",}, + { }, +}; +MODULE_DEVICE_TABLE(of, hi6220_phy_of_match); + +static struct platform_driver hi6220_phy_driver = { + .probe = hi6220_phy_probe, + .driver = { + .name = "hi6220-usb-phy", + .of_match_table = hi6220_phy_of_match, + } +}; +module_platform_driver(hi6220_phy_driver); + +MODULE_DESCRIPTION("HISILICON HI6220 USB PHY driver"); +MODULE_ALIAS("platform:hi6220-usb-phy"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/hisilicon/phy-hisi-inno-usb2.c b/drivers/phy/hisilicon/phy-hisi-inno-usb2.c new file mode 100644 index 000000000..524381249 --- /dev/null +++ b/drivers/phy/hisilicon/phy-hisi-inno-usb2.c @@ -0,0 +1,197 @@ +/* + * HiSilicon INNO USB2 PHY Driver. + * + * Copyright (c) 2016-2017 HiSilicon Technologies Co., Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/phy/phy.h> +#include <linux/reset.h> + +#define INNO_PHY_PORT_NUM 2 +#define REF_CLK_STABLE_TIME 100 /* unit:us */ +#define UTMI_CLK_STABLE_TIME 200 /* unit:us */ +#define TEST_CLK_STABLE_TIME 2 /* unit:ms */ +#define PHY_CLK_STABLE_TIME 2 /* unit:ms */ +#define UTMI_RST_COMPLETE_TIME 2 /* unit:ms */ +#define POR_RST_COMPLETE_TIME 300 /* unit:us */ +#define PHY_TEST_DATA GENMASK(7, 0) +#define PHY_TEST_ADDR GENMASK(15, 8) +#define PHY_TEST_PORT GENMASK(18, 16) +#define PHY_TEST_WREN BIT(21) +#define PHY_TEST_CLK BIT(22) /* rising edge active */ +#define PHY_TEST_RST BIT(23) /* low active */ +#define PHY_CLK_ENABLE BIT(2) + +struct hisi_inno_phy_port { + struct reset_control *utmi_rst; + struct hisi_inno_phy_priv *priv; +}; + +struct hisi_inno_phy_priv { + void __iomem *mmio; + struct clk *ref_clk; + struct reset_control *por_rst; + struct hisi_inno_phy_port ports[INNO_PHY_PORT_NUM]; +}; + +static void hisi_inno_phy_write_reg(struct hisi_inno_phy_priv *priv, + u8 port, u32 addr, u32 data) +{ + void __iomem *reg = priv->mmio; + u32 val; + + val = (data & PHY_TEST_DATA) | + ((addr << 8) & PHY_TEST_ADDR) | + ((port << 16) & PHY_TEST_PORT) | + PHY_TEST_WREN | PHY_TEST_RST; + writel(val, reg); + + val |= PHY_TEST_CLK; + writel(val, reg); + + val &= ~PHY_TEST_CLK; + writel(val, reg); +} + +static void hisi_inno_phy_setup(struct hisi_inno_phy_priv *priv) +{ + /* The phy clk is controlled by the port0 register 0x06. */ + hisi_inno_phy_write_reg(priv, 0, 0x06, PHY_CLK_ENABLE); + msleep(PHY_CLK_STABLE_TIME); +} + +static int hisi_inno_phy_init(struct phy *phy) +{ + struct hisi_inno_phy_port *port = phy_get_drvdata(phy); + struct hisi_inno_phy_priv *priv = port->priv; + int ret; + + ret = clk_prepare_enable(priv->ref_clk); + if (ret) + return ret; + udelay(REF_CLK_STABLE_TIME); + + reset_control_deassert(priv->por_rst); + udelay(POR_RST_COMPLETE_TIME); + + /* Set up phy registers */ + hisi_inno_phy_setup(priv); + + reset_control_deassert(port->utmi_rst); + udelay(UTMI_RST_COMPLETE_TIME); + + return 0; +} + +static int hisi_inno_phy_exit(struct phy *phy) +{ + struct hisi_inno_phy_port *port = phy_get_drvdata(phy); + struct hisi_inno_phy_priv *priv = port->priv; + + reset_control_assert(port->utmi_rst); + reset_control_assert(priv->por_rst); + clk_disable_unprepare(priv->ref_clk); + + return 0; +} + +static const struct phy_ops hisi_inno_phy_ops = { + .init = hisi_inno_phy_init, + .exit = hisi_inno_phy_exit, + .owner = THIS_MODULE, +}; + +static int hisi_inno_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct hisi_inno_phy_priv *priv; + struct phy_provider *provider; + struct device_node *child; + struct resource *res; + int i = 0; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->mmio)) { + ret = PTR_ERR(priv->mmio); + return ret; + } + + priv->ref_clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->ref_clk)) + return PTR_ERR(priv->ref_clk); + + priv->por_rst = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->por_rst)) + return PTR_ERR(priv->por_rst); + + for_each_child_of_node(np, child) { + struct reset_control *rst; + struct phy *phy; + + rst = of_reset_control_get_exclusive(child, NULL); + if (IS_ERR(rst)) + return PTR_ERR(rst); + priv->ports[i].utmi_rst = rst; + priv->ports[i].priv = priv; + + phy = devm_phy_create(dev, child, &hisi_inno_phy_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_bus_width(phy, 8); + phy_set_drvdata(phy, &priv->ports[i]); + i++; + + if (i > INNO_PHY_PORT_NUM) { + dev_warn(dev, "Support %d ports in maximum\n", i); + break; + } + } + + provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(provider); +} + +static const struct of_device_id hisi_inno_phy_of_match[] = { + { .compatible = "hisilicon,inno-usb2-phy", }, + { .compatible = "hisilicon,hi3798cv200-usb2-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, hisi_inno_phy_of_match); + +static struct platform_driver hisi_inno_phy_driver = { + .probe = hisi_inno_phy_probe, + .driver = { + .name = "hisi-inno-phy", + .of_match_table = hisi_inno_phy_of_match, + } +}; +module_platform_driver(hisi_inno_phy_driver); + +MODULE_DESCRIPTION("HiSilicon INNO USB2 PHY Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/hisilicon/phy-histb-combphy.c b/drivers/phy/hisilicon/phy-histb-combphy.c new file mode 100644 index 000000000..5777b3120 --- /dev/null +++ b/drivers/phy/hisilicon/phy-histb-combphy.c @@ -0,0 +1,289 @@ +/* + * COMBPHY driver for HiSilicon STB SoCs + * + * Copyright (C) 2016-2017 HiSilicon Co., Ltd. http://www.hisilicon.com + * + * Authors: Jianguo Sun <sunjianguo1@huawei.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <dt-bindings/phy/phy.h> + +#define COMBPHY_MODE_PCIE 0 +#define COMBPHY_MODE_USB3 1 +#define COMBPHY_MODE_SATA 2 + +#define COMBPHY_CFG_REG 0x0 +#define COMBPHY_BYPASS_CODEC BIT(31) +#define COMBPHY_TEST_WRITE BIT(24) +#define COMBPHY_TEST_DATA_SHIFT 20 +#define COMBPHY_TEST_DATA_MASK GENMASK(23, 20) +#define COMBPHY_TEST_ADDR_SHIFT 12 +#define COMBPHY_TEST_ADDR_MASK GENMASK(16, 12) +#define COMBPHY_CLKREF_OUT_OEN BIT(0) + +struct histb_combphy_mode { + int fixed; + int select; + u32 reg; + u32 shift; + u32 mask; +}; + +struct histb_combphy_priv { + void __iomem *mmio; + struct regmap *syscon; + struct reset_control *por_rst; + struct clk *ref_clk; + struct phy *phy; + struct histb_combphy_mode mode; +}; + +static void nano_register_write(struct histb_combphy_priv *priv, + u32 addr, u32 data) +{ + void __iomem *reg = priv->mmio + COMBPHY_CFG_REG; + u32 val; + + /* Set up address and data for the write */ + val = readl(reg); + val &= ~COMBPHY_TEST_ADDR_MASK; + val |= addr << COMBPHY_TEST_ADDR_SHIFT; + val &= ~COMBPHY_TEST_DATA_MASK; + val |= data << COMBPHY_TEST_DATA_SHIFT; + writel(val, reg); + + /* Flip strobe control to trigger the write */ + val &= ~COMBPHY_TEST_WRITE; + writel(val, reg); + val |= COMBPHY_TEST_WRITE; + writel(val, reg); +} + +static int is_mode_fixed(struct histb_combphy_mode *mode) +{ + return (mode->fixed != PHY_NONE) ? true : false; +} + +static int histb_combphy_set_mode(struct histb_combphy_priv *priv) +{ + struct histb_combphy_mode *mode = &priv->mode; + struct regmap *syscon = priv->syscon; + u32 hw_sel; + + if (is_mode_fixed(mode)) + return 0; + + switch (mode->select) { + case PHY_TYPE_SATA: + hw_sel = COMBPHY_MODE_SATA; + break; + case PHY_TYPE_PCIE: + hw_sel = COMBPHY_MODE_PCIE; + break; + case PHY_TYPE_USB3: + hw_sel = COMBPHY_MODE_USB3; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(syscon, mode->reg, mode->mask, + hw_sel << mode->shift); +} + +static int histb_combphy_init(struct phy *phy) +{ + struct histb_combphy_priv *priv = phy_get_drvdata(phy); + u32 val; + int ret; + + ret = histb_combphy_set_mode(priv); + if (ret) + return ret; + + /* Clear bypass bit to enable encoding/decoding */ + val = readl(priv->mmio + COMBPHY_CFG_REG); + val &= ~COMBPHY_BYPASS_CODEC; + writel(val, priv->mmio + COMBPHY_CFG_REG); + + ret = clk_prepare_enable(priv->ref_clk); + if (ret) + return ret; + + reset_control_deassert(priv->por_rst); + + /* Enable EP clock */ + val = readl(priv->mmio + COMBPHY_CFG_REG); + val |= COMBPHY_CLKREF_OUT_OEN; + writel(val, priv->mmio + COMBPHY_CFG_REG); + + /* Need to wait for EP clock stable */ + mdelay(5); + + /* Configure nano phy registers as suggested by vendor */ + nano_register_write(priv, 0x1, 0x8); + nano_register_write(priv, 0xc, 0x9); + nano_register_write(priv, 0x1a, 0x4); + + return 0; +} + +static int histb_combphy_exit(struct phy *phy) +{ + struct histb_combphy_priv *priv = phy_get_drvdata(phy); + u32 val; + + /* Disable EP clock */ + val = readl(priv->mmio + COMBPHY_CFG_REG); + val &= ~COMBPHY_CLKREF_OUT_OEN; + writel(val, priv->mmio + COMBPHY_CFG_REG); + + reset_control_assert(priv->por_rst); + clk_disable_unprepare(priv->ref_clk); + + return 0; +} + +static const struct phy_ops histb_combphy_ops = { + .init = histb_combphy_init, + .exit = histb_combphy_exit, + .owner = THIS_MODULE, +}; + +static struct phy *histb_combphy_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct histb_combphy_priv *priv = dev_get_drvdata(dev); + struct histb_combphy_mode *mode = &priv->mode; + + if (args->args_count < 1) { + dev_err(dev, "invalid number of arguments\n"); + return ERR_PTR(-EINVAL); + } + + mode->select = args->args[0]; + + if (mode->select < PHY_TYPE_SATA || mode->select > PHY_TYPE_USB3) { + dev_err(dev, "invalid phy mode select argument\n"); + return ERR_PTR(-EINVAL); + } + + if (is_mode_fixed(mode) && mode->select != mode->fixed) { + dev_err(dev, "mode select %d mismatch fixed phy mode %d\n", + mode->select, mode->fixed); + return ERR_PTR(-EINVAL); + } + + return priv->phy; +} + +static int histb_combphy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct histb_combphy_priv *priv; + struct device_node *np = dev->of_node; + struct histb_combphy_mode *mode; + struct resource *res; + u32 vals[3]; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->mmio = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->mmio)) { + ret = PTR_ERR(priv->mmio); + return ret; + } + + priv->syscon = syscon_node_to_regmap(np->parent); + if (IS_ERR(priv->syscon)) { + dev_err(dev, "failed to find peri_ctrl syscon regmap\n"); + return PTR_ERR(priv->syscon); + } + + mode = &priv->mode; + mode->fixed = PHY_NONE; + + ret = of_property_read_u32(np, "hisilicon,fixed-mode", &mode->fixed); + if (ret == 0) + dev_dbg(dev, "found fixed phy mode %d\n", mode->fixed); + + ret = of_property_read_u32_array(np, "hisilicon,mode-select-bits", + vals, ARRAY_SIZE(vals)); + if (ret == 0) { + if (is_mode_fixed(mode)) { + dev_err(dev, "found select bits for fixed mode phy\n"); + return -EINVAL; + } + + mode->reg = vals[0]; + mode->shift = vals[1]; + mode->mask = vals[2]; + dev_dbg(dev, "found mode select bits\n"); + } else { + if (!is_mode_fixed(mode)) { + dev_err(dev, "no valid select bits found for non-fixed phy\n"); + return -ENODEV; + } + } + + priv->ref_clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->ref_clk)) { + dev_err(dev, "failed to find ref clock\n"); + return PTR_ERR(priv->ref_clk); + } + + priv->por_rst = devm_reset_control_get(dev, NULL); + if (IS_ERR(priv->por_rst)) { + dev_err(dev, "failed to get poweron reset\n"); + return PTR_ERR(priv->por_rst); + } + + priv->phy = devm_phy_create(dev, NULL, &histb_combphy_ops); + if (IS_ERR(priv->phy)) { + dev_err(dev, "failed to create combphy\n"); + return PTR_ERR(priv->phy); + } + + dev_set_drvdata(dev, priv); + phy_set_drvdata(priv->phy, priv); + + phy_provider = devm_of_phy_provider_register(dev, histb_combphy_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id histb_combphy_of_match[] = { + { .compatible = "hisilicon,hi3798cv200-combphy" }, + { }, +}; +MODULE_DEVICE_TABLE(of, histb_combphy_of_match); + +static struct platform_driver histb_combphy_driver = { + .probe = histb_combphy_probe, + .driver = { + .name = "combphy", + .of_match_table = histb_combphy_of_match, + }, +}; +module_platform_driver(histb_combphy_driver); + +MODULE_DESCRIPTION("HiSilicon STB COMBPHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/hisilicon/phy-hix5hd2-sata.c b/drivers/phy/hisilicon/phy-hix5hd2-sata.c new file mode 100644 index 000000000..e5ab3aa78 --- /dev/null +++ b/drivers/phy/hisilicon/phy-hix5hd2-sata.c @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2014 Linaro Ltd. + * Copyright (c) 2014 Hisilicon Limited. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define SATA_PHY0_CTLL 0xa0 +#define MPLL_MULTIPLIER_SHIFT 1 +#define MPLL_MULTIPLIER_MASK 0xfe +#define MPLL_MULTIPLIER_50M 0x3c +#define MPLL_MULTIPLIER_100M 0x1e +#define PHY_RESET BIT(0) +#define REF_SSP_EN BIT(9) +#define SSC_EN BIT(10) +#define REF_USE_PAD BIT(23) + +#define SATA_PORT_PHYCTL 0x174 +#define SPEED_MODE_MASK 0x6f0000 +#define HALF_RATE_SHIFT 16 +#define PHY_CONFIG_SHIFT 18 +#define GEN2_EN_SHIFT 21 +#define SPEED_CTRL BIT(20) + +#define SATA_PORT_PHYCTL1 0x148 +#define AMPLITUDE_MASK 0x3ffffe +#define AMPLITUDE_GEN3 0x68 +#define AMPLITUDE_GEN3_SHIFT 15 +#define AMPLITUDE_GEN2 0x56 +#define AMPLITUDE_GEN2_SHIFT 8 +#define AMPLITUDE_GEN1 0x56 +#define AMPLITUDE_GEN1_SHIFT 1 + +#define SATA_PORT_PHYCTL2 0x14c +#define PREEMPH_MASK 0x3ffff +#define PREEMPH_GEN3 0x20 +#define PREEMPH_GEN3_SHIFT 12 +#define PREEMPH_GEN2 0x15 +#define PREEMPH_GEN2_SHIFT 6 +#define PREEMPH_GEN1 0x5 +#define PREEMPH_GEN1_SHIFT 0 + +struct hix5hd2_priv { + void __iomem *base; + struct regmap *peri_ctrl; +}; + +enum phy_speed_mode { + SPEED_MODE_GEN1 = 0, + SPEED_MODE_GEN2 = 1, + SPEED_MODE_GEN3 = 2, +}; + +static int hix5hd2_sata_phy_init(struct phy *phy) +{ + struct hix5hd2_priv *priv = phy_get_drvdata(phy); + u32 val, data[2]; + int ret; + + if (priv->peri_ctrl) { + ret = of_property_read_u32_array(phy->dev.of_node, + "hisilicon,power-reg", + &data[0], 2); + if (ret) { + dev_err(&phy->dev, "Fail read hisilicon,power-reg\n"); + return ret; + } + + regmap_update_bits(priv->peri_ctrl, data[0], + BIT(data[1]), BIT(data[1])); + } + + /* reset phy */ + val = readl_relaxed(priv->base + SATA_PHY0_CTLL); + val &= ~(MPLL_MULTIPLIER_MASK | REF_USE_PAD); + val |= MPLL_MULTIPLIER_50M << MPLL_MULTIPLIER_SHIFT | + REF_SSP_EN | PHY_RESET; + writel_relaxed(val, priv->base + SATA_PHY0_CTLL); + msleep(20); + val &= ~PHY_RESET; + writel_relaxed(val, priv->base + SATA_PHY0_CTLL); + + val = readl_relaxed(priv->base + SATA_PORT_PHYCTL1); + val &= ~AMPLITUDE_MASK; + val |= AMPLITUDE_GEN3 << AMPLITUDE_GEN3_SHIFT | + AMPLITUDE_GEN2 << AMPLITUDE_GEN2_SHIFT | + AMPLITUDE_GEN1 << AMPLITUDE_GEN1_SHIFT; + writel_relaxed(val, priv->base + SATA_PORT_PHYCTL1); + + val = readl_relaxed(priv->base + SATA_PORT_PHYCTL2); + val &= ~PREEMPH_MASK; + val |= PREEMPH_GEN3 << PREEMPH_GEN3_SHIFT | + PREEMPH_GEN2 << PREEMPH_GEN2_SHIFT | + PREEMPH_GEN1 << PREEMPH_GEN1_SHIFT; + writel_relaxed(val, priv->base + SATA_PORT_PHYCTL2); + + /* ensure PHYCTRL setting takes effect */ + val = readl_relaxed(priv->base + SATA_PORT_PHYCTL); + val &= ~SPEED_MODE_MASK; + val |= SPEED_MODE_GEN1 << HALF_RATE_SHIFT | + SPEED_MODE_GEN1 << PHY_CONFIG_SHIFT | + SPEED_MODE_GEN1 << GEN2_EN_SHIFT | SPEED_CTRL; + writel_relaxed(val, priv->base + SATA_PORT_PHYCTL); + + msleep(20); + val &= ~SPEED_MODE_MASK; + val |= SPEED_MODE_GEN3 << HALF_RATE_SHIFT | + SPEED_MODE_GEN3 << PHY_CONFIG_SHIFT | + SPEED_MODE_GEN3 << GEN2_EN_SHIFT | SPEED_CTRL; + writel_relaxed(val, priv->base + SATA_PORT_PHYCTL); + + val &= ~(SPEED_MODE_MASK | SPEED_CTRL); + val |= SPEED_MODE_GEN2 << HALF_RATE_SHIFT | + SPEED_MODE_GEN2 << PHY_CONFIG_SHIFT | + SPEED_MODE_GEN2 << GEN2_EN_SHIFT; + writel_relaxed(val, priv->base + SATA_PORT_PHYCTL); + + return 0; +} + +static const struct phy_ops hix5hd2_sata_phy_ops = { + .init = hix5hd2_sata_phy_init, + .owner = THIS_MODULE, +}; + +static int hix5hd2_sata_phy_probe(struct platform_device *pdev) +{ + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct resource *res; + struct phy *phy; + struct hix5hd2_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + priv->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!priv->base) + return -ENOMEM; + + priv->peri_ctrl = syscon_regmap_lookup_by_phandle(dev->of_node, + "hisilicon,peripheral-syscon"); + if (IS_ERR(priv->peri_ctrl)) + priv->peri_ctrl = NULL; + + phy = devm_phy_create(dev, NULL, &hix5hd2_sata_phy_ops); + if (IS_ERR(phy)) { + dev_err(dev, "failed to create PHY\n"); + return PTR_ERR(phy); + } + + phy_set_drvdata(phy, priv); + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id hix5hd2_sata_phy_of_match[] = { + {.compatible = "hisilicon,hix5hd2-sata-phy",}, + { }, +}; +MODULE_DEVICE_TABLE(of, hix5hd2_sata_phy_of_match); + +static struct platform_driver hix5hd2_sata_phy_driver = { + .probe = hix5hd2_sata_phy_probe, + .driver = { + .name = "hix5hd2-sata-phy", + .of_match_table = hix5hd2_sata_phy_of_match, + } +}; +module_platform_driver(hix5hd2_sata_phy_driver); + +MODULE_AUTHOR("Jiancheng Xue <xuejiancheng@huawei.com>"); +MODULE_DESCRIPTION("HISILICON HIX5HD2 SATA PHY driver"); +MODULE_ALIAS("platform:hix5hd2-sata-phy"); +MODULE_LICENSE("GPL v2"); |