diff --git a/drivers/soc/fsl/guts.c b/drivers/soc/fsl/guts.c index c283d44b68a3..28361c63e26c 100644 --- a/drivers/soc/fsl/guts.c +++ b/drivers/soc/fsl/guts.c @@ -5,7 +5,10 @@ * Copyright (C) 2016 Freescale Semiconductor, Inc. */ +#include +#include #include +#include #include #include #include @@ -15,6 +18,28 @@ #include #define DCFG_CCSR 0 +#define DCFG_DCSR 1 + +#define RCW_TIMEOUT_US 1 + +#define LS1046A_RCWSR5_SRDS_PRTCL_S1(lane) \ + GENMASK(19 + 4 * (lane), 16 + 4 * (lane)) +#define LS1046A_SRDS_PRTCL_XFI 1 +#define LS1046A_SRDS_PRTCL_100BASEX_SGMII 3 + +#define LS1088A_RCWSR29_SRDS_PRTCL_S1_LNn(lane) \ + GENMASK(19 + 4 * (3 - (lane)), 16 + 4 * (3 - (lane))) +#define LS1088A_RCWSR30_SRDS_PRTCL_S2_LNn(lane) \ + GENMASK(3 + 4 * (3 - (lane)), 4 * (3 - (lane))) +#define LS1088A_SRDS_PRTCL_XFI 1 +#define LS1088A_SRDS_PRTCL_100BASEX_SGMII 3 + +#define LS2088A_RCWSR29_SRDS_PRTCL_S1 GENMASK(23, 16) +#define LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1 BIT(14) +#define LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(lane) BIT(6 + (7 - (lane))) +#define LS2088A_RCWSR30_SRDS_CLK_SEL_MSK GENMASK(13, 6) +#define LS2088A_SRDS_CLK_SEL_XGMII 1 +#define LS2088A_SRDS_CLK_SEL_GMII 0 struct fsl_soc_die_attr { char *die; @@ -22,9 +47,21 @@ struct fsl_soc_die_attr { u32 mask; }; +struct fsl_soc_serdes_rcw_override { + int offset; + int mask; + int val; +}; + struct fsl_soc_data { const char *sfp_compat; u32 uid_offset; + int num_serdes_blocks; + int num_serdes_lanes; + int (*serdes_init_rcwcr)(int index); + int (*serdes_get_rcw_override)(int index, int lane, + enum lynx_lane_mode lane_mode, + struct fsl_soc_serdes_rcw_override *override); }; enum qoriq_die { @@ -44,6 +81,48 @@ enum qoriq_die { DIE_MAX, }; +struct rcw_full_srds_proto { + int srds_proto_val; + unsigned long xgmii_lanes; +}; + +static const struct rcw_full_srds_proto ls2088a_rcw_full_srds1_protos[] = { + { 0x05, 0 }, + { 0x07, 0 }, + { 0x09, 0 }, + { 0x0a, 0 }, + { 0x0c, 0 }, + { 0x0e, 0 }, + { 0x10, 0 }, + { 0x12, 0 }, + { 0x14, 0 }, + { 0x16, 0 }, + { 0x18, 0 }, + { 0x1a, 0 }, + { 0x1c, 0 }, + { 0x1e, 0 }, + { 0x20, 0 }, + { 0x22, 0 }, + { 0x24, 0 }, + { 0x26, GENMASK(7, 6) }, + { 0x28, GENMASK(7, 4) }, + { 0x2a, GENMASK(7, 0) }, + /* 0x2b: unimplemented because of XAUI1 (lanes E-H) */ + /* 0x2d: unimplemented because of XAUI1 (lanes E-H) */ + /* 0x2e: unimplemented because of XAUI1 (lanes E-H) */ + /* 0x30: unimplemented because of XAUI1 (lanes E-H) */ + /* 0x32: unimplemented because of XAUI1 (lanes E-H) and XAUI2 (A-D) */ + /* 0x33: unimplemented because of QSGMII (lanes E-H) */ + /* 0x35: unimplemented because of QSGMII (lanes A-C) */ + /* 0x37: unimplemented because of QSGMII (lanes E-F) */ + { 0x39, 0 }, + { 0x3b, GENMASK(6, 4) | GENMASK(2, 0) }, + { 0x4b, GENMASK(7, 4) }, + { 0x4c, GENMASK(3, 0) }, + { 0x4d, 0 }, + { 0x58, 0 }, +}; + /* SoC die attribute definition for QorIQ platform */ static const struct fsl_soc_die_attr fsl_soc_die[] = { /* @@ -138,9 +217,11 @@ static const struct fsl_soc_die_attr fsl_soc_die[] = { static struct fsl_soc_guts { struct ccsr_guts __iomem *dcfg_ccsr; + struct ccsr_guts __iomem *dcfg_dcsr; const struct fsl_soc_data *data; bool little_endian; u32 svr; + spinlock_t rcwcr_lock; /* serializes concurrent writes to the RCWCR */ } soc; static unsigned int fsl_guts_read(const void __iomem *reg) @@ -151,6 +232,36 @@ static unsigned int fsl_guts_read(const void __iomem *reg) return ioread32be(reg); } +static void fsl_guts_write(void __iomem *reg, u32 val) +{ + if (soc.little_endian) + iowrite32(val, reg); + else + iowrite32be(val, reg); +} + +/* Some fields of the Reset Configuration Word (RCW) can be overridden at + * runtime by writing to the RCWCRn registers contained within the DCSR space + * of the Device Configuration (DCFG) block. The layout of the RCWCRn registers + * is identical with the read-only RCWSRn from the CCSR space. + */ +static int fsl_guts_rcw_rmw(int offset, u32 val, u32 mask) +{ + u32 rcwcr, rcwsr = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[offset]); + + rcwcr = rcwsr & ~mask; + rcwcr |= val; + fsl_guts_write(&soc.dcfg_dcsr->rcwcr[offset], rcwcr); + + pr_debug("RCW override: RCWSR%d 0x%x -> RCWCR%d 0x%x\n", + offset + 1, rcwsr, offset + 1, rcwcr); + + /* Updates to RCWCR should be visible back in RCWSR immediately */ + return read_poll_timeout_atomic(fsl_guts_read, rcwsr, rcwsr == rcwcr, + 0, RCW_TIMEOUT_US, false, + &soc.dcfg_ccsr->rcwsr[offset]); +} + static bool fsl_soc_die_match_one(u32 svr, const struct fsl_soc_die_attr *match) { return match->svr == (svr & match->mask); @@ -167,6 +278,97 @@ static const struct fsl_soc_die_attr *fsl_soc_die_match( return NULL; } +static int +fsl_guts_serdes_get_rcw_override(int serdes_idx, int lane, + enum lynx_lane_mode lane_mode, + struct fsl_soc_serdes_rcw_override *override) +{ + const struct fsl_soc_data *soc_data = soc.data; + + if (!soc_data) + return -ENODEV; + + /* serdes_idx is one-based */ + if (serdes_idx > soc_data->num_serdes_blocks || serdes_idx <= 0) + return -ERANGE; + + if (lane >= soc_data->num_serdes_lanes || lane < 0) + return -ERANGE; + + if (!soc_data->serdes_get_rcw_override) { + pr_debug("RCW override not implemented for SoC\n"); + return -EINVAL; + } + + if (!soc.dcfg_dcsr) { + pr_debug("Device tree does not define DCFG_DCSR region necessary for RCW override\n"); + return -EINVAL; + } + + return soc_data->serdes_get_rcw_override(serdes_idx, lane, lane_mode, + override); +} + +/** + * fsl_guts_lane_validate() - Validate that SerDes protocol is implemented and + * supported on current SoC + * @serdes_idx: one-based SerDes block index + * @lane: zero-based lane index within SerDes + * @lane_mode: requested SerDes protocol + * + * Should be called before actually requesting the RCW override procedure to be + * applied using %fsl_guts_lane_set_mode() + * + * Return: 0 if RCW override to protocol is possible, negative error otherwise + */ +int fsl_guts_lane_validate(int serdes_idx, int lane, enum lynx_lane_mode lane_mode) +{ + struct fsl_soc_serdes_rcw_override override; + + return fsl_guts_serdes_get_rcw_override(serdes_idx, lane, lane_mode, + &override); +} +EXPORT_SYMBOL_NS_GPL(fsl_guts_lane_validate, "FSL_GUTS"); + +/** + * fsl_guts_lane_set_mode() - apply RCW override procedure for SerDes lane + * @serdes_idx: one-based SerDes block index + * @lane: zero-based lane index within SerDes + * @lane_mode: requested SerDes protocol + * + * Return: 0 on success, negative error otherwise + */ +int fsl_guts_lane_set_mode(int serdes_idx, int lane, enum lynx_lane_mode lane_mode) +{ + struct fsl_soc_serdes_rcw_override override; + int err; + + err = fsl_guts_serdes_get_rcw_override(serdes_idx, lane, lane_mode, + &override); + if (err) + return err; + + spin_lock(&soc.rcwcr_lock); + + if (soc.data->serdes_init_rcwcr) { + err = soc.data->serdes_init_rcwcr(serdes_idx); + if (err) + goto out_unlock; + } + + err = fsl_guts_rcw_rmw(override.offset, + override.val << __ffs(override.mask), + override.mask); + if (err) + pr_err("RCW override failed: %pe\n", ERR_PTR(err)); + +out_unlock: + spin_unlock(&soc.rcwcr_lock); + + return err; +} +EXPORT_SYMBOL_NS_GPL(fsl_guts_lane_set_mode, "FSL_GUTS"); + static u64 fsl_guts_get_soc_uid(const char *compat, unsigned int offset) { struct device_node *np; @@ -193,9 +395,188 @@ static u64 fsl_guts_get_soc_uid(const char *compat, unsigned int offset) return uid; } +static int ls1046a_serdes_get_rcw_override(int index, int lane, + enum lynx_lane_mode lane_mode, + struct fsl_soc_serdes_rcw_override *override) +{ + /* The RCW override procedure has to write to different registers + * depending on the SerDes block index. + */ + switch (index) { + case 1: + override->offset = 4; + override->mask = LS1046A_RCWSR5_SRDS_PRTCL_S1(lane); + break; + default: + return -EINVAL; + } + + if (lynx_lane_mode_uses_xgmii_mac(lane_mode)) + override->val = LS1046A_SRDS_PRTCL_XFI; + else if (lynx_lane_mode_uses_gmii_mac(lane_mode)) + override->val = LS1046A_SRDS_PRTCL_100BASEX_SGMII; + else + return -EINVAL; + + return 0; +} + +static int ls1088a_serdes_get_rcw_override(int index, int lane, + enum lynx_lane_mode lane_mode, + struct fsl_soc_serdes_rcw_override *override) +{ + /* The RCW override procedure has to write to different registers + * depending on the SerDes block index. + */ + switch (index) { + case 1: + override->offset = 28; + override->mask = LS1088A_RCWSR29_SRDS_PRTCL_S1_LNn(lane); + break; + case 2: + override->offset = 29; + override->mask = LS1088A_RCWSR30_SRDS_PRTCL_S2_LNn(lane); + break; + default: + return -EINVAL; + } + + if (lynx_lane_mode_uses_xgmii_mac(lane_mode)) + override->val = LS1088A_SRDS_PRTCL_XFI; + else if (lynx_lane_mode_uses_gmii_mac(lane_mode)) + override->val = LS1088A_SRDS_PRTCL_100BASEX_SGMII; + else + return -EINVAL; + + return 0; +} + +static const struct rcw_full_srds_proto *ls2088a_get_full_serdes1_proto(void) +{ + u32 rcwsr29 = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[28]); + u32 srds_prtcl_s1 = FIELD_GET(LS2088A_RCWSR29_SRDS_PRTCL_S1, rcwsr29); + + for (int i = 0; i < ARRAY_SIZE(ls2088a_rcw_full_srds1_protos); i++) { + const struct rcw_full_srds_proto *proto; + + proto = &ls2088a_rcw_full_srds1_protos[i]; + if (proto->srds_proto_val == srds_prtcl_s1) + return proto; + } + + return NULL; +} + +static int ls2088a_serdes_get_rcw_override(int index, int lane, + enum lynx_lane_mode lane_mode, + struct fsl_soc_serdes_rcw_override *override) +{ + switch (index) { + case 1: + override->offset = 29; + override->mask = LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(lane); + break; + default: + return -EINVAL; + } + + /* RCW override only supported if we know how to handle the initial + * RCWSR29[SRDS_PRTCL_S1] value and turn it into an override. + */ + if (!ls2088a_get_full_serdes1_proto()) { + u32 rcwsr30 = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[29]); + + /* If a SerDes-level override is already in place (probably + * left there by a previous boot stage), use it. + */ + if (!(rcwsr30 & LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1)) + return -EINVAL; + } + + if (lynx_lane_mode_uses_xgmii_mac(lane_mode)) + override->val = LS2088A_SRDS_CLK_SEL_XGMII; + else if (lynx_lane_mode_uses_gmii_mac(lane_mode)) + override->val = LS2088A_SRDS_CLK_SEL_GMII; + else + return -EINVAL; + + return 0; +} + +static int ls2088a_serdes_init_rcwcr(int serdes_idx) +{ + const struct rcw_full_srds_proto *srds_prtcl_s1; + const struct fsl_soc_data *soc_data = soc.data; + u32 rcwsr30; + int i, err; + + /* SerDes 2 supports only SGMII for networking. There should be + * no need for RCW override + */ + if (serdes_idx != 1) + return -EINVAL; + + /* SRDS_CLK_EN_SEL_XGMII_S1: SerDes Clock Enable Select XGMII Serdes 1: + * Enables to select GMII/XGMII clock according to + * SRDS_CLK_SEL_XGMII_Ln_S1. + * If the GMII/XGMII select override has already been set, use it. + * Otherwise, derive an initial override for all lanes based on the + * full SerDes protocol table. + */ + rcwsr30 = fsl_guts_read(&soc.dcfg_ccsr->rcwsr[29]); + if (rcwsr30 & LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1) { + pr_debug("RCWSR30 = 0x%x, using this.\n", rcwsr30); + return 0; + } + + srds_prtcl_s1 = ls2088a_get_full_serdes1_proto(); + + rcwsr30 = LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1; + + /* We need to configure the initial state of all lanes for + * the SerDes block #1 + */ + for_each_set_bit(i, &srds_prtcl_s1->xgmii_lanes, soc_data->num_serdes_lanes) + rcwsr30 |= LS2088A_RCWSR30_SRDS_CLK_SEL_XGMII_Ln_S1(i); + + pr_debug("Setting initial RCWSR30 = 0x%x based on SRDS_PRTCL_S1 = 0x%x\n", + rcwsr30, srds_prtcl_s1->srds_proto_val); + + err = fsl_guts_rcw_rmw(29, rcwsr30, + LS2088A_RCWSR30_SRDS_CLK_EN_SEL_XGMII_S1 | + LS2088A_RCWSR30_SRDS_CLK_SEL_MSK); + if (err) { + pr_err("Setting up initial RCWCR failed: %pe\n", ERR_PTR(err)); + return err; + } + + return 0; +} + +static const struct fsl_soc_data ls1088a_data = { + .serdes_get_rcw_override = ls1088a_serdes_get_rcw_override, + .num_serdes_blocks = 2, + .num_serdes_lanes = 4, +}; + +static const struct fsl_soc_data ls1046a_data = { + .serdes_get_rcw_override = ls1046a_serdes_get_rcw_override, + .num_serdes_blocks = 2, + .num_serdes_lanes = 4, +}; + +static const struct fsl_soc_data ls2088a_data = { + .serdes_get_rcw_override = ls2088a_serdes_get_rcw_override, + .serdes_init_rcwcr = ls2088a_serdes_init_rcwcr, + .num_serdes_blocks = 2, + .num_serdes_lanes = 8, +}; + static const struct fsl_soc_data ls1028a_data = { .sfp_compat = "fsl,ls1028a-sfp", .uid_offset = 0x21c, + .num_serdes_blocks = 1, + .num_serdes_lanes = 4, }; /* @@ -221,10 +602,10 @@ static const struct of_device_id fsl_guts_of_match[] = { { .compatible = "fsl,mpc8572-guts", }, { .compatible = "fsl,ls1021a-dcfg", }, { .compatible = "fsl,ls1043a-dcfg", }, - { .compatible = "fsl,ls2080a-dcfg", }, - { .compatible = "fsl,ls1088a-dcfg", }, + { .compatible = "fsl,ls2080a-dcfg", .data = &ls2088a_data}, + { .compatible = "fsl,ls1088a-dcfg", .data = &ls1088a_data}, { .compatible = "fsl,ls1012a-dcfg", }, - { .compatible = "fsl,ls1046a-dcfg", }, + { .compatible = "fsl,ls1046a-dcfg", .data = &ls1046a_data}, { .compatible = "fsl,lx2160a-dcfg", }, { .compatible = "fsl,ls1028a-dcfg", .data = &ls1028a_data}, {} @@ -240,6 +621,8 @@ static int __init fsl_guts_init(void) u64 soc_uid = 0; int ret; + spin_lock_init(&soc.rcwcr_lock); + np = of_find_matching_node_and_match(NULL, fsl_guts_of_match, &match); if (!np) return 0; @@ -251,6 +634,8 @@ static int __init fsl_guts_init(void) ret = -ENOMEM; goto err_clear_soc_data; } + /* DCFG_DCSR is optional */ + soc.dcfg_dcsr = of_iomap(np, DCFG_DCSR); soc.little_endian = of_property_read_bool(np, "little-endian"); soc.svr = fsl_guts_read(&soc.dcfg_ccsr->svr); @@ -260,7 +645,7 @@ static int __init fsl_guts_init(void) soc_dev_attr = kzalloc_obj(*soc_dev_attr); if (!soc_dev_attr) { ret = -ENOMEM; - goto err_unmap_dcfg_ccsr; + goto err_unmap_dcfg_ccsr_dcsr; } ret = soc_attr_read_machine(soc_dev_attr); @@ -321,7 +706,11 @@ err_free_family: kfree(soc_dev_attr->family); err_free_soc_dev_attr: kfree(soc_dev_attr); -err_unmap_dcfg_ccsr: +err_unmap_dcfg_ccsr_dcsr: + if (soc.dcfg_dcsr) { + iounmap(soc.dcfg_dcsr); + soc.dcfg_dcsr = NULL; + } iounmap(soc.dcfg_ccsr); soc.dcfg_ccsr = NULL; err_clear_soc_data: diff --git a/include/linux/fsl/guts.h b/include/linux/fsl/guts.h index fdb55ca47a4f..8d5ffb985fee 100644 --- a/include/linux/fsl/guts.h +++ b/include/linux/fsl/guts.h @@ -13,6 +13,7 @@ #include #include +#include /* * Global Utility Registers. @@ -91,9 +92,15 @@ struct ccsr_guts { u32 iovselsr; /* 0x.00c0 - I/O voltage select status register Called 'elbcvselcr' on 86xx SOCs */ u8 res0c4[0x100 - 0xc4]; - u32 rcwsr[16]; /* 0x.0100 - Reset Control Word Status registers - There are 16 registers */ - u8 res140[0x224 - 0x140]; + /* 0x.0100 - read-only Reset Configuration Word Status registers in + * CCSR, or write-only Reset Configuration Word Control registers in + * DCSR. In both cases there are 32 registers. + */ + union { + u32 rcwsr[32]; + u32 rcwcr[32]; + }; + u8 res180[0x224 - 0x180]; u32 iodelay1; /* 0x.0224 - IO delay control register 1 */ u32 iodelay2; /* 0x.0228 - IO delay control register 2 */ u8 res22c[0x604 - 0x22c]; @@ -131,6 +138,11 @@ struct ccsr_guts { u32 srds2cr1; /* 0x.0f44 - SerDes2 Control Register 0 */ } __attribute__ ((packed)); +int fsl_guts_lane_validate(int serdes_idx, int lane, + enum lynx_lane_mode lane_mode); +int fsl_guts_lane_set_mode(int serdes_idx, int lane, + enum lynx_lane_mode lane_mode); + /* Alternate function signal multiplex control */ #define MPC85xx_PMUXCR_QE(x) (0x8000 >> (x))