diff options
Diffstat (limited to 'drivers/phy/amlogic')
-rw-r--r-- | drivers/phy/amlogic/Kconfig | 107 | ||||
-rw-r--r-- | drivers/phy/amlogic/Makefile | 10 | ||||
-rw-r--r-- | drivers/phy/amlogic/phy-meson-axg-mipi-dphy.c | 412 | ||||
-rw-r--r-- | drivers/phy/amlogic/phy-meson-axg-mipi-pcie-analog.c | 256 | ||||
-rw-r--r-- | drivers/phy/amlogic/phy-meson-axg-pcie.c | 191 | ||||
-rw-r--r-- | drivers/phy/amlogic/phy-meson-g12a-mipi-dphy-analog.c | 172 | ||||
-rw-r--r-- | drivers/phy/amlogic/phy-meson-g12a-usb2.c | 383 | ||||
-rw-r--r-- | drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c | 447 | ||||
-rw-r--r-- | drivers/phy/amlogic/phy-meson-gxl-usb2.c | 301 | ||||
-rw-r--r-- | drivers/phy/amlogic/phy-meson8-hdmi-tx.c | 160 | ||||
-rw-r--r-- | drivers/phy/amlogic/phy-meson8b-usb2.c | 337 |
11 files changed, 2776 insertions, 0 deletions
diff --git a/drivers/phy/amlogic/Kconfig b/drivers/phy/amlogic/Kconfig new file mode 100644 index 0000000000..ce7ba3eb2a --- /dev/null +++ b/drivers/phy/amlogic/Kconfig @@ -0,0 +1,107 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Phy drivers for Amlogic platforms +# +config PHY_MESON8_HDMI_TX + tristate "Meson8, Meson8b and Meson8m2 HDMI TX PHY driver" + depends on (ARCH_MESON && ARM) || COMPILE_TEST + depends on OF + select MFD_SYSCON + help + Enable this to support the HDMI TX PHYs found in Meson8, + Meson8b and Meson8m2 SoCs. + If unsure, say N. + +config PHY_MESON8B_USB2 + tristate "Meson8, Meson8b, Meson8m2 and GXBB USB2 PHY driver" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + depends on USB_SUPPORT + select USB_COMMON + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to support the Meson USB2 PHYs found in Meson8, + Meson8b and GXBB SoCs. + If unsure, say N. + +config PHY_MESON_GXL_USB2 + tristate "Meson GXL and GXM USB2 PHY drivers" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + depends on USB_SUPPORT + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to support the Meson USB2 PHYs found in Meson + GXL and GXM SoCs. + If unsure, say N. + +config PHY_MESON_G12A_MIPI_DPHY_ANALOG + tristate "Meson G12A MIPI Analog DPHY driver" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + select GENERIC_PHY + select MFD_SYSCON + select GENERIC_PHY_MIPI_DPHY + help + Enable this to support the Meson MIPI Analog DPHY found in Meson G12A + SoCs. + If unsure, say N. + +config PHY_MESON_G12A_USB2 + tristate "Meson G12A USB2 PHY driver" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to support the Meson USB2 PHYs found in Meson + G12A SoCs. + If unsure, say N. + +config PHY_MESON_G12A_USB3_PCIE + tristate "Meson G12A USB3+PCIE Combo PHY driver" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to support the Meson USB3 + PCIE Combo PHY found + in Meson G12A SoCs. + If unsure, say N. + +config PHY_MESON_AXG_PCIE + tristate "Meson AXG PCIE PHY driver" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + select GENERIC_PHY + select REGMAP_MMIO + help + Enable this to support the Meson MIPI + PCIE PHY found + in Meson AXG SoCs. + If unsure, say N. + +config PHY_MESON_AXG_MIPI_PCIE_ANALOG + tristate "Meson AXG MIPI + PCIE analog PHY driver" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + select GENERIC_PHY + select REGMAP_MMIO + select GENERIC_PHY_MIPI_DPHY + help + Enable this to support the Meson MIPI + PCIE analog PHY + found in Meson AXG SoCs. + If unsure, say N. + +config PHY_MESON_AXG_MIPI_DPHY + tristate "Meson AXG MIPI DPHY driver" + default ARCH_MESON + depends on OF && (ARCH_MESON || COMPILE_TEST) + select GENERIC_PHY + select REGMAP_MMIO + select GENERIC_PHY_MIPI_DPHY + help + Enable this to support the Meson MIPI DPHY found in Meson AXG + SoCs. + If unsure, say N. diff --git a/drivers/phy/amlogic/Makefile b/drivers/phy/amlogic/Makefile new file mode 100644 index 0000000000..91e3b9790c --- /dev/null +++ b/drivers/phy/amlogic/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_PHY_MESON8_HDMI_TX) += phy-meson8-hdmi-tx.o +obj-$(CONFIG_PHY_MESON8B_USB2) += phy-meson8b-usb2.o +obj-$(CONFIG_PHY_MESON_GXL_USB2) += phy-meson-gxl-usb2.o +obj-$(CONFIG_PHY_MESON_G12A_USB2) += phy-meson-g12a-usb2.o +obj-$(CONFIG_PHY_MESON_G12A_USB3_PCIE) += phy-meson-g12a-usb3-pcie.o +obj-$(CONFIG_PHY_MESON_G12A_MIPI_DPHY_ANALOG) += phy-meson-g12a-mipi-dphy-analog.o +obj-$(CONFIG_PHY_MESON_AXG_PCIE) += phy-meson-axg-pcie.o +obj-$(CONFIG_PHY_MESON_AXG_MIPI_PCIE_ANALOG) += phy-meson-axg-mipi-pcie-analog.o +obj-$(CONFIG_PHY_MESON_AXG_MIPI_DPHY) += phy-meson-axg-mipi-dphy.o diff --git a/drivers/phy/amlogic/phy-meson-axg-mipi-dphy.c b/drivers/phy/amlogic/phy-meson-axg-mipi-dphy.c new file mode 100644 index 0000000000..08a86962d9 --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-axg-mipi-dphy.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Meson AXG MIPI DPHY driver + * + * Copyright (C) 2018 Amlogic, Inc. All rights reserved + * Copyright (C) 2020 BayLibre, SAS + * Author: Neil Armstrong <narmstrong@baylibre.com> + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +/* [31] soft reset for the phy. + * 1: reset. 0: dessert the reset. + * [30] clock lane soft reset. + * [29] data byte lane 3 soft reset. + * [28] data byte lane 2 soft reset. + * [27] data byte lane 1 soft reset. + * [26] data byte lane 0 soft reset. + * [25] mipi dsi pll clock selection. + * 1: clock from fixed 850Mhz clock source. 0: from VID2 PLL. + * [12] mipi HSbyteclk enable. + * [11] mipi divider clk selection. + * 1: select the mipi DDRCLKHS from clock divider. + * 0: from PLL clock. + * [10] mipi clock divider control. + * 1: /4. 0: /2. + * [9] mipi divider output enable. + * [8] mipi divider counter enable. + * [7] PLL clock enable. + * [5] LPDT data endian. + * 1 = transfer the high bit first. 0 : transfer the low bit first. + * [4] HS data endian. + * [3] force data byte lane in stop mode. + * [2] force data byte lane 0 in receiver mode. + * [1] write 1 to sync the txclkesc input. the internal logic have to + * use txclkesc to decide Txvalid and Txready. + * [0] enalbe the MIPI DPHY TxDDRClk. + */ +#define MIPI_DSI_PHY_CTRL 0x0 + +/* [31] clk lane tx_hs_en control selection. + * 1: from register. 0: use clk lane state machine. + * [30] register bit for clock lane tx_hs_en. + * [29] clk lane tx_lp_en contrl selection. + * 1: from register. 0: from clk lane state machine. + * [28] register bit for clock lane tx_lp_en. + * [27] chan0 tx_hs_en control selection. + * 1: from register. 0: from chan0 state machine. + * [26] register bit for chan0 tx_hs_en. + * [25] chan0 tx_lp_en control selection. + * 1: from register. 0: from chan0 state machine. + * [24] register bit from chan0 tx_lp_en. + * [23] chan0 rx_lp_en control selection. + * 1: from register. 0: from chan0 state machine. + * [22] register bit from chan0 rx_lp_en. + * [21] chan0 contention detection enable control selection. + * 1: from register. 0: from chan0 state machine. + * [20] register bit from chan0 contention dectection enable. + * [19] chan1 tx_hs_en control selection. + * 1: from register. 0: from chan0 state machine. + * [18] register bit for chan1 tx_hs_en. + * [17] chan1 tx_lp_en control selection. + * 1: from register. 0: from chan0 state machine. + * [16] register bit from chan1 tx_lp_en. + * [15] chan2 tx_hs_en control selection. + * 1: from register. 0: from chan0 state machine. + * [14] register bit for chan2 tx_hs_en. + * [13] chan2 tx_lp_en control selection. + * 1: from register. 0: from chan0 state machine. + * [12] register bit from chan2 tx_lp_en. + * [11] chan3 tx_hs_en control selection. + * 1: from register. 0: from chan0 state machine. + * [10] register bit for chan3 tx_hs_en. + * [9] chan3 tx_lp_en control selection. + * 1: from register. 0: from chan0 state machine. + * [8] register bit from chan3 tx_lp_en. + * [4] clk chan power down. this bit is also used as the power down + * of the whole MIPI_DSI_PHY. + * [3] chan3 power down. + * [2] chan2 power down. + * [1] chan1 power down. + * [0] chan0 power down. + */ +#define MIPI_DSI_CHAN_CTRL 0x4 + +/* [24] rx turn watch dog triggered. + * [23] rx esc watchdog triggered. + * [22] mbias ready. + * [21] txclkesc synced and ready. + * [20:17] clk lane state. {mbias_ready, tx_stop, tx_ulps, tx_hs_active} + * [16:13] chan3 state{0, tx_stop, tx_ulps, tx_hs_active} + * [12:9] chan2 state.{0, tx_stop, tx_ulps, tx_hs_active} + * [8:5] chan1 state. {0, tx_stop, tx_ulps, tx_hs_active} + * [4:0] chan0 state. {TX_STOP, tx_ULPS, hs_active, direction, rxulpsesc} + */ +#define MIPI_DSI_CHAN_STS 0x8 + +/* [31:24] TCLK_PREPARE. + * [23:16] TCLK_ZERO. + * [15:8] TCLK_POST. + * [7:0] TCLK_TRAIL. + */ +#define MIPI_DSI_CLK_TIM 0xc + +/* [31:24] THS_PREPARE. + * [23:16] THS_ZERO. + * [15:8] THS_TRAIL. + * [7:0] THS_EXIT. + */ +#define MIPI_DSI_HS_TIM 0x10 + +/* [31:24] tTA_GET. + * [23:16] tTA_GO. + * [15:8] tTA_SURE. + * [7:0] tLPX. + */ +#define MIPI_DSI_LP_TIM 0x14 + +/* wait time to MIPI DIS analog ready. */ +#define MIPI_DSI_ANA_UP_TIM 0x18 + +/* TINIT. */ +#define MIPI_DSI_INIT_TIM 0x1c + +/* TWAKEUP. */ +#define MIPI_DSI_WAKEUP_TIM 0x20 + +/* when in RxULPS check state, after the logic enable the analog, + * how long we should wait to check the lP state . + */ +#define MIPI_DSI_LPOK_TIM 0x24 + +/* Watchdog for RX low power state no finished. */ +#define MIPI_DSI_LP_WCHDOG 0x28 + +/* tMBIAS, after send power up signals to analog, + * how long we should wait for analog powered up. + */ +#define MIPI_DSI_ANA_CTRL 0x2c + +/* [31:8] reserved for future. + * [7:0] tCLK_PRE. + */ +#define MIPI_DSI_CLK_TIM1 0x30 + +/* watchdog for turn around waiting time. */ +#define MIPI_DSI_TURN_WCHDOG 0x34 + +/* When in RxULPS state, how frequency we should to check + * if the TX side out of ULPS state. + */ +#define MIPI_DSI_ULPS_CHECK 0x38 +#define MIPI_DSI_TEST_CTRL0 0x3c +#define MIPI_DSI_TEST_CTRL1 0x40 + +struct phy_meson_axg_mipi_dphy_priv { + struct device *dev; + struct regmap *regmap; + struct clk *clk; + struct reset_control *reset; + struct phy *analog; + struct phy_configure_opts_mipi_dphy config; +}; + +static const struct regmap_config phy_meson_axg_mipi_dphy_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = MIPI_DSI_TEST_CTRL1, +}; + +static int phy_meson_axg_mipi_dphy_init(struct phy *phy) +{ + struct phy_meson_axg_mipi_dphy_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = phy_init(priv->analog); + if (ret) + return ret; + + ret = reset_control_reset(priv->reset); + if (ret) + return ret; + + return 0; +} + +static int phy_meson_axg_mipi_dphy_configure(struct phy *phy, + union phy_configure_opts *opts) +{ + struct phy_meson_axg_mipi_dphy_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy); + if (ret) + return ret; + + ret = phy_configure(priv->analog, opts); + if (ret) + return ret; + + memcpy(&priv->config, opts, sizeof(priv->config)); + + return 0; +} + +static int phy_meson_axg_mipi_dphy_power_on(struct phy *phy) +{ + struct phy_meson_axg_mipi_dphy_priv *priv = phy_get_drvdata(phy); + int ret; + unsigned long temp; + + ret = phy_power_on(priv->analog); + if (ret) + return ret; + + /* enable phy clock */ + regmap_write(priv->regmap, MIPI_DSI_PHY_CTRL, 0x1); + regmap_write(priv->regmap, MIPI_DSI_PHY_CTRL, + BIT(0) | /* enable the DSI PLL clock . */ + BIT(7) | /* enable pll clock which connected to DDR clock path */ + BIT(8)); /* enable the clock divider counter */ + + /* enable the divider clock out */ + regmap_update_bits(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(9), BIT(9)); + + /* enable the byte clock generation. */ + regmap_update_bits(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(12), BIT(12)); + regmap_update_bits(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(31), BIT(31)); + regmap_update_bits(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(31), 0); + + /* Calculate lanebyteclk period in ps */ + temp = (1000000 * 100) / (priv->config.hs_clk_rate / 1000); + temp = temp * 8 * 10; + + regmap_write(priv->regmap, MIPI_DSI_CLK_TIM, + DIV_ROUND_UP(priv->config.clk_trail, temp) | + (DIV_ROUND_UP(priv->config.clk_post + + priv->config.hs_trail, temp) << 8) | + (DIV_ROUND_UP(priv->config.clk_zero, temp) << 16) | + (DIV_ROUND_UP(priv->config.clk_prepare, temp) << 24)); + regmap_write(priv->regmap, MIPI_DSI_CLK_TIM1, + DIV_ROUND_UP(priv->config.clk_pre, BITS_PER_BYTE)); + + regmap_write(priv->regmap, MIPI_DSI_HS_TIM, + DIV_ROUND_UP(priv->config.hs_exit, temp) | + (DIV_ROUND_UP(priv->config.hs_trail, temp) << 8) | + (DIV_ROUND_UP(priv->config.hs_zero, temp) << 16) | + (DIV_ROUND_UP(priv->config.hs_prepare, temp) << 24)); + + regmap_write(priv->regmap, MIPI_DSI_LP_TIM, + DIV_ROUND_UP(priv->config.lpx, temp) | + (DIV_ROUND_UP(priv->config.ta_sure, temp) << 8) | + (DIV_ROUND_UP(priv->config.ta_go, temp) << 16) | + (DIV_ROUND_UP(priv->config.ta_get, temp) << 24)); + + regmap_write(priv->regmap, MIPI_DSI_ANA_UP_TIM, 0x0100); + regmap_write(priv->regmap, MIPI_DSI_INIT_TIM, + DIV_ROUND_UP(priv->config.init * NSEC_PER_MSEC, temp)); + regmap_write(priv->regmap, MIPI_DSI_WAKEUP_TIM, + DIV_ROUND_UP(priv->config.wakeup * NSEC_PER_MSEC, temp)); + regmap_write(priv->regmap, MIPI_DSI_LPOK_TIM, 0x7C); + regmap_write(priv->regmap, MIPI_DSI_ULPS_CHECK, 0x927C); + regmap_write(priv->regmap, MIPI_DSI_LP_WCHDOG, 0x1000); + regmap_write(priv->regmap, MIPI_DSI_TURN_WCHDOG, 0x1000); + + /* Powerup the analog circuit */ + switch (priv->config.lanes) { + case 1: + regmap_write(priv->regmap, MIPI_DSI_CHAN_CTRL, 0xe); + break; + case 2: + regmap_write(priv->regmap, MIPI_DSI_CHAN_CTRL, 0xc); + break; + case 3: + regmap_write(priv->regmap, MIPI_DSI_CHAN_CTRL, 0x8); + break; + case 4: + default: + regmap_write(priv->regmap, MIPI_DSI_CHAN_CTRL, 0); + break; + } + + /* Trigger a sync active for esc_clk */ + regmap_update_bits(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(1), BIT(1)); + + return 0; +} + +static int phy_meson_axg_mipi_dphy_power_off(struct phy *phy) +{ + struct phy_meson_axg_mipi_dphy_priv *priv = phy_get_drvdata(phy); + + regmap_write(priv->regmap, MIPI_DSI_CHAN_CTRL, 0xf); + regmap_write(priv->regmap, MIPI_DSI_PHY_CTRL, BIT(31)); + + phy_power_off(priv->analog); + + return 0; +} + +static int phy_meson_axg_mipi_dphy_exit(struct phy *phy) +{ + struct phy_meson_axg_mipi_dphy_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = phy_exit(priv->analog); + if (ret) + return ret; + + return reset_control_reset(priv->reset); +} + +static const struct phy_ops phy_meson_axg_mipi_dphy_ops = { + .configure = phy_meson_axg_mipi_dphy_configure, + .init = phy_meson_axg_mipi_dphy_init, + .exit = phy_meson_axg_mipi_dphy_exit, + .power_on = phy_meson_axg_mipi_dphy_power_on, + .power_off = phy_meson_axg_mipi_dphy_power_off, + .owner = THIS_MODULE, +}; + +static int phy_meson_axg_mipi_dphy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *phy_provider; + struct phy_meson_axg_mipi_dphy_priv *priv; + struct phy *phy; + void __iomem *base; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + platform_set_drvdata(pdev, priv); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(dev, base, + &phy_meson_axg_mipi_dphy_regmap_conf); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->clk = devm_clk_get(dev, "pclk"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->reset = devm_reset_control_get(dev, "phy"); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + priv->analog = devm_phy_get(dev, "analog"); + if (IS_ERR(priv->analog)) + return PTR_ERR(priv->analog); + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + ret = reset_control_deassert(priv->reset); + if (ret) + return ret; + + phy = devm_phy_create(dev, NULL, &phy_meson_axg_mipi_dphy_ops); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to create PHY\n"); + + return ret; + } + + 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 phy_meson_axg_mipi_dphy_of_match[] = { + { .compatible = "amlogic,axg-mipi-dphy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_meson_axg_mipi_dphy_of_match); + +static struct platform_driver phy_meson_axg_mipi_dphy_driver = { + .probe = phy_meson_axg_mipi_dphy_probe, + .driver = { + .name = "phy-meson-axg-mipi-dphy", + .of_match_table = phy_meson_axg_mipi_dphy_of_match, + }, +}; +module_platform_driver(phy_meson_axg_mipi_dphy_driver); + +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_DESCRIPTION("Meson AXG MIPI DPHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/amlogic/phy-meson-axg-mipi-pcie-analog.c b/drivers/phy/amlogic/phy-meson-axg-mipi-pcie-analog.c new file mode 100644 index 0000000000..ae898f93f9 --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-axg-mipi-pcie-analog.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Amlogic AXG MIPI + PCIE analog PHY driver + * + * Copyright (C) 2019 Remi Pommarel <repk@triplefau.lt> + */ +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include <linux/delay.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <dt-bindings/phy/phy.h> + +#define HHI_MIPI_CNTL0 0x00 +#define HHI_MIPI_CNTL0_COMMON_BLOCK GENMASK(31, 28) +#define HHI_MIPI_CNTL0_ENABLE BIT(29) +#define HHI_MIPI_CNTL0_BANDGAP BIT(26) +#define HHI_MIPI_CNTL0_DIF_REF_CTL1 GENMASK(25, 16) +#define HHI_MIPI_CNTL0_DIF_REF_CTL0 GENMASK(15, 0) + +#define HHI_MIPI_CNTL1 0x04 +#define HHI_MIPI_CNTL1_CH0_CML_PDR_EN BIT(12) +#define HHI_MIPI_CNTL1_LP_ABILITY GENMASK(5, 4) +#define HHI_MIPI_CNTL1_LP_RESISTER BIT(3) +#define HHI_MIPI_CNTL1_INPUT_SETTING BIT(2) +#define HHI_MIPI_CNTL1_INPUT_SEL BIT(1) +#define HHI_MIPI_CNTL1_PRBS7_EN BIT(0) + +#define HHI_MIPI_CNTL2 0x08 +#define HHI_MIPI_CNTL2_CH_PU GENMASK(31, 25) +#define HHI_MIPI_CNTL2_CH_CTL GENMASK(24, 19) +#define HHI_MIPI_CNTL2_CH0_DIGDR_EN BIT(18) +#define HHI_MIPI_CNTL2_CH_DIGDR_EN BIT(17) +#define HHI_MIPI_CNTL2_LPULPS_EN BIT(16) +#define HHI_MIPI_CNTL2_CH_EN GENMASK(15, 11) +#define HHI_MIPI_CNTL2_CH0_LP_CTL GENMASK(10, 1) + +#define DSI_LANE_0 BIT(4) +#define DSI_LANE_1 BIT(3) +#define DSI_LANE_CLK BIT(2) +#define DSI_LANE_2 BIT(1) +#define DSI_LANE_3 BIT(0) + +struct phy_axg_mipi_pcie_analog_priv { + struct phy *phy; + struct regmap *regmap; + bool dsi_configured; + bool dsi_enabled; + bool powered; + struct phy_configure_opts_mipi_dphy config; +}; + +static void phy_bandgap_enable(struct phy_axg_mipi_pcie_analog_priv *priv) +{ + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0, + HHI_MIPI_CNTL0_BANDGAP, HHI_MIPI_CNTL0_BANDGAP); + + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0, + HHI_MIPI_CNTL0_ENABLE, HHI_MIPI_CNTL0_ENABLE); +} + +static void phy_bandgap_disable(struct phy_axg_mipi_pcie_analog_priv *priv) +{ + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0, + HHI_MIPI_CNTL0_BANDGAP, 0); + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0, + HHI_MIPI_CNTL0_ENABLE, 0); +} + +static void phy_dsi_analog_enable(struct phy_axg_mipi_pcie_analog_priv *priv) +{ + u32 reg; + + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0, + HHI_MIPI_CNTL0_DIF_REF_CTL1, + FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL1, 0x1b8)); + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0, + BIT(31), BIT(31)); + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0, + HHI_MIPI_CNTL0_DIF_REF_CTL0, + FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL0, 0x8)); + + regmap_write(priv->regmap, HHI_MIPI_CNTL1, 0x001e); + + regmap_write(priv->regmap, HHI_MIPI_CNTL2, + (0x26e0 << 16) | (0x459 << 0)); + + reg = DSI_LANE_CLK; + switch (priv->config.lanes) { + case 4: + reg |= DSI_LANE_3; + fallthrough; + case 3: + reg |= DSI_LANE_2; + fallthrough; + case 2: + reg |= DSI_LANE_1; + fallthrough; + case 1: + reg |= DSI_LANE_0; + break; + default: + reg = 0; + } + + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL2, + HHI_MIPI_CNTL2_CH_EN, + FIELD_PREP(HHI_MIPI_CNTL2_CH_EN, reg)); + + priv->dsi_enabled = true; +} + +static void phy_dsi_analog_disable(struct phy_axg_mipi_pcie_analog_priv *priv) +{ + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0, + HHI_MIPI_CNTL0_DIF_REF_CTL1, + FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL1, 0)); + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0, BIT(31), 0); + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL0, + HHI_MIPI_CNTL0_DIF_REF_CTL1, 0); + + regmap_write(priv->regmap, HHI_MIPI_CNTL1, 0x6); + + regmap_write(priv->regmap, HHI_MIPI_CNTL2, 0x00200000); + + priv->dsi_enabled = false; +} + +static int phy_axg_mipi_pcie_analog_configure(struct phy *phy, + union phy_configure_opts *opts) +{ + struct phy_axg_mipi_pcie_analog_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy); + if (ret) + return ret; + + memcpy(&priv->config, opts, sizeof(priv->config)); + + priv->dsi_configured = true; + + /* If PHY was already powered on, setup the DSI analog part */ + if (priv->powered) { + /* If reconfiguring, disable & reconfigure */ + if (priv->dsi_enabled) + phy_dsi_analog_disable(priv); + + usleep_range(100, 200); + + phy_dsi_analog_enable(priv); + } + + return 0; +} + +static int phy_axg_mipi_pcie_analog_power_on(struct phy *phy) +{ + struct phy_axg_mipi_pcie_analog_priv *priv = phy_get_drvdata(phy); + + phy_bandgap_enable(priv); + + if (priv->dsi_configured) + phy_dsi_analog_enable(priv); + + priv->powered = true; + + return 0; +} + +static int phy_axg_mipi_pcie_analog_power_off(struct phy *phy) +{ + struct phy_axg_mipi_pcie_analog_priv *priv = phy_get_drvdata(phy); + + phy_bandgap_disable(priv); + + if (priv->dsi_enabled) + phy_dsi_analog_disable(priv); + + priv->powered = false; + + return 0; +} + +static const struct phy_ops phy_axg_mipi_pcie_analog_ops = { + .configure = phy_axg_mipi_pcie_analog_configure, + .power_on = phy_axg_mipi_pcie_analog_power_on, + .power_off = phy_axg_mipi_pcie_analog_power_off, + .owner = THIS_MODULE, +}; + +static int phy_axg_mipi_pcie_analog_probe(struct platform_device *pdev) +{ + struct phy_provider *phy; + struct device *dev = &pdev->dev; + struct phy_axg_mipi_pcie_analog_priv *priv; + struct device_node *np = dev->of_node, *parent_np; + struct regmap *map; + int ret; + + priv = devm_kmalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Get the hhi system controller node */ + parent_np = of_get_parent(dev->of_node); + map = syscon_node_to_regmap(parent_np); + of_node_put(parent_np); + if (IS_ERR(map)) { + dev_err(dev, + "failed to get HHI regmap\n"); + return PTR_ERR(map); + } + + priv->regmap = map; + + priv->phy = devm_phy_create(dev, np, &phy_axg_mipi_pcie_analog_ops); + if (IS_ERR(priv->phy)) { + ret = PTR_ERR(priv->phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to create PHY\n"); + return ret; + } + + phy_set_drvdata(priv->phy, priv); + dev_set_drvdata(dev, priv); + + phy = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy); +} + +static const struct of_device_id phy_axg_mipi_pcie_analog_of_match[] = { + { + .compatible = "amlogic,axg-mipi-pcie-analog-phy", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_axg_mipi_pcie_analog_of_match); + +static struct platform_driver phy_axg_mipi_pcie_analog_driver = { + .probe = phy_axg_mipi_pcie_analog_probe, + .driver = { + .name = "phy-axg-mipi-pcie-analog", + .of_match_table = phy_axg_mipi_pcie_analog_of_match, + }, +}; +module_platform_driver(phy_axg_mipi_pcie_analog_driver); + +MODULE_AUTHOR("Remi Pommarel <repk@triplefau.lt>"); +MODULE_DESCRIPTION("Amlogic AXG MIPI + PCIE analog PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/amlogic/phy-meson-axg-pcie.c b/drivers/phy/amlogic/phy-meson-axg-pcie.c new file mode 100644 index 0000000000..60be5cdc60 --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-axg-pcie.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Amlogic AXG PCIE PHY driver + * + * Copyright (C) 2020 Remi Pommarel <repk@triplefau.lt> + */ +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/platform_device.h> +#include <linux/bitfield.h> +#include <dt-bindings/phy/phy.h> + +#define MESON_PCIE_REG0 0x00 +#define MESON_PCIE_COMMON_CLK BIT(4) +#define MESON_PCIE_PORT_SEL GENMASK(3, 2) +#define MESON_PCIE_CLK BIT(1) +#define MESON_PCIE_POWERDOWN BIT(0) + +#define MESON_PCIE_TWO_X1 FIELD_PREP(MESON_PCIE_PORT_SEL, 0x3) +#define MESON_PCIE_COMMON_REF_CLK FIELD_PREP(MESON_PCIE_COMMON_CLK, 0x1) +#define MESON_PCIE_PHY_INIT (MESON_PCIE_TWO_X1 | \ + MESON_PCIE_COMMON_REF_CLK) +#define MESON_PCIE_RESET_DELAY 500 + +struct phy_axg_pcie_priv { + struct phy *phy; + struct phy *analog; + struct regmap *regmap; + struct reset_control *reset; +}; + +static const struct regmap_config phy_axg_pcie_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = MESON_PCIE_REG0, +}; + +static int phy_axg_pcie_power_on(struct phy *phy) +{ + struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = phy_power_on(priv->analog); + if (ret != 0) + return ret; + + regmap_update_bits(priv->regmap, MESON_PCIE_REG0, + MESON_PCIE_POWERDOWN, 0); + return 0; +} + +static int phy_axg_pcie_power_off(struct phy *phy) +{ + struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = phy_power_off(priv->analog); + if (ret != 0) + return ret; + + regmap_update_bits(priv->regmap, MESON_PCIE_REG0, + MESON_PCIE_POWERDOWN, 1); + return 0; +} + +static int phy_axg_pcie_init(struct phy *phy) +{ + struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = phy_init(priv->analog); + if (ret != 0) + return ret; + + regmap_write(priv->regmap, MESON_PCIE_REG0, MESON_PCIE_PHY_INIT); + return reset_control_reset(priv->reset); +} + +static int phy_axg_pcie_exit(struct phy *phy) +{ + struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = phy_exit(priv->analog); + if (ret != 0) + return ret; + + return reset_control_reset(priv->reset); +} + +static int phy_axg_pcie_reset(struct phy *phy) +{ + struct phy_axg_pcie_priv *priv = phy_get_drvdata(phy); + int ret = 0; + + ret = phy_reset(priv->analog); + if (ret != 0) + goto out; + + ret = reset_control_assert(priv->reset); + if (ret != 0) + goto out; + udelay(MESON_PCIE_RESET_DELAY); + + ret = reset_control_deassert(priv->reset); + if (ret != 0) + goto out; + udelay(MESON_PCIE_RESET_DELAY); + +out: + return ret; +} + +static const struct phy_ops phy_axg_pcie_ops = { + .init = phy_axg_pcie_init, + .exit = phy_axg_pcie_exit, + .power_on = phy_axg_pcie_power_on, + .power_off = phy_axg_pcie_power_off, + .reset = phy_axg_pcie_reset, + .owner = THIS_MODULE, +}; + +static int phy_axg_pcie_probe(struct platform_device *pdev) +{ + struct phy_provider *pphy; + struct device *dev = &pdev->dev; + struct phy_axg_pcie_priv *priv; + struct device_node *np = dev->of_node; + void __iomem *base; + int ret; + + priv = devm_kmalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->phy = devm_phy_create(dev, np, &phy_axg_pcie_ops); + if (IS_ERR(priv->phy)) { + ret = PTR_ERR(priv->phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to create PHY\n"); + return ret; + } + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(dev, base, + &phy_axg_pcie_regmap_conf); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->reset = devm_reset_control_array_get_exclusive(dev); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + priv->analog = devm_phy_get(dev, "analog"); + if (IS_ERR(priv->analog)) + return PTR_ERR(priv->analog); + + phy_set_drvdata(priv->phy, priv); + dev_set_drvdata(dev, priv); + pphy = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(pphy); +} + +static const struct of_device_id phy_axg_pcie_of_match[] = { + { + .compatible = "amlogic,axg-pcie-phy", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_axg_pcie_of_match); + +static struct platform_driver phy_axg_pcie_driver = { + .probe = phy_axg_pcie_probe, + .driver = { + .name = "phy-axg-pcie", + .of_match_table = phy_axg_pcie_of_match, + }, +}; +module_platform_driver(phy_axg_pcie_driver); + +MODULE_AUTHOR("Remi Pommarel <repk@triplefau.lt>"); +MODULE_DESCRIPTION("Amlogic AXG PCIE PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/amlogic/phy-meson-g12a-mipi-dphy-analog.c b/drivers/phy/amlogic/phy-meson-g12a-mipi-dphy-analog.c new file mode 100644 index 0000000000..46e5f7e7eb --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-g12a-mipi-dphy-analog.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Meson G12A MIPI DSI Analog PHY + * + * Copyright (C) 2018 Amlogic, Inc. All rights reserved + * Copyright (C) 2022 BayLibre, SAS + * Author: Neil Armstrong <narmstrong@baylibre.com> + */ +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/module.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include <linux/delay.h> +#include <linux/mfd/syscon.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <dt-bindings/phy/phy.h> + +#define HHI_MIPI_CNTL0 0x00 +#define HHI_MIPI_CNTL0_DIF_REF_CTL1 GENMASK(31, 16) +#define HHI_MIPI_CNTL0_DIF_REF_CTL0 GENMASK(15, 0) + +#define HHI_MIPI_CNTL1 0x04 +#define HHI_MIPI_CNTL1_BANDGAP BIT(16) +#define HHI_MIPI_CNTL2_DIF_REF_CTL2 GENMASK(15, 0) + +#define HHI_MIPI_CNTL2 0x08 +#define HHI_MIPI_CNTL2_DIF_TX_CTL1 GENMASK(31, 16) +#define HHI_MIPI_CNTL2_CH_EN GENMASK(15, 11) +#define HHI_MIPI_CNTL2_DIF_TX_CTL0 GENMASK(10, 0) + +#define DSI_LANE_0 BIT(4) +#define DSI_LANE_1 BIT(3) +#define DSI_LANE_CLK BIT(2) +#define DSI_LANE_2 BIT(1) +#define DSI_LANE_3 BIT(0) + +struct phy_g12a_mipi_dphy_analog_priv { + struct phy *phy; + struct regmap *regmap; + struct phy_configure_opts_mipi_dphy config; +}; + +static int phy_g12a_mipi_dphy_analog_configure(struct phy *phy, + union phy_configure_opts *opts) +{ + struct phy_g12a_mipi_dphy_analog_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = phy_mipi_dphy_config_validate(&opts->mipi_dphy); + if (ret) + return ret; + + memcpy(&priv->config, opts, sizeof(priv->config)); + + return 0; +} + +static int phy_g12a_mipi_dphy_analog_power_on(struct phy *phy) +{ + struct phy_g12a_mipi_dphy_analog_priv *priv = phy_get_drvdata(phy); + unsigned int reg; + + regmap_write(priv->regmap, HHI_MIPI_CNTL0, + FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL0, 0x8) | + FIELD_PREP(HHI_MIPI_CNTL0_DIF_REF_CTL1, 0xa487)); + + regmap_write(priv->regmap, HHI_MIPI_CNTL1, + FIELD_PREP(HHI_MIPI_CNTL2_DIF_REF_CTL2, 0x2e) | + HHI_MIPI_CNTL1_BANDGAP); + + regmap_write(priv->regmap, HHI_MIPI_CNTL2, + FIELD_PREP(HHI_MIPI_CNTL2_DIF_TX_CTL0, 0x45a) | + FIELD_PREP(HHI_MIPI_CNTL2_DIF_TX_CTL1, 0x2680)); + + reg = DSI_LANE_CLK; + switch (priv->config.lanes) { + case 4: + reg |= DSI_LANE_3; + fallthrough; + case 3: + reg |= DSI_LANE_2; + fallthrough; + case 2: + reg |= DSI_LANE_1; + fallthrough; + case 1: + reg |= DSI_LANE_0; + break; + default: + reg = 0; + } + + regmap_update_bits(priv->regmap, HHI_MIPI_CNTL2, + HHI_MIPI_CNTL2_CH_EN, + FIELD_PREP(HHI_MIPI_CNTL2_CH_EN, reg)); + + return 0; +} + +static int phy_g12a_mipi_dphy_analog_power_off(struct phy *phy) +{ + struct phy_g12a_mipi_dphy_analog_priv *priv = phy_get_drvdata(phy); + + regmap_write(priv->regmap, HHI_MIPI_CNTL0, 0); + regmap_write(priv->regmap, HHI_MIPI_CNTL1, 0); + regmap_write(priv->regmap, HHI_MIPI_CNTL2, 0); + + return 0; +} + +static const struct phy_ops phy_g12a_mipi_dphy_analog_ops = { + .configure = phy_g12a_mipi_dphy_analog_configure, + .power_on = phy_g12a_mipi_dphy_analog_power_on, + .power_off = phy_g12a_mipi_dphy_analog_power_off, + .owner = THIS_MODULE, +}; + +static int phy_g12a_mipi_dphy_analog_probe(struct platform_device *pdev) +{ + struct phy_provider *phy; + struct device *dev = &pdev->dev; + struct phy_g12a_mipi_dphy_analog_priv *priv; + struct device_node *np = dev->of_node, *parent_np; + struct regmap *map; + + priv = devm_kmalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* Get the hhi system controller node */ + parent_np = of_get_parent(np); + map = syscon_node_to_regmap(parent_np); + of_node_put(parent_np); + if (IS_ERR(map)) + return dev_err_probe(dev, PTR_ERR(map), "failed to get HHI regmap\n"); + + priv->regmap = map; + + priv->phy = devm_phy_create(dev, np, &phy_g12a_mipi_dphy_analog_ops); + if (IS_ERR(priv->phy)) + return dev_err_probe(dev, PTR_ERR(priv->phy), "failed to create PHY\n"); + + phy_set_drvdata(priv->phy, priv); + dev_set_drvdata(dev, priv); + + phy = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy); +} + +static const struct of_device_id phy_g12a_mipi_dphy_analog_of_match[] = { + { + .compatible = "amlogic,g12a-mipi-dphy-analog", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, phy_g12a_mipi_dphy_analog_of_match); + +static struct platform_driver phy_g12a_mipi_dphy_analog_driver = { + .probe = phy_g12a_mipi_dphy_analog_probe, + .driver = { + .name = "phy-meson-g12a-mipi-dphy-analog", + .of_match_table = phy_g12a_mipi_dphy_analog_of_match, + }, +}; +module_platform_driver(phy_g12a_mipi_dphy_analog_driver); + +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_DESCRIPTION("Meson G12A MIPI Analog D-PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/amlogic/phy-meson-g12a-usb2.c b/drivers/phy/amlogic/phy-meson-g12a-usb2.c new file mode 100644 index 0000000000..0e0b5c00b6 --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-g12a-usb2.c @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Meson G12A USB2 PHY driver + * + * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + * Copyright (C) 2017 Amlogic, Inc. All rights reserved + * Copyright (C) 2019 BayLibre, SAS + * Author: Neil Armstrong <narmstrong@baylibre.com> + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#define PHY_CTRL_R0 0x0 +#define PHY_CTRL_R1 0x4 +#define PHY_CTRL_R2 0x8 +#define PHY_CTRL_R3 0xc + #define PHY_CTRL_R3_SQUELCH_REF GENMASK(1, 0) + #define PHY_CTRL_R3_HSDIC_REF GENMASK(3, 2) + #define PHY_CTRL_R3_DISC_THRESH GENMASK(7, 4) + +#define PHY_CTRL_R4 0x10 + #define PHY_CTRL_R4_CALIB_CODE_7_0 GENMASK(7, 0) + #define PHY_CTRL_R4_CALIB_CODE_15_8 GENMASK(15, 8) + #define PHY_CTRL_R4_CALIB_CODE_23_16 GENMASK(23, 16) + #define PHY_CTRL_R4_I_C2L_CAL_EN BIT(24) + #define PHY_CTRL_R4_I_C2L_CAL_RESET_N BIT(25) + #define PHY_CTRL_R4_I_C2L_CAL_DONE BIT(26) + #define PHY_CTRL_R4_TEST_BYPASS_MODE_EN BIT(27) + #define PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0 GENMASK(29, 28) + #define PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2 GENMASK(31, 30) + +#define PHY_CTRL_R5 0x14 +#define PHY_CTRL_R6 0x18 +#define PHY_CTRL_R7 0x1c +#define PHY_CTRL_R8 0x20 +#define PHY_CTRL_R9 0x24 +#define PHY_CTRL_R10 0x28 +#define PHY_CTRL_R11 0x2c +#define PHY_CTRL_R12 0x30 +#define PHY_CTRL_R13 0x34 + #define PHY_CTRL_R13_CUSTOM_PATTERN_19 GENMASK(7, 0) + #define PHY_CTRL_R13_LOAD_STAT BIT(14) + #define PHY_CTRL_R13_UPDATE_PMA_SIGNALS BIT(15) + #define PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET GENMASK(20, 16) + #define PHY_CTRL_R13_CLEAR_HOLD_HS_DISCONNECT BIT(21) + #define PHY_CTRL_R13_BYPASS_HOST_DISCONNECT_VAL BIT(22) + #define PHY_CTRL_R13_BYPASS_HOST_DISCONNECT_EN BIT(23) + #define PHY_CTRL_R13_I_C2L_HS_EN BIT(24) + #define PHY_CTRL_R13_I_C2L_FS_EN BIT(25) + #define PHY_CTRL_R13_I_C2L_LS_EN BIT(26) + #define PHY_CTRL_R13_I_C2L_HS_OE BIT(27) + #define PHY_CTRL_R13_I_C2L_FS_OE BIT(28) + #define PHY_CTRL_R13_I_C2L_HS_RX_EN BIT(29) + #define PHY_CTRL_R13_I_C2L_FSLS_RX_EN BIT(30) + +#define PHY_CTRL_R14 0x38 + #define PHY_CTRL_R14_I_RDP_EN BIT(0) + #define PHY_CTRL_R14_I_RPU_SW1_EN BIT(1) + #define PHY_CTRL_R14_I_RPU_SW2_EN GENMASK(3, 2) + #define PHY_CTRL_R14_PG_RSTN BIT(4) + #define PHY_CTRL_R14_I_C2L_DATA_16_8 BIT(5) + #define PHY_CTRL_R14_I_C2L_ASSERT_SINGLE_EN_ZERO BIT(6) + #define PHY_CTRL_R14_BYPASS_CTRL_7_0 GENMASK(15, 8) + #define PHY_CTRL_R14_BYPASS_CTRL_15_8 GENMASK(23, 16) + +#define PHY_CTRL_R15 0x3c +#define PHY_CTRL_R16 0x40 + #define PHY_CTRL_R16_MPLL_M GENMASK(8, 0) + #define PHY_CTRL_R16_MPLL_N GENMASK(14, 10) + #define PHY_CTRL_R16_MPLL_TDC_MODE BIT(20) + #define PHY_CTRL_R16_MPLL_SDM_EN BIT(21) + #define PHY_CTRL_R16_MPLL_LOAD BIT(22) + #define PHY_CTRL_R16_MPLL_DCO_SDM_EN BIT(23) + #define PHY_CTRL_R16_MPLL_LOCK_LONG GENMASK(25, 24) + #define PHY_CTRL_R16_MPLL_LOCK_F BIT(26) + #define PHY_CTRL_R16_MPLL_FAST_LOCK BIT(27) + #define PHY_CTRL_R16_MPLL_EN BIT(28) + #define PHY_CTRL_R16_MPLL_RESET BIT(29) + #define PHY_CTRL_R16_MPLL_LOCK BIT(30) + #define PHY_CTRL_R16_MPLL_LOCK_DIG BIT(31) + +#define PHY_CTRL_R17 0x44 + #define PHY_CTRL_R17_MPLL_FRAC_IN GENMASK(13, 0) + #define PHY_CTRL_R17_MPLL_FIX_EN BIT(16) + #define PHY_CTRL_R17_MPLL_LAMBDA1 GENMASK(19, 17) + #define PHY_CTRL_R17_MPLL_LAMBDA0 GENMASK(22, 20) + #define PHY_CTRL_R17_MPLL_FILTER_MODE BIT(23) + #define PHY_CTRL_R17_MPLL_FILTER_PVT2 GENMASK(27, 24) + #define PHY_CTRL_R17_MPLL_FILTER_PVT1 GENMASK(31, 28) + +#define PHY_CTRL_R18 0x48 + #define PHY_CTRL_R18_MPLL_LKW_SEL GENMASK(1, 0) + #define PHY_CTRL_R18_MPLL_LK_W GENMASK(5, 2) + #define PHY_CTRL_R18_MPLL_LK_S GENMASK(11, 6) + #define PHY_CTRL_R18_MPLL_DCO_M_EN BIT(12) + #define PHY_CTRL_R18_MPLL_DCO_CLK_SEL BIT(13) + #define PHY_CTRL_R18_MPLL_PFD_GAIN GENMASK(15, 14) + #define PHY_CTRL_R18_MPLL_ROU GENMASK(18, 16) + #define PHY_CTRL_R18_MPLL_DATA_SEL GENMASK(21, 19) + #define PHY_CTRL_R18_MPLL_BIAS_ADJ GENMASK(23, 22) + #define PHY_CTRL_R18_MPLL_BB_MODE GENMASK(25, 24) + #define PHY_CTRL_R18_MPLL_ALPHA GENMASK(28, 26) + #define PHY_CTRL_R18_MPLL_ADJ_LDO GENMASK(30, 29) + #define PHY_CTRL_R18_MPLL_ACG_RANGE BIT(31) + +#define PHY_CTRL_R19 0x4c +#define PHY_CTRL_R20 0x50 + #define PHY_CTRL_R20_USB2_IDDET_EN BIT(0) + #define PHY_CTRL_R20_USB2_OTG_VBUS_TRIM_2_0 GENMASK(3, 1) + #define PHY_CTRL_R20_USB2_OTG_VBUSDET_EN BIT(4) + #define PHY_CTRL_R20_USB2_AMON_EN BIT(5) + #define PHY_CTRL_R20_USB2_CAL_CODE_R5 BIT(6) + #define PHY_CTRL_R20_BYPASS_OTG_DET BIT(7) + #define PHY_CTRL_R20_USB2_DMON_EN BIT(8) + #define PHY_CTRL_R20_USB2_DMON_SEL_3_0 GENMASK(12, 9) + #define PHY_CTRL_R20_USB2_EDGE_DRV_EN BIT(13) + #define PHY_CTRL_R20_USB2_EDGE_DRV_TRIM_1_0 GENMASK(15, 14) + #define PHY_CTRL_R20_USB2_BGR_ADJ_4_0 GENMASK(20, 16) + #define PHY_CTRL_R20_USB2_BGR_START BIT(21) + #define PHY_CTRL_R20_USB2_BGR_VREF_4_0 GENMASK(28, 24) + #define PHY_CTRL_R20_USB2_BGR_DBG_1_0 GENMASK(30, 29) + #define PHY_CTRL_R20_BYPASS_CAL_DONE_R5 BIT(31) + +#define PHY_CTRL_R21 0x54 + #define PHY_CTRL_R21_USB2_BGR_FORCE BIT(0) + #define PHY_CTRL_R21_USB2_CAL_ACK_EN BIT(1) + #define PHY_CTRL_R21_USB2_OTG_ACA_EN BIT(2) + #define PHY_CTRL_R21_USB2_TX_STRG_PD BIT(3) + #define PHY_CTRL_R21_USB2_OTG_ACA_TRIM_1_0 GENMASK(5, 4) + #define PHY_CTRL_R21_BYPASS_UTMI_CNTR GENMASK(15, 6) + #define PHY_CTRL_R21_BYPASS_UTMI_REG GENMASK(25, 20) + +#define PHY_CTRL_R22 0x58 +#define PHY_CTRL_R23 0x5c + +#define RESET_COMPLETE_TIME 1000 +#define PLL_RESET_COMPLETE_TIME 100 + +enum meson_soc_id { + MESON_SOC_G12A = 0, + MESON_SOC_A1, +}; + +struct phy_meson_g12a_usb2_priv { + struct device *dev; + struct regmap *regmap; + struct clk *clk; + struct reset_control *reset; + int soc_id; +}; + +static const struct regmap_config phy_meson_g12a_usb2_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = PHY_CTRL_R23, +}; + +static int phy_meson_g12a_usb2_init(struct phy *phy) +{ + struct phy_meson_g12a_usb2_priv *priv = phy_get_drvdata(phy); + int ret; + unsigned int value; + + ret = clk_prepare_enable(priv->clk); + if (ret) + return ret; + + ret = reset_control_reset(priv->reset); + if (ret) { + clk_disable_unprepare(priv->clk); + return ret; + } + + udelay(RESET_COMPLETE_TIME); + + /* usb2_otg_aca_en == 0 */ + regmap_update_bits(priv->regmap, PHY_CTRL_R21, + PHY_CTRL_R21_USB2_OTG_ACA_EN, 0); + + /* PLL Setup : 24MHz * 20 / 1 = 480MHz */ + regmap_write(priv->regmap, PHY_CTRL_R16, + FIELD_PREP(PHY_CTRL_R16_MPLL_M, 20) | + FIELD_PREP(PHY_CTRL_R16_MPLL_N, 1) | + PHY_CTRL_R16_MPLL_LOAD | + FIELD_PREP(PHY_CTRL_R16_MPLL_LOCK_LONG, 1) | + PHY_CTRL_R16_MPLL_FAST_LOCK | + PHY_CTRL_R16_MPLL_EN | + PHY_CTRL_R16_MPLL_RESET); + + regmap_write(priv->regmap, PHY_CTRL_R17, + FIELD_PREP(PHY_CTRL_R17_MPLL_FRAC_IN, 0) | + FIELD_PREP(PHY_CTRL_R17_MPLL_LAMBDA1, 7) | + FIELD_PREP(PHY_CTRL_R17_MPLL_LAMBDA0, 7) | + FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT2, 2) | + FIELD_PREP(PHY_CTRL_R17_MPLL_FILTER_PVT1, 9)); + + value = FIELD_PREP(PHY_CTRL_R18_MPLL_LKW_SEL, 1) | + FIELD_PREP(PHY_CTRL_R18_MPLL_LK_W, 9) | + FIELD_PREP(PHY_CTRL_R18_MPLL_LK_S, 0x27) | + FIELD_PREP(PHY_CTRL_R18_MPLL_PFD_GAIN, 1) | + FIELD_PREP(PHY_CTRL_R18_MPLL_ROU, 7) | + FIELD_PREP(PHY_CTRL_R18_MPLL_DATA_SEL, 3) | + FIELD_PREP(PHY_CTRL_R18_MPLL_BIAS_ADJ, 1) | + FIELD_PREP(PHY_CTRL_R18_MPLL_BB_MODE, 0) | + FIELD_PREP(PHY_CTRL_R18_MPLL_ALPHA, 3) | + FIELD_PREP(PHY_CTRL_R18_MPLL_ADJ_LDO, 1) | + PHY_CTRL_R18_MPLL_ACG_RANGE; + + if (priv->soc_id == MESON_SOC_A1) + value |= PHY_CTRL_R18_MPLL_DCO_CLK_SEL; + + regmap_write(priv->regmap, PHY_CTRL_R18, value); + + udelay(PLL_RESET_COMPLETE_TIME); + + /* UnReset PLL */ + regmap_write(priv->regmap, PHY_CTRL_R16, + FIELD_PREP(PHY_CTRL_R16_MPLL_M, 20) | + FIELD_PREP(PHY_CTRL_R16_MPLL_N, 1) | + PHY_CTRL_R16_MPLL_LOAD | + FIELD_PREP(PHY_CTRL_R16_MPLL_LOCK_LONG, 1) | + PHY_CTRL_R16_MPLL_FAST_LOCK | + PHY_CTRL_R16_MPLL_EN); + + /* PHY Tuning */ + regmap_write(priv->regmap, PHY_CTRL_R20, + FIELD_PREP(PHY_CTRL_R20_USB2_OTG_VBUS_TRIM_2_0, 4) | + PHY_CTRL_R20_USB2_OTG_VBUSDET_EN | + FIELD_PREP(PHY_CTRL_R20_USB2_DMON_SEL_3_0, 15) | + PHY_CTRL_R20_USB2_EDGE_DRV_EN | + FIELD_PREP(PHY_CTRL_R20_USB2_EDGE_DRV_TRIM_1_0, 3) | + FIELD_PREP(PHY_CTRL_R20_USB2_BGR_ADJ_4_0, 0) | + FIELD_PREP(PHY_CTRL_R20_USB2_BGR_VREF_4_0, 0) | + FIELD_PREP(PHY_CTRL_R20_USB2_BGR_DBG_1_0, 0)); + + if (priv->soc_id == MESON_SOC_G12A) + regmap_write(priv->regmap, PHY_CTRL_R4, + FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_7_0, 0xf) | + FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_15_8, 0xf) | + FIELD_PREP(PHY_CTRL_R4_CALIB_CODE_23_16, 0xf) | + PHY_CTRL_R4_TEST_BYPASS_MODE_EN | + FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_1_0, 0) | + FIELD_PREP(PHY_CTRL_R4_I_C2L_BIAS_TRIM_3_2, 0)); + else if (priv->soc_id == MESON_SOC_A1) { + regmap_write(priv->regmap, PHY_CTRL_R21, + PHY_CTRL_R21_USB2_CAL_ACK_EN | + PHY_CTRL_R21_USB2_TX_STRG_PD | + FIELD_PREP(PHY_CTRL_R21_USB2_OTG_ACA_TRIM_1_0, 2)); + + /* Analog Settings */ + regmap_write(priv->regmap, PHY_CTRL_R13, + FIELD_PREP(PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET, 7)); + } + + /* Tuning Disconnect Threshold */ + regmap_write(priv->regmap, PHY_CTRL_R3, + FIELD_PREP(PHY_CTRL_R3_SQUELCH_REF, 0) | + FIELD_PREP(PHY_CTRL_R3_HSDIC_REF, 1) | + FIELD_PREP(PHY_CTRL_R3_DISC_THRESH, 3)); + + if (priv->soc_id == MESON_SOC_G12A) { + /* Analog Settings */ + regmap_write(priv->regmap, PHY_CTRL_R14, 0); + regmap_write(priv->regmap, PHY_CTRL_R13, + PHY_CTRL_R13_UPDATE_PMA_SIGNALS | + FIELD_PREP(PHY_CTRL_R13_MIN_COUNT_FOR_SYNC_DET, 7)); + } + + return 0; +} + +static int phy_meson_g12a_usb2_exit(struct phy *phy) +{ + struct phy_meson_g12a_usb2_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = reset_control_reset(priv->reset); + if (!ret) + clk_disable_unprepare(priv->clk); + + return ret; +} + +/* set_mode is not needed, mode setting is handled via the UTMI bus */ +static const struct phy_ops phy_meson_g12a_usb2_ops = { + .init = phy_meson_g12a_usb2_init, + .exit = phy_meson_g12a_usb2_exit, + .owner = THIS_MODULE, +}; + +static int phy_meson_g12a_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *phy_provider; + struct phy_meson_g12a_usb2_priv *priv; + struct phy *phy; + void __iomem *base; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + platform_set_drvdata(pdev, priv); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->soc_id = (uintptr_t)of_device_get_match_data(&pdev->dev); + + priv->regmap = devm_regmap_init_mmio(dev, base, + &phy_meson_g12a_usb2_regmap_conf); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->clk = devm_clk_get(dev, "xtal"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->reset = devm_reset_control_get(dev, "phy"); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + ret = reset_control_deassert(priv->reset); + if (ret) + return ret; + + phy = devm_phy_create(dev, NULL, &phy_meson_g12a_usb2_ops); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to create PHY\n"); + + return ret; + } + + phy_set_bus_width(phy, 8); + 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 phy_meson_g12a_usb2_of_match[] = { + { + .compatible = "amlogic,g12a-usb2-phy", + .data = (void *)MESON_SOC_G12A, + }, + { + .compatible = "amlogic,a1-usb2-phy", + .data = (void *)MESON_SOC_A1, + }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, phy_meson_g12a_usb2_of_match); + +static struct platform_driver phy_meson_g12a_usb2_driver = { + .probe = phy_meson_g12a_usb2_probe, + .driver = { + .name = "phy-meson-g12a-usb2", + .of_match_table = phy_meson_g12a_usb2_of_match, + }, +}; +module_platform_driver(phy_meson_g12a_usb2_driver); + +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_DESCRIPTION("Meson G12A USB2 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c b/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c new file mode 100644 index 0000000000..2712c4bd54 --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-g12a-usb3-pcie.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Amlogic G12A USB3 + PCIE Combo PHY driver + * + * Copyright (C) 2017 Amlogic, Inc. All rights reserved + * Copyright (C) 2019 BayLibre, SAS + * Author: Neil Armstrong <narmstrong@baylibre.com> + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/platform_device.h> +#include <dt-bindings/phy/phy.h> + +#define PHY_R0 0x00 + #define PHY_R0_PCIE_POWER_STATE GENMASK(4, 0) + #define PHY_R0_PCIE_USB3_SWITCH GENMASK(6, 5) + +#define PHY_R1 0x04 + #define PHY_R1_PHY_TX1_TERM_OFFSET GENMASK(4, 0) + #define PHY_R1_PHY_TX0_TERM_OFFSET GENMASK(9, 5) + #define PHY_R1_PHY_RX1_EQ GENMASK(12, 10) + #define PHY_R1_PHY_RX0_EQ GENMASK(15, 13) + #define PHY_R1_PHY_LOS_LEVEL GENMASK(20, 16) + #define PHY_R1_PHY_LOS_BIAS GENMASK(23, 21) + #define PHY_R1_PHY_REF_CLKDIV2 BIT(24) + #define PHY_R1_PHY_MPLL_MULTIPLIER GENMASK(31, 25) + +#define PHY_R2 0x08 + #define PHY_R2_PCS_TX_DEEMPH_GEN2_6DB GENMASK(5, 0) + #define PHY_R2_PCS_TX_DEEMPH_GEN2_3P5DB GENMASK(11, 6) + #define PHY_R2_PCS_TX_DEEMPH_GEN1 GENMASK(17, 12) + #define PHY_R2_PHY_TX_VBOOST_LVL GENMASK(20, 18) + +#define PHY_R4 0x10 + #define PHY_R4_PHY_CR_WRITE BIT(0) + #define PHY_R4_PHY_CR_READ BIT(1) + #define PHY_R4_PHY_CR_DATA_IN GENMASK(17, 2) + #define PHY_R4_PHY_CR_CAP_DATA BIT(18) + #define PHY_R4_PHY_CR_CAP_ADDR BIT(19) + +#define PHY_R5 0x14 + #define PHY_R5_PHY_CR_DATA_OUT GENMASK(15, 0) + #define PHY_R5_PHY_CR_ACK BIT(16) + #define PHY_R5_PHY_BS_OUT BIT(17) + +#define PCIE_RESET_DELAY 500 + +struct phy_g12a_usb3_pcie_priv { + struct regmap *regmap; + struct regmap *regmap_cr; + struct clk *clk_ref; + struct reset_control *reset; + struct phy *phy; + unsigned int mode; +}; + +static const struct regmap_config phy_g12a_usb3_pcie_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = PHY_R5, +}; + +static int phy_g12a_usb3_pcie_cr_bus_addr(struct phy_g12a_usb3_pcie_priv *priv, + unsigned int addr) +{ + unsigned int val, reg; + int ret; + + reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, addr); + + regmap_write(priv->regmap, PHY_R4, reg); + regmap_write(priv->regmap, PHY_R4, reg); + + regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_ADDR); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + regmap_write(priv->regmap, PHY_R4, reg); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + !(val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + return 0; +} + +static int phy_g12a_usb3_pcie_cr_bus_read(void *context, unsigned int addr, + unsigned int *data) +{ + struct phy_g12a_usb3_pcie_priv *priv = context; + unsigned int val; + int ret; + + ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr); + if (ret) + return ret; + + regmap_write(priv->regmap, PHY_R4, 0); + regmap_write(priv->regmap, PHY_R4, PHY_R4_PHY_CR_READ); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + *data = FIELD_GET(PHY_R5_PHY_CR_DATA_OUT, val); + + regmap_write(priv->regmap, PHY_R4, 0); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + !(val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + return 0; +} + +static int phy_g12a_usb3_pcie_cr_bus_write(void *context, unsigned int addr, + unsigned int data) +{ + struct phy_g12a_usb3_pcie_priv *priv = context; + unsigned int val, reg; + int ret; + + ret = phy_g12a_usb3_pcie_cr_bus_addr(priv, addr); + if (ret) + return ret; + + reg = FIELD_PREP(PHY_R4_PHY_CR_DATA_IN, data); + + regmap_write(priv->regmap, PHY_R4, reg); + regmap_write(priv->regmap, PHY_R4, reg); + + regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_CAP_DATA); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + regmap_write(priv->regmap, PHY_R4, reg); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK) == 0, + 5, 1000); + if (ret) + return ret; + + regmap_write(priv->regmap, PHY_R4, reg); + + regmap_write(priv->regmap, PHY_R4, reg | PHY_R4_PHY_CR_WRITE); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK), + 5, 1000); + if (ret) + return ret; + + regmap_write(priv->regmap, PHY_R4, reg); + + ret = regmap_read_poll_timeout(priv->regmap, PHY_R5, val, + (val & PHY_R5_PHY_CR_ACK) == 0, + 5, 1000); + if (ret) + return ret; + + return 0; +} + +static const struct regmap_config phy_g12a_usb3_pcie_cr_regmap_conf = { + .reg_bits = 16, + .val_bits = 16, + .reg_read = phy_g12a_usb3_pcie_cr_bus_read, + .reg_write = phy_g12a_usb3_pcie_cr_bus_write, + .max_register = 0xffff, + .disable_locking = true, +}; + +static int phy_g12a_usb3_init(struct phy *phy) +{ + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); + int data, ret; + + ret = reset_control_reset(priv->reset); + if (ret) + return ret; + + /* Switch PHY to USB3 */ + /* TODO figure out how to handle when PCIe was set in the bootloader */ + regmap_update_bits(priv->regmap, PHY_R0, + PHY_R0_PCIE_USB3_SWITCH, + PHY_R0_PCIE_USB3_SWITCH); + + /* + * WORKAROUND: There is SSPHY suspend bug due to + * which USB enumerates + * in HS mode instead of SS mode. Workaround it by asserting + * LANE0.TX_ALT_BLOCK.EN_ALT_BUS to enable TX to use alt bus + * mode + */ + ret = regmap_update_bits(priv->regmap_cr, 0x102d, BIT(7), BIT(7)); + if (ret) + return ret; + + ret = regmap_update_bits(priv->regmap_cr, 0x1010, 0xff0, 20); + if (ret) + return ret; + + /* + * Fix RX Equalization setting as follows + * LANE0.RX_OVRD_IN_HI. RX_EQ_EN set to 0 + * LANE0.RX_OVRD_IN_HI.RX_EQ_EN_OVRD set to 1 + * LANE0.RX_OVRD_IN_HI.RX_EQ set to 3 + * LANE0.RX_OVRD_IN_HI.RX_EQ_OVRD set to 1 + */ + ret = regmap_read(priv->regmap_cr, 0x1006, &data); + if (ret) + return ret; + + data &= ~BIT(6); + data |= BIT(7); + data &= ~(0x7 << 8); + data |= (0x3 << 8); + data |= (1 << 11); + ret = regmap_write(priv->regmap_cr, 0x1006, data); + if (ret) + return ret; + + /* + * Set EQ and TX launch amplitudes as follows + * LANE0.TX_OVRD_DRV_LO.PREEMPH set to 22 + * LANE0.TX_OVRD_DRV_LO.AMPLITUDE set to 127 + * LANE0.TX_OVRD_DRV_LO.EN set to 1. + */ + ret = regmap_read(priv->regmap_cr, 0x1002, &data); + if (ret) + return ret; + + data &= ~0x3f80; + data |= (0x16 << 7); + data &= ~0x7f; + data |= (0x7f | BIT(14)); + ret = regmap_write(priv->regmap_cr, 0x1002, data); + if (ret) + return ret; + + /* MPLL_LOOP_CTL.PROP_CNTRL = 8 */ + ret = regmap_update_bits(priv->regmap_cr, 0x30, 0xf << 4, 8 << 4); + if (ret) + return ret; + + regmap_update_bits(priv->regmap, PHY_R2, + PHY_R2_PHY_TX_VBOOST_LVL, + FIELD_PREP(PHY_R2_PHY_TX_VBOOST_LVL, 0x4)); + + regmap_update_bits(priv->regmap, PHY_R1, + PHY_R1_PHY_LOS_BIAS | PHY_R1_PHY_LOS_LEVEL, + FIELD_PREP(PHY_R1_PHY_LOS_BIAS, 4) | + FIELD_PREP(PHY_R1_PHY_LOS_LEVEL, 9)); + + return 0; +} + +static int phy_g12a_usb3_pcie_power_on(struct phy *phy) +{ + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); + + if (priv->mode == PHY_TYPE_USB3) + return 0; + + regmap_update_bits(priv->regmap, PHY_R0, + PHY_R0_PCIE_POWER_STATE, + FIELD_PREP(PHY_R0_PCIE_POWER_STATE, 0x1c)); + + return 0; +} + +static int phy_g12a_usb3_pcie_power_off(struct phy *phy) +{ + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); + + if (priv->mode == PHY_TYPE_USB3) + return 0; + + regmap_update_bits(priv->regmap, PHY_R0, + PHY_R0_PCIE_POWER_STATE, + FIELD_PREP(PHY_R0_PCIE_POWER_STATE, 0x1d)); + + return 0; +} + +static int phy_g12a_usb3_pcie_reset(struct phy *phy) +{ + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); + int ret; + + if (priv->mode == PHY_TYPE_USB3) + return 0; + + ret = reset_control_assert(priv->reset); + if (ret) + return ret; + + udelay(PCIE_RESET_DELAY); + + ret = reset_control_deassert(priv->reset); + if (ret) + return ret; + + udelay(PCIE_RESET_DELAY); + + return 0; +} + +static int phy_g12a_usb3_pcie_init(struct phy *phy) +{ + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); + + if (priv->mode == PHY_TYPE_USB3) + return phy_g12a_usb3_init(phy); + + return 0; +} + +static int phy_g12a_usb3_pcie_exit(struct phy *phy) +{ + struct phy_g12a_usb3_pcie_priv *priv = phy_get_drvdata(phy); + + if (priv->mode == PHY_TYPE_USB3) + return reset_control_reset(priv->reset); + + return 0; +} + +static struct phy *phy_g12a_usb3_pcie_xlate(struct device *dev, + struct of_phandle_args *args) +{ + struct phy_g12a_usb3_pcie_priv *priv = dev_get_drvdata(dev); + unsigned int mode; + + if (args->args_count < 1) { + dev_err(dev, "invalid number of arguments\n"); + return ERR_PTR(-EINVAL); + } + + mode = args->args[0]; + + if (mode != PHY_TYPE_USB3 && mode != PHY_TYPE_PCIE) { + dev_err(dev, "invalid phy mode select argument\n"); + return ERR_PTR(-EINVAL); + } + + priv->mode = mode; + + return priv->phy; +} + +static const struct phy_ops phy_g12a_usb3_pcie_ops = { + .init = phy_g12a_usb3_pcie_init, + .exit = phy_g12a_usb3_pcie_exit, + .power_on = phy_g12a_usb3_pcie_power_on, + .power_off = phy_g12a_usb3_pcie_power_off, + .reset = phy_g12a_usb3_pcie_reset, + .owner = THIS_MODULE, +}; + +static int phy_g12a_usb3_pcie_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct phy_g12a_usb3_pcie_priv *priv; + struct phy_provider *phy_provider; + void __iomem *base; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->regmap = devm_regmap_init_mmio(dev, base, + &phy_g12a_usb3_pcie_regmap_conf); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->regmap_cr = devm_regmap_init(dev, NULL, priv, + &phy_g12a_usb3_pcie_cr_regmap_conf); + if (IS_ERR(priv->regmap_cr)) + return PTR_ERR(priv->regmap_cr); + + priv->clk_ref = devm_clk_get_enabled(dev, "ref_clk"); + if (IS_ERR(priv->clk_ref)) + return PTR_ERR(priv->clk_ref); + + priv->reset = devm_reset_control_array_get_exclusive(dev); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + priv->phy = devm_phy_create(dev, np, &phy_g12a_usb3_pcie_ops); + if (IS_ERR(priv->phy)) + return dev_err_probe(dev, PTR_ERR(priv->phy), "failed to create PHY\n"); + + phy_set_drvdata(priv->phy, priv); + dev_set_drvdata(dev, priv); + + phy_provider = devm_of_phy_provider_register(dev, + phy_g12a_usb3_pcie_xlate); + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id phy_g12a_usb3_pcie_of_match[] = { + { .compatible = "amlogic,g12a-usb3-pcie-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_g12a_usb3_pcie_of_match); + +static struct platform_driver phy_g12a_usb3_pcie_driver = { + .probe = phy_g12a_usb3_pcie_probe, + .driver = { + .name = "phy-g12a-usb3-pcie", + .of_match_table = phy_g12a_usb3_pcie_of_match, + }, +}; +module_platform_driver(phy_g12a_usb3_pcie_driver); + +MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); +MODULE_DESCRIPTION("Amlogic G12A USB3 + PCIE Combo PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/amlogic/phy-meson-gxl-usb2.c b/drivers/phy/amlogic/phy-meson-gxl-usb2.c new file mode 100644 index 0000000000..14ea89927a --- /dev/null +++ b/drivers/phy/amlogic/phy-meson-gxl-usb2.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Meson GXL and GXM USB2 PHY driver + * + * Copyright (C) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +/* bits [31:27] are read-only */ +#define U2P_R0 0x0 + #define U2P_R0_BYPASS_SEL BIT(0) + #define U2P_R0_BYPASS_DM_EN BIT(1) + #define U2P_R0_BYPASS_DP_EN BIT(2) + #define U2P_R0_TXBITSTUFF_ENH BIT(3) + #define U2P_R0_TXBITSTUFF_EN BIT(4) + #define U2P_R0_DM_PULLDOWN BIT(5) + #define U2P_R0_DP_PULLDOWN BIT(6) + #define U2P_R0_DP_VBUS_VLD_EXT_SEL BIT(7) + #define U2P_R0_DP_VBUS_VLD_EXT BIT(8) + #define U2P_R0_ADP_PRB_EN BIT(9) + #define U2P_R0_ADP_DISCHARGE BIT(10) + #define U2P_R0_ADP_CHARGE BIT(11) + #define U2P_R0_DRV_VBUS BIT(12) + #define U2P_R0_ID_PULLUP BIT(13) + #define U2P_R0_LOOPBACK_EN_B BIT(14) + #define U2P_R0_OTG_DISABLE BIT(15) + #define U2P_R0_COMMON_ONN BIT(16) + #define U2P_R0_FSEL_MASK GENMASK(19, 17) + #define U2P_R0_REF_CLK_SEL_MASK GENMASK(21, 20) + #define U2P_R0_POWER_ON_RESET BIT(22) + #define U2P_R0_V_ATE_TEST_EN_B_MASK GENMASK(24, 23) + #define U2P_R0_ID_SET_ID_DQ BIT(25) + #define U2P_R0_ATE_RESET BIT(26) + #define U2P_R0_FSV_MINUS BIT(27) + #define U2P_R0_FSV_PLUS BIT(28) + #define U2P_R0_BYPASS_DM_DATA BIT(29) + #define U2P_R0_BYPASS_DP_DATA BIT(30) + +#define U2P_R1 0x4 + #define U2P_R1_BURN_IN_TEST BIT(0) + #define U2P_R1_ACA_ENABLE BIT(1) + #define U2P_R1_DCD_ENABLE BIT(2) + #define U2P_R1_VDAT_SRC_EN_B BIT(3) + #define U2P_R1_VDAT_DET_EN_B BIT(4) + #define U2P_R1_CHARGES_SEL BIT(5) + #define U2P_R1_TX_PREEMP_PULSE_TUNE BIT(6) + #define U2P_R1_TX_PREEMP_AMP_TUNE_MASK GENMASK(8, 7) + #define U2P_R1_TX_RES_TUNE_MASK GENMASK(10, 9) + #define U2P_R1_TX_RISE_TUNE_MASK GENMASK(12, 11) + #define U2P_R1_TX_VREF_TUNE_MASK GENMASK(16, 13) + #define U2P_R1_TX_FSLS_TUNE_MASK GENMASK(20, 17) + #define U2P_R1_TX_HSXV_TUNE_MASK GENMASK(22, 21) + #define U2P_R1_OTG_TUNE_MASK GENMASK(25, 23) + #define U2P_R1_SQRX_TUNE_MASK GENMASK(28, 26) + #define U2P_R1_COMP_DIS_TUNE_MASK GENMASK(31, 29) + +/* bits [31:14] are read-only */ +#define U2P_R2 0x8 + #define U2P_R2_TESTDATA_IN_MASK GENMASK(7, 0) + #define U2P_R2_TESTADDR_MASK GENMASK(11, 8) + #define U2P_R2_TESTDATA_OUT_SEL BIT(12) + #define U2P_R2_TESTCLK BIT(13) + #define U2P_R2_TESTDATA_OUT_MASK GENMASK(17, 14) + #define U2P_R2_ACA_PIN_RANGE_C BIT(18) + #define U2P_R2_ACA_PIN_RANGE_B BIT(19) + #define U2P_R2_ACA_PIN_RANGE_A BIT(20) + #define U2P_R2_ACA_PIN_GND BIT(21) + #define U2P_R2_ACA_PIN_FLOAT BIT(22) + #define U2P_R2_CHARGE_DETECT BIT(23) + #define U2P_R2_DEVICE_SESSION_VALID BIT(24) + #define U2P_R2_ADP_PROBE BIT(25) + #define U2P_R2_ADP_SENSE BIT(26) + #define U2P_R2_SESSION_END BIT(27) + #define U2P_R2_VBUS_VALID BIT(28) + #define U2P_R2_B_VALID BIT(29) + #define U2P_R2_A_VALID BIT(30) + #define U2P_R2_ID_DIG BIT(31) + +#define U2P_R3 0xc + +#define RESET_COMPLETE_TIME 500 + +struct phy_meson_gxl_usb2_priv { + struct regmap *regmap; + enum phy_mode mode; + int is_enabled; + struct clk *clk; + struct reset_control *reset; +}; + +static const struct regmap_config phy_meson_gxl_usb2_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = U2P_R3, +}; + +static int phy_meson_gxl_usb2_init(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + int ret; + + ret = reset_control_reset(priv->reset); + if (ret) + return ret; + + ret = clk_prepare_enable(priv->clk); + if (ret) { + reset_control_rearm(priv->reset); + return ret; + } + + return 0; +} + +static int phy_meson_gxl_usb2_exit(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + + clk_disable_unprepare(priv->clk); + reset_control_rearm(priv->reset); + + return 0; +} + +static int phy_meson_gxl_usb2_reset(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + + if (priv->is_enabled) { + /* reset the PHY and wait until settings are stabilized */ + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, + U2P_R0_POWER_ON_RESET); + udelay(RESET_COMPLETE_TIME); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, + 0); + udelay(RESET_COMPLETE_TIME); + } + + return 0; +} + +static int phy_meson_gxl_usb2_set_mode(struct phy *phy, + enum phy_mode mode, int submode) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + + switch (mode) { + case PHY_MODE_USB_HOST: + case PHY_MODE_USB_OTG: + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DM_PULLDOWN, + U2P_R0_DM_PULLDOWN); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DP_PULLDOWN, + U2P_R0_DP_PULLDOWN); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_ID_PULLUP, + U2P_R0_ID_PULLUP); + break; + + case PHY_MODE_USB_DEVICE: + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DM_PULLDOWN, + 0); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_DP_PULLDOWN, + 0); + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_ID_PULLUP, + U2P_R0_ID_PULLUP); + break; + + default: + return -EINVAL; + } + + phy_meson_gxl_usb2_reset(phy); + + priv->mode = mode; + + return 0; +} + +static int phy_meson_gxl_usb2_power_off(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + + priv->is_enabled = 0; + + /* power off the PHY by putting it into reset mode */ + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, + U2P_R0_POWER_ON_RESET); + + return 0; +} + +static int phy_meson_gxl_usb2_power_on(struct phy *phy) +{ + struct phy_meson_gxl_usb2_priv *priv = phy_get_drvdata(phy); + int ret; + + priv->is_enabled = 1; + + /* power on the PHY by taking it out of reset mode */ + regmap_update_bits(priv->regmap, U2P_R0, U2P_R0_POWER_ON_RESET, 0); + + ret = phy_meson_gxl_usb2_set_mode(phy, priv->mode, 0); + if (ret) { + phy_meson_gxl_usb2_power_off(phy); + + dev_err(&phy->dev, "Failed to initialize PHY with mode %d\n", + priv->mode); + return ret; + } + + return 0; +} + +static const struct phy_ops phy_meson_gxl_usb2_ops = { + .init = phy_meson_gxl_usb2_init, + .exit = phy_meson_gxl_usb2_exit, + .power_on = phy_meson_gxl_usb2_power_on, + .power_off = phy_meson_gxl_usb2_power_off, + .set_mode = phy_meson_gxl_usb2_set_mode, + .reset = phy_meson_gxl_usb2_reset, + .owner = THIS_MODULE, +}; + +static int phy_meson_gxl_usb2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct phy_provider *phy_provider; + struct phy_meson_gxl_usb2_priv *priv; + struct phy *phy; + void __iomem *base; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + /* start in host mode */ + priv->mode = PHY_MODE_USB_HOST; + + priv->regmap = devm_regmap_init_mmio(dev, base, + &phy_meson_gxl_usb2_regmap_conf); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->clk = devm_clk_get_optional(dev, "phy"); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->reset = devm_reset_control_get_optional_shared(dev, "phy"); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + phy = devm_phy_create(dev, NULL, &phy_meson_gxl_usb2_ops); + if (IS_ERR(phy)) { + ret = PTR_ERR(phy); + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to create PHY\n"); + + return ret; + } + + 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 phy_meson_gxl_usb2_of_match[] = { + { .compatible = "amlogic,meson-gxl-usb2-phy", }, + { }, +}; +MODULE_DEVICE_TABLE(of, phy_meson_gxl_usb2_of_match); + +static struct platform_driver phy_meson_gxl_usb2_driver = { + .probe = phy_meson_gxl_usb2_probe, + .driver = { + .name = "phy-meson-gxl-usb2", + .of_match_table = phy_meson_gxl_usb2_of_match, + }, +}; +module_platform_driver(phy_meson_gxl_usb2_driver); + +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); +MODULE_DESCRIPTION("Meson GXL and GXM USB2 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/amlogic/phy-meson8-hdmi-tx.c b/drivers/phy/amlogic/phy-meson8-hdmi-tx.c new file mode 100644 index 0000000000..2617f7f6c2 --- /dev/null +++ b/drivers/phy/amlogic/phy-meson8-hdmi-tx.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Meson8, Meson8b and Meson8m2 HDMI TX PHY. + * + * Copyright (C) 2021 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + */ + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/clk.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regmap.h> + +/* + * Unfortunately there is no detailed documentation available for the + * HHI_HDMI_PHY_CNTL0 register. CTL0 and CTL1 is all we know about. + * Magic register values in the driver below are taken from the vendor + * BSP / kernel. + */ +#define HHI_HDMI_PHY_CNTL0 0x3a0 + #define HHI_HDMI_PHY_CNTL0_HDMI_CTL1 GENMASK(31, 16) + #define HHI_HDMI_PHY_CNTL0_HDMI_CTL0 GENMASK(15, 0) + +#define HHI_HDMI_PHY_CNTL1 0x3a4 + #define HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE BIT(1) + #define HHI_HDMI_PHY_CNTL1_SOFT_RESET BIT(0) + +#define HHI_HDMI_PHY_CNTL2 0x3a8 + +struct phy_meson8_hdmi_tx_priv { + struct regmap *hhi; + struct clk *tmds_clk; +}; + +static int phy_meson8_hdmi_tx_init(struct phy *phy) +{ + struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); + + return clk_prepare_enable(priv->tmds_clk); +} + +static int phy_meson8_hdmi_tx_exit(struct phy *phy) +{ + struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); + + clk_disable_unprepare(priv->tmds_clk); + + return 0; +} + +static int phy_meson8_hdmi_tx_power_on(struct phy *phy) +{ + struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); + unsigned int i; + u16 hdmi_ctl0; + + if (clk_get_rate(priv->tmds_clk) >= 2970UL * 1000 * 1000) + hdmi_ctl0 = 0x1e8b; + else + hdmi_ctl0 = 0x4d0b; + + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, + FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x08c3) | + FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, hdmi_ctl0)); + + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, 0x0); + + /* Reset three times, just like the vendor driver does */ + for (i = 0; i < 3; i++) { + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, + HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE | + HHI_HDMI_PHY_CNTL1_SOFT_RESET); + usleep_range(1000, 2000); + + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, + HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE); + usleep_range(1000, 2000); + } + + return 0; +} + +static int phy_meson8_hdmi_tx_power_off(struct phy *phy) +{ + struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); + + regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0, + FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x0841) | + FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, 0x8d00)); + + return 0; +} + +static const struct phy_ops phy_meson8_hdmi_tx_ops = { + .init = phy_meson8_hdmi_tx_init, + .exit = phy_meson8_hdmi_tx_exit, + .power_on = phy_meson8_hdmi_tx_power_on, + .power_off = phy_meson8_hdmi_tx_power_off, + .owner = THIS_MODULE, +}; + +static int phy_meson8_hdmi_tx_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct phy_meson8_hdmi_tx_priv *priv; + struct phy_provider *phy_provider; + struct resource *res; + struct phy *phy; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->hhi = syscon_node_to_regmap(np->parent); + if (IS_ERR(priv->hhi)) + return PTR_ERR(priv->hhi); + + priv->tmds_clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(priv->tmds_clk)) + return PTR_ERR(priv->tmds_clk); + + phy = devm_phy_create(&pdev->dev, np, &phy_meson8_hdmi_tx_ops); + if (IS_ERR(phy)) + return PTR_ERR(phy); + + phy_set_drvdata(phy, priv); + + phy_provider = devm_of_phy_provider_register(&pdev->dev, + of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct of_device_id phy_meson8_hdmi_tx_of_match[] = { + { .compatible = "amlogic,meson8-hdmi-tx-phy" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, phy_meson8_hdmi_tx_of_match); + +static struct platform_driver phy_meson8_hdmi_tx_driver = { + .probe = phy_meson8_hdmi_tx_probe, + .driver = { + .name = "phy-meson8-hdmi-tx", + .of_match_table = phy_meson8_hdmi_tx_of_match, + }, +}; +module_platform_driver(phy_meson8_hdmi_tx_driver); + +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); +MODULE_DESCRIPTION("Meson8, Meson8b and Meson8m2 HDMI TX PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/phy/amlogic/phy-meson8b-usb2.c b/drivers/phy/amlogic/phy-meson8b-usb2.c new file mode 100644 index 0000000000..d63147c41b --- /dev/null +++ b/drivers/phy/amlogic/phy-meson8b-usb2.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Meson8, Meson8b and GXBB USB2 PHY driver + * + * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/usb/of.h> + +#define REG_CONFIG 0x00 + #define REG_CONFIG_CLK_EN BIT(0) + #define REG_CONFIG_CLK_SEL_MASK GENMASK(3, 1) + #define REG_CONFIG_CLK_DIV_MASK GENMASK(10, 4) + #define REG_CONFIG_CLK_32k_ALTSEL BIT(15) + #define REG_CONFIG_TEST_TRIG BIT(31) + +#define REG_CTRL 0x04 + #define REG_CTRL_SOFT_PRST BIT(0) + #define REG_CTRL_SOFT_HRESET BIT(1) + #define REG_CTRL_SS_SCALEDOWN_MODE_MASK GENMASK(3, 2) + #define REG_CTRL_CLK_DET_RST BIT(4) + #define REG_CTRL_INTR_SEL BIT(5) + #define REG_CTRL_CLK_DETECTED BIT(8) + #define REG_CTRL_SOF_SENT_RCVD_TGL BIT(9) + #define REG_CTRL_SOF_TOGGLE_OUT BIT(10) + #define REG_CTRL_POWER_ON_RESET BIT(15) + #define REG_CTRL_SLEEPM BIT(16) + #define REG_CTRL_TX_BITSTUFF_ENN_H BIT(17) + #define REG_CTRL_TX_BITSTUFF_ENN BIT(18) + #define REG_CTRL_COMMON_ON BIT(19) + #define REG_CTRL_REF_CLK_SEL_MASK GENMASK(21, 20) + #define REG_CTRL_REF_CLK_SEL_SHIFT 20 + #define REG_CTRL_FSEL_MASK GENMASK(24, 22) + #define REG_CTRL_FSEL_SHIFT 22 + #define REG_CTRL_PORT_RESET BIT(25) + #define REG_CTRL_THREAD_ID_MASK GENMASK(31, 26) + +#define REG_ENDP_INTR 0x08 + +/* bits [31:26], [24:21] and [15:3] seem to be read-only */ +#define REG_ADP_BC 0x0c + #define REG_ADP_BC_VBUS_VLD_EXT_SEL BIT(0) + #define REG_ADP_BC_VBUS_VLD_EXT BIT(1) + #define REG_ADP_BC_OTG_DISABLE BIT(2) + #define REG_ADP_BC_ID_PULLUP BIT(3) + #define REG_ADP_BC_DRV_VBUS BIT(4) + #define REG_ADP_BC_ADP_PRB_EN BIT(5) + #define REG_ADP_BC_ADP_DISCHARGE BIT(6) + #define REG_ADP_BC_ADP_CHARGE BIT(7) + #define REG_ADP_BC_SESS_END BIT(8) + #define REG_ADP_BC_DEVICE_SESS_VLD BIT(9) + #define REG_ADP_BC_B_VALID BIT(10) + #define REG_ADP_BC_A_VALID BIT(11) + #define REG_ADP_BC_ID_DIG BIT(12) + #define REG_ADP_BC_VBUS_VALID BIT(13) + #define REG_ADP_BC_ADP_PROBE BIT(14) + #define REG_ADP_BC_ADP_SENSE BIT(15) + #define REG_ADP_BC_ACA_ENABLE BIT(16) + #define REG_ADP_BC_DCD_ENABLE BIT(17) + #define REG_ADP_BC_VDAT_DET_EN_B BIT(18) + #define REG_ADP_BC_VDAT_SRC_EN_B BIT(19) + #define REG_ADP_BC_CHARGE_SEL BIT(20) + #define REG_ADP_BC_CHARGE_DETECT BIT(21) + #define REG_ADP_BC_ACA_PIN_RANGE_C BIT(22) + #define REG_ADP_BC_ACA_PIN_RANGE_B BIT(23) + #define REG_ADP_BC_ACA_PIN_RANGE_A BIT(24) + #define REG_ADP_BC_ACA_PIN_GND BIT(25) + #define REG_ADP_BC_ACA_PIN_FLOAT BIT(26) + +#define REG_DBG_UART 0x10 + #define REG_DBG_UART_BYPASS_SEL BIT(0) + #define REG_DBG_UART_BYPASS_DM_EN BIT(1) + #define REG_DBG_UART_BYPASS_DP_EN BIT(2) + #define REG_DBG_UART_BYPASS_DM_DATA BIT(3) + #define REG_DBG_UART_BYPASS_DP_DATA BIT(4) + #define REG_DBG_UART_FSV_MINUS BIT(5) + #define REG_DBG_UART_FSV_PLUS BIT(6) + #define REG_DBG_UART_FSV_BURN_IN_TEST BIT(7) + #define REG_DBG_UART_LOOPBACK_EN_B BIT(8) + #define REG_DBG_UART_SET_IDDQ BIT(9) + #define REG_DBG_UART_ATE_RESET BIT(10) + +#define REG_TEST 0x14 + #define REG_TEST_DATA_IN_MASK GENMASK(3, 0) + #define REG_TEST_EN_MASK GENMASK(7, 4) + #define REG_TEST_ADDR_MASK GENMASK(11, 8) + #define REG_TEST_DATA_OUT_SEL BIT(12) + #define REG_TEST_CLK BIT(13) + #define REG_TEST_VA_TEST_EN_B_MASK GENMASK(15, 14) + #define REG_TEST_DATA_OUT_MASK GENMASK(19, 16) + #define REG_TEST_DISABLE_ID_PULLUP BIT(20) + +#define REG_TUNE 0x18 + #define REG_TUNE_TX_RES_TUNE_MASK GENMASK(1, 0) + #define REG_TUNE_TX_HSXV_TUNE_MASK GENMASK(3, 2) + #define REG_TUNE_TX_VREF_TUNE_MASK GENMASK(7, 4) + #define REG_TUNE_TX_RISE_TUNE_MASK GENMASK(9, 8) + #define REG_TUNE_TX_PREEMP_PULSE_TUNE BIT(10) + #define REG_TUNE_TX_PREEMP_AMP_TUNE_MASK GENMASK(12, 11) + #define REG_TUNE_TX_FSLS_TUNE_MASK GENMASK(16, 13) + #define REG_TUNE_SQRX_TUNE_MASK GENMASK(19, 17) + #define REG_TUNE_OTG_TUNE GENMASK(22, 20) + #define REG_TUNE_COMP_DIS_TUNE GENMASK(25, 23) + #define REG_TUNE_HOST_DM_PULLDOWN BIT(26) + #define REG_TUNE_HOST_DP_PULLDOWN BIT(27) + +#define RESET_COMPLETE_TIME 500 +#define ACA_ENABLE_COMPLETE_TIME 50 + +struct phy_meson8b_usb2_match_data { + bool host_enable_aca; +}; + +struct phy_meson8b_usb2_priv { + struct regmap *regmap; + enum usb_dr_mode dr_mode; + struct clk *clk_usb_general; + struct clk *clk_usb; + struct reset_control *reset; + const struct phy_meson8b_usb2_match_data *match; +}; + +static const struct regmap_config phy_meson8b_usb2_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = REG_TUNE, +}; + +static int phy_meson8b_usb2_power_on(struct phy *phy) +{ + struct phy_meson8b_usb2_priv *priv = phy_get_drvdata(phy); + u32 reg; + int ret; + + if (!IS_ERR_OR_NULL(priv->reset)) { + ret = reset_control_reset(priv->reset); + if (ret) { + dev_err(&phy->dev, "Failed to trigger USB reset\n"); + return ret; + } + } + + ret = clk_prepare_enable(priv->clk_usb_general); + if (ret) { + dev_err(&phy->dev, "Failed to enable USB general clock\n"); + reset_control_rearm(priv->reset); + return ret; + } + + ret = clk_prepare_enable(priv->clk_usb); + if (ret) { + dev_err(&phy->dev, "Failed to enable USB DDR clock\n"); + clk_disable_unprepare(priv->clk_usb_general); + reset_control_rearm(priv->reset); + return ret; + } + + regmap_update_bits(priv->regmap, REG_CONFIG, REG_CONFIG_CLK_32k_ALTSEL, + REG_CONFIG_CLK_32k_ALTSEL); + + regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_REF_CLK_SEL_MASK, + 0x2 << REG_CTRL_REF_CLK_SEL_SHIFT); + + regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_FSEL_MASK, + 0x5 << REG_CTRL_FSEL_SHIFT); + + /* reset the PHY */ + regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_POWER_ON_RESET, + REG_CTRL_POWER_ON_RESET); + udelay(RESET_COMPLETE_TIME); + regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_POWER_ON_RESET, 0); + udelay(RESET_COMPLETE_TIME); + + regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_SOF_TOGGLE_OUT, + REG_CTRL_SOF_TOGGLE_OUT); + + if (priv->dr_mode == USB_DR_MODE_HOST) { + regmap_update_bits(priv->regmap, REG_DBG_UART, + REG_DBG_UART_SET_IDDQ, 0); + + if (priv->match->host_enable_aca) { + regmap_update_bits(priv->regmap, REG_ADP_BC, + REG_ADP_BC_ACA_ENABLE, + REG_ADP_BC_ACA_ENABLE); + + udelay(ACA_ENABLE_COMPLETE_TIME); + + regmap_read(priv->regmap, REG_ADP_BC, ®); + if (reg & REG_ADP_BC_ACA_PIN_FLOAT) { + dev_warn(&phy->dev, "USB ID detect failed!\n"); + clk_disable_unprepare(priv->clk_usb); + clk_disable_unprepare(priv->clk_usb_general); + reset_control_rearm(priv->reset); + return -EINVAL; + } + } + } + + return 0; +} + +static int phy_meson8b_usb2_power_off(struct phy *phy) +{ + struct phy_meson8b_usb2_priv *priv = phy_get_drvdata(phy); + + if (priv->dr_mode == USB_DR_MODE_HOST) + regmap_update_bits(priv->regmap, REG_DBG_UART, + REG_DBG_UART_SET_IDDQ, + REG_DBG_UART_SET_IDDQ); + + clk_disable_unprepare(priv->clk_usb); + clk_disable_unprepare(priv->clk_usb_general); + reset_control_rearm(priv->reset); + + /* power off the PHY by putting it into reset mode */ + regmap_update_bits(priv->regmap, REG_CTRL, REG_CTRL_POWER_ON_RESET, + REG_CTRL_POWER_ON_RESET); + + return 0; +} + +static const struct phy_ops phy_meson8b_usb2_ops = { + .power_on = phy_meson8b_usb2_power_on, + .power_off = phy_meson8b_usb2_power_off, + .owner = THIS_MODULE, +}; + +static int phy_meson8b_usb2_probe(struct platform_device *pdev) +{ + struct phy_meson8b_usb2_priv *priv; + struct phy *phy; + struct phy_provider *phy_provider; + void __iomem *base; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->match = device_get_match_data(&pdev->dev); + if (!priv->match) + return -ENODEV; + + priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, + &phy_meson8b_usb2_regmap_conf); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->clk_usb_general = devm_clk_get(&pdev->dev, "usb_general"); + if (IS_ERR(priv->clk_usb_general)) + return PTR_ERR(priv->clk_usb_general); + + priv->clk_usb = devm_clk_get(&pdev->dev, "usb"); + if (IS_ERR(priv->clk_usb)) + return PTR_ERR(priv->clk_usb); + + priv->reset = devm_reset_control_get_optional_shared(&pdev->dev, NULL); + if (IS_ERR(priv->reset)) + return dev_err_probe(&pdev->dev, PTR_ERR(priv->reset), + "Failed to get the reset line"); + + priv->dr_mode = of_usb_get_dr_mode_by_phy(pdev->dev.of_node, -1); + if (priv->dr_mode == USB_DR_MODE_UNKNOWN) { + dev_err(&pdev->dev, + "missing dual role configuration of the controller\n"); + return -EINVAL; + } + + phy = devm_phy_create(&pdev->dev, NULL, &phy_meson8b_usb2_ops); + if (IS_ERR(phy)) { + return dev_err_probe(&pdev->dev, PTR_ERR(phy), + "failed to create PHY\n"); + } + + phy_set_drvdata(phy, priv); + + phy_provider = + devm_of_phy_provider_register(&pdev->dev, of_phy_simple_xlate); + + return PTR_ERR_OR_ZERO(phy_provider); +} + +static const struct phy_meson8b_usb2_match_data phy_meson8_usb2_match_data = { + .host_enable_aca = false, +}; + +static const struct phy_meson8b_usb2_match_data phy_meson8b_usb2_match_data = { + .host_enable_aca = true, +}; + +static const struct of_device_id phy_meson8b_usb2_of_match[] = { + { + .compatible = "amlogic,meson8-usb2-phy", + .data = &phy_meson8_usb2_match_data + }, + { + .compatible = "amlogic,meson8b-usb2-phy", + .data = &phy_meson8b_usb2_match_data + }, + { + .compatible = "amlogic,meson8m2-usb2-phy", + .data = &phy_meson8b_usb2_match_data + }, + { + .compatible = "amlogic,meson-gxbb-usb2-phy", + .data = &phy_meson8b_usb2_match_data + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, phy_meson8b_usb2_of_match); + +static struct platform_driver phy_meson8b_usb2_driver = { + .probe = phy_meson8b_usb2_probe, + .driver = { + .name = "phy-meson-usb2", + .of_match_table = phy_meson8b_usb2_of_match, + }, +}; +module_platform_driver(phy_meson8b_usb2_driver); + +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); +MODULE_DESCRIPTION("Meson8, Meson8b, Meson8m2 and GXBB USB2 PHY driver"); +MODULE_LICENSE("GPL"); |