mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-29 08:01:08 +00:00
888e1caeae
Add a new SERDES driver for TI's AM654x SoC which configures the SERDES only for PCIe. Support fo USB3 can be added later. SERDES in am654x has three input clocks (left input, external reference clock and right input) and two output clocks (left output and right output) in addition to a PLL mux clock which the SERDES uses for Clock Multiplier Unit (CMU refclock). The PLL mux clock can select from one of the three input clocks. The right output can select between left input and external reference clock while the left output can select between the right input and external reference clock. The driver has support to select PLL mux and left/right output mux as specified in device tree. Signed-off-by: Sekhar Nori <nsekhar@ti.com>
411 lines
9.7 KiB
C
411 lines
9.7 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/**
|
|
* PCIe SERDES driver for AM654x SoC
|
|
*
|
|
* Copyright (C) 2018 Texas Instruments
|
|
* Author: Kishon Vijay Abraham I <kishon@ti.com>
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <clk-uclass.h>
|
|
#include <dm.h>
|
|
#include <dm/device.h>
|
|
#include <dm/lists.h>
|
|
#include <dt-bindings/phy/phy.h>
|
|
#include <generic-phy.h>
|
|
#include <asm/io.h>
|
|
#include <asm/arch/sys_proto.h>
|
|
#include <power-domain.h>
|
|
#include <regmap.h>
|
|
#include <syscon.h>
|
|
|
|
#define CMU_R07C 0x7c
|
|
#define CMU_MASTER_CDN_O BIT(24)
|
|
|
|
#define COMLANE_R138 0xb38
|
|
#define CONFIG_VERSION_REG_MASK GENMASK(23, 16)
|
|
#define CONFIG_VERSION_REG_SHIFT 16
|
|
#define VERSION 0x70
|
|
|
|
#define COMLANE_R190 0xb90
|
|
#define L1_MASTER_CDN_O BIT(9)
|
|
|
|
#define COMLANE_R194 0xb94
|
|
#define CMU_OK_I_0 BIT(19)
|
|
|
|
#define SERDES_CTRL 0x1fd0
|
|
#define POR_EN BIT(29)
|
|
|
|
#define WIZ_LANEXCTL_STS 0x1fe0
|
|
#define TX0_ENABLE_OVL BIT(31)
|
|
#define TX0_ENABLE_MASK GENMASK(30, 29)
|
|
#define TX0_ENABLE_SHIFT 29
|
|
#define TX0_DISABLE_STATE 0x0
|
|
#define TX0_SLEEP_STATE 0x1
|
|
#define TX0_SNOOZE_STATE 0x2
|
|
#define TX0_ENABLE_STATE 0x3
|
|
#define RX0_ENABLE_OVL BIT(15)
|
|
#define RX0_ENABLE_MASK GENMASK(14, 13)
|
|
#define RX0_ENABLE_SHIFT 13
|
|
#define RX0_DISABLE_STATE 0x0
|
|
#define RX0_SLEEP_STATE 0x1
|
|
#define RX0_SNOOZE_STATE 0x2
|
|
#define RX0_ENABLE_STATE 0x3
|
|
|
|
#define WIZ_PLL_CTRL 0x1ff4
|
|
#define PLL_ENABLE_OVL BIT(31)
|
|
#define PLL_ENABLE_MASK GENMASK(30, 29)
|
|
#define PLL_ENABLE_SHIFT 29
|
|
#define PLL_DISABLE_STATE 0x0
|
|
#define PLL_SLEEP_STATE 0x1
|
|
#define PLL_SNOOZE_STATE 0x2
|
|
#define PLL_ENABLE_STATE 0x3
|
|
#define PLL_OK BIT(28)
|
|
|
|
#define PLL_LOCK_TIME 1000 /* in milliseconds */
|
|
#define SLEEP_TIME 100 /* in microseconds */
|
|
|
|
#define LANE_USB3 0x0
|
|
#define LANE_PCIE0_LANE0 0x1
|
|
|
|
#define LANE_PCIE1_LANE0 0x0
|
|
#define LANE_PCIE0_LANE1 0x1
|
|
|
|
#define SERDES_NUM_CLOCKS 3
|
|
|
|
/* SERDES control MMR bit offsets */
|
|
#define SERDES_CTL_LANE_FUNC_SEL_SHIFT 0
|
|
#define SERDES_CTL_LANE_FUNC_SEL_MASK GENMASK(1, 0)
|
|
#define SERDES_CTL_CLK_SEL_SHIFT 4
|
|
#define SERDES_CTL_CLK_SEL_MASK GENMASK(7, 4)
|
|
|
|
/**
|
|
* struct serdes_am654_mux_clk_data - clock controller information structure
|
|
*/
|
|
struct serdes_am654_mux_clk_data {
|
|
struct regmap *regmap;
|
|
struct clk_bulk parents;
|
|
};
|
|
|
|
static int serdes_am654_mux_clk_probe(struct udevice *dev)
|
|
{
|
|
struct serdes_am654_mux_clk_data *data = dev_get_priv(dev);
|
|
struct udevice *syscon;
|
|
struct regmap *regmap;
|
|
int ret;
|
|
|
|
debug("%s(dev=%s)\n", __func__, dev->name);
|
|
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
ret = uclass_get_device_by_phandle(UCLASS_SYSCON, dev,
|
|
"ti,serdes-clk", &syscon);
|
|
if (ret) {
|
|
dev_err(dev, "unable to find syscon device\n");
|
|
return ret;
|
|
}
|
|
|
|
regmap = syscon_get_regmap(syscon);
|
|
if (IS_ERR(regmap)) {
|
|
dev_err(dev, "Fail to get Syscon regmap\n");
|
|
return PTR_ERR(regmap);
|
|
}
|
|
|
|
data->regmap = regmap;
|
|
|
|
ret = clk_get_bulk(dev, &data->parents);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to obtain parent clocks\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mux_table[SERDES_NUM_CLOCKS][3] = {
|
|
/*
|
|
* The entries represent values for selecting between
|
|
* {left input, external reference clock, right input}
|
|
* Only one of Left Output or Right Output should be used since
|
|
* both left and right output clock uses the same bits and modifying
|
|
* one clock will impact the other.
|
|
*/
|
|
{ BIT(2), 0, BIT(0) }, /* Mux of CMU refclk */
|
|
{ -1, BIT(3), BIT(1) }, /* Mux of Left Output */
|
|
{ BIT(1), BIT(3) | BIT(1), -1 }, /* Mux of Right Output */
|
|
};
|
|
|
|
static int serdes_am654_mux_clk_set_parent(struct clk *clk, struct clk *parent)
|
|
{
|
|
struct serdes_am654_mux_clk_data *data = dev_get_priv(clk->dev);
|
|
u32 val;
|
|
int i;
|
|
|
|
debug("%s(clk=%s, parent=%s)\n", __func__, clk->dev->name,
|
|
parent->dev->name);
|
|
|
|
/*
|
|
* Since we have the same device-tree node represent both the
|
|
* clock and serdes device, we have two devices associated with
|
|
* the serdes node. assigned-clocks for this node is processed twice,
|
|
* once for the clock device and another time for the serdes
|
|
* device. When it is processed for the clock device, it is before
|
|
* the probe for clock device has been called. We ignore this case
|
|
* and rely on assigned-clocks to be processed correctly for the
|
|
* serdes case.
|
|
*/
|
|
if (!data->regmap)
|
|
return 0;
|
|
|
|
for (i = 0; i < data->parents.count; i++) {
|
|
if (clk_is_match(&data->parents.clks[i], parent))
|
|
break;
|
|
}
|
|
|
|
if (i >= data->parents.count)
|
|
return -EINVAL;
|
|
|
|
val = mux_table[clk->id][i];
|
|
val <<= SERDES_CTL_CLK_SEL_SHIFT;
|
|
|
|
regmap_update_bits(data->regmap, 0, SERDES_CTL_CLK_SEL_MASK, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct clk_ops serdes_am654_mux_clk_ops = {
|
|
.set_parent = serdes_am654_mux_clk_set_parent,
|
|
};
|
|
|
|
U_BOOT_DRIVER(serdes_am654_mux_clk) = {
|
|
.name = "ti-serdes-am654-mux-clk",
|
|
.id = UCLASS_CLK,
|
|
.probe = serdes_am654_mux_clk_probe,
|
|
.priv_auto_alloc_size = sizeof(struct serdes_am654_mux_clk_data),
|
|
.ops = &serdes_am654_mux_clk_ops,
|
|
};
|
|
|
|
struct serdes_am654 {
|
|
struct regmap *regmap;
|
|
struct regmap *serdes_ctl;
|
|
};
|
|
|
|
static int serdes_am654_enable_pll(struct serdes_am654 *phy)
|
|
{
|
|
u32 mask = PLL_ENABLE_OVL | PLL_ENABLE_MASK;
|
|
u32 val = PLL_ENABLE_OVL | (PLL_ENABLE_STATE << PLL_ENABLE_SHIFT);
|
|
|
|
regmap_update_bits(phy->regmap, WIZ_PLL_CTRL, mask, val);
|
|
|
|
return regmap_read_poll_timeout(phy->regmap, WIZ_PLL_CTRL, val,
|
|
val & PLL_OK, 1000, PLL_LOCK_TIME);
|
|
}
|
|
|
|
static void serdes_am654_disable_pll(struct serdes_am654 *phy)
|
|
{
|
|
u32 mask = PLL_ENABLE_OVL | PLL_ENABLE_MASK;
|
|
|
|
regmap_update_bits(phy->regmap, WIZ_PLL_CTRL, mask, 0);
|
|
}
|
|
|
|
static int serdes_am654_enable_txrx(struct serdes_am654 *phy)
|
|
{
|
|
u32 mask;
|
|
u32 val;
|
|
|
|
/* Enable TX */
|
|
mask = TX0_ENABLE_OVL | TX0_ENABLE_MASK;
|
|
val = TX0_ENABLE_OVL | (TX0_ENABLE_STATE << TX0_ENABLE_SHIFT);
|
|
regmap_update_bits(phy->regmap, WIZ_LANEXCTL_STS, mask, val);
|
|
|
|
/* Enable RX */
|
|
mask = RX0_ENABLE_OVL | RX0_ENABLE_MASK;
|
|
val = RX0_ENABLE_OVL | (RX0_ENABLE_STATE << RX0_ENABLE_SHIFT);
|
|
regmap_update_bits(phy->regmap, WIZ_LANEXCTL_STS, mask, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int serdes_am654_disable_txrx(struct serdes_am654 *phy)
|
|
{
|
|
u32 mask;
|
|
|
|
/* Disable TX */
|
|
mask = TX0_ENABLE_OVL | TX0_ENABLE_MASK;
|
|
regmap_update_bits(phy->regmap, WIZ_LANEXCTL_STS, mask, 0);
|
|
|
|
/* Disable RX */
|
|
mask = RX0_ENABLE_OVL | RX0_ENABLE_MASK;
|
|
regmap_update_bits(phy->regmap, WIZ_LANEXCTL_STS, mask, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int serdes_am654_power_on(struct phy *x)
|
|
{
|
|
struct serdes_am654 *phy = dev_get_priv(x->dev);
|
|
int ret;
|
|
u32 val;
|
|
|
|
ret = serdes_am654_enable_pll(phy);
|
|
if (ret) {
|
|
dev_err(x->dev, "Failed to enable PLL\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = serdes_am654_enable_txrx(phy);
|
|
if (ret) {
|
|
dev_err(x->dev, "Failed to enable TX RX\n");
|
|
return ret;
|
|
}
|
|
|
|
return regmap_read_poll_timeout(phy->regmap, COMLANE_R194, val,
|
|
val & CMU_OK_I_0, SLEEP_TIME,
|
|
PLL_LOCK_TIME);
|
|
}
|
|
|
|
static int serdes_am654_power_off(struct phy *x)
|
|
{
|
|
struct serdes_am654 *phy = dev_get_priv(x->dev);
|
|
|
|
serdes_am654_disable_txrx(phy);
|
|
serdes_am654_disable_pll(phy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int serdes_am654_init(struct phy *x)
|
|
{
|
|
struct serdes_am654 *phy = dev_get_priv(x->dev);
|
|
u32 mask;
|
|
u32 val;
|
|
|
|
mask = CONFIG_VERSION_REG_MASK;
|
|
val = VERSION << CONFIG_VERSION_REG_SHIFT;
|
|
regmap_update_bits(phy->regmap, COMLANE_R138, mask, val);
|
|
|
|
val = CMU_MASTER_CDN_O;
|
|
regmap_update_bits(phy->regmap, CMU_R07C, val, val);
|
|
|
|
val = L1_MASTER_CDN_O;
|
|
regmap_update_bits(phy->regmap, COMLANE_R190, val, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int serdes_am654_reset(struct phy *x)
|
|
{
|
|
struct serdes_am654 *phy = dev_get_priv(x->dev);
|
|
u32 val;
|
|
|
|
val = POR_EN;
|
|
regmap_update_bits(phy->regmap, SERDES_CTRL, val, val);
|
|
mdelay(1);
|
|
regmap_update_bits(phy->regmap, SERDES_CTRL, val, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int serdes_am654_of_xlate(struct phy *x,
|
|
struct ofnode_phandle_args *args)
|
|
{
|
|
struct serdes_am654 *phy = dev_get_priv(x->dev);
|
|
|
|
if (args->args_count != 2) {
|
|
dev_err(phy->dev, "Invalid DT PHY argument count: %d\n",
|
|
args->args_count);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (args->args[0] != PHY_TYPE_PCIE) {
|
|
dev_err(phy->dev, "Unrecognized PHY type: %d\n",
|
|
args->args[0]);
|
|
return -EINVAL;
|
|
}
|
|
|
|
x->id = args->args[0] | (args->args[1] << 16);
|
|
|
|
/* Setup mux mode using second argument */
|
|
regmap_update_bits(phy->serdes_ctl, 0, SERDES_CTL_LANE_FUNC_SEL_MASK,
|
|
args->args[1]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int serdes_am654_bind(struct udevice *dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = device_bind_driver_to_node(dev->parent,
|
|
"ti-serdes-am654-mux-clk",
|
|
dev_read_name(dev), dev->node,
|
|
NULL);
|
|
if (ret) {
|
|
dev_err(dev, "%s: not able to bind clock driver\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int serdes_am654_probe(struct udevice *dev)
|
|
{
|
|
struct serdes_am654 *phy = dev_get_priv(dev);
|
|
struct power_domain serdes_pwrdmn;
|
|
struct regmap *serdes_ctl;
|
|
struct regmap *map;
|
|
int ret;
|
|
|
|
ret = regmap_init_mem(dev_ofnode(dev), &map);
|
|
if (ret)
|
|
return ret;
|
|
|
|
phy->regmap = map;
|
|
|
|
serdes_ctl = syscon_regmap_lookup_by_phandle(dev, "ti,serdes-clk");
|
|
if (IS_ERR(serdes_ctl)) {
|
|
dev_err(dev, "unable to find syscon device\n");
|
|
return PTR_ERR(serdes_ctl);
|
|
}
|
|
|
|
phy->serdes_ctl = serdes_ctl;
|
|
|
|
ret = power_domain_get_by_index(dev, &serdes_pwrdmn, 0);
|
|
if (ret) {
|
|
dev_err(dev, "failed to get power domain\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = power_domain_on(&serdes_pwrdmn);
|
|
if (ret) {
|
|
dev_err(dev, "Power domain on failed\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct udevice_id serdes_am654_phy_ids[] = {
|
|
{
|
|
.compatible = "ti,phy-am654-serdes",
|
|
},
|
|
};
|
|
|
|
static const struct phy_ops serdes_am654_phy_ops = {
|
|
.reset = serdes_am654_reset,
|
|
.init = serdes_am654_init,
|
|
.power_on = serdes_am654_power_on,
|
|
.power_off = serdes_am654_power_off,
|
|
.of_xlate = serdes_am654_of_xlate,
|
|
};
|
|
|
|
U_BOOT_DRIVER(am654_serdes_phy) = {
|
|
.name = "am654_serdes_phy",
|
|
.id = UCLASS_PHY,
|
|
.of_match = serdes_am654_phy_ids,
|
|
.bind = serdes_am654_bind,
|
|
.ops = &serdes_am654_phy_ops,
|
|
.probe = serdes_am654_probe,
|
|
.priv_auto_alloc_size = sizeof(struct serdes_am654),
|
|
};
|