| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Freescale QorIQ Platforms GUTS Driver | |
| * | |
| * Copyright (C) 2016 Freescale Semiconductor, Inc. | |
| */ | |
| #include <linux/bitfield.h> | |
| #include <linux/bitops.h> | |
| #include <linux/io.h> | |
| #include <linux/iopoll.h> | |
| #include <linux/slab.h> | |
| #include <linux/module.h> | |
| #include <linux/of_fdt.h> | |
| #include <linux/sys_soc.h> | |
| #include <linux/of_address.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/fsl/guts.h> | |
| #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; | |
| u32 svr; | |
| 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 { | |
| DIE_T4240, | |
| DIE_T1040, | |
| DIE_T2080, | |
| DIE_T1024, | |
| DIE_LS1043A, | |
| DIE_LS2080A, | |
| DIE_LS1088A, | |
| DIE_LS1012A, | |
| DIE_LS1046A, | |
| DIE_LS2088A, | |
| DIE_LS1021A, | |
| DIE_LX2160A, | |
| DIE_LS1028A, | |
| 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[] = { | |
| /* | |
| * Power Architecture-based SoCs T Series | |
| */ | |
| /* Die: T4240, SoC: T4240/T4160/T4080 */ | |
| [DIE_T4240] = | |
| { .die = "T4240", | |
| .svr = 0x82400000, | |
| .mask = 0xfff00000, | |
| }, | |
| /* Die: T1040, SoC: T1040/T1020/T1042/T1022 */ | |
| [DIE_T1040] = | |
| { .die = "T1040", | |
| .svr = 0x85200000, | |
| .mask = 0xfff00000, | |
| }, | |
| /* Die: T2080, SoC: T2080/T2081 */ | |
| [DIE_T2080] = | |
| { .die = "T2080", | |
| .svr = 0x85300000, | |
| .mask = 0xfff00000, | |
| }, | |
| /* Die: T1024, SoC: T1024/T1014/T1023/T1013 */ | |
| [DIE_T1024] = | |
| { .die = "T1024", | |
| .svr = 0x85400000, | |
| .mask = 0xfff00000, | |
| }, | |
| /* | |
| * ARM-based SoCs LS Series | |
| */ | |
| /* Die: LS1043A, SoC: LS1043A/LS1023A */ | |
| [DIE_LS1043A] = | |
| { .die = "LS1043A", | |
| .svr = 0x87920000, | |
| .mask = 0xffff0000, | |
| }, | |
| /* Die: LS2080A, SoC: LS2080A/LS2040A/LS2085A */ | |
| [DIE_LS2080A] = | |
| { .die = "LS2080A", | |
| .svr = 0x87010000, | |
| .mask = 0xff3f0000, | |
| }, | |
| /* Die: LS1088A, SoC: LS1088A/LS1048A/LS1084A/LS1044A */ | |
| [DIE_LS1088A] = | |
| { .die = "LS1088A", | |
| .svr = 0x87030000, | |
| .mask = 0xff3f0000, | |
| }, | |
| /* Die: LS1012A, SoC: LS1012A */ | |
| [DIE_LS1012A] = | |
| { .die = "LS1012A", | |
| .svr = 0x87040000, | |
| .mask = 0xffff0000, | |
| }, | |
| /* Die: LS1046A, SoC: LS1046A/LS1026A */ | |
| [DIE_LS1046A] = | |
| { .die = "LS1046A", | |
| .svr = 0x87070000, | |
| .mask = 0xffff0000, | |
| }, | |
| /* Die: LS2088A, SoC: LS2088A/LS2048A/LS2084A/LS2044A */ | |
| [DIE_LS2088A] = | |
| { .die = "LS2088A", | |
| .svr = 0x87090000, | |
| .mask = 0xff3f0000, | |
| }, | |
| /* Die: LS1021A, SoC: LS1021A/LS1020A/LS1022A */ | |
| [DIE_LS1021A] = | |
| { .die = "LS1021A", | |
| .svr = 0x87000000, | |
| .mask = 0xfff70000, | |
| }, | |
| /* Die: LX2160A, SoC: LX2160A/LX2120A/LX2080A */ | |
| [DIE_LX2160A] = | |
| { .die = "LX2160A", | |
| .svr = 0x87360000, | |
| .mask = 0xff3f0000, | |
| }, | |
| /* Die: LS1028A, SoC: LS1028A */ | |
| [DIE_LS1028A] = | |
| { .die = "LS1028A", | |
| .svr = 0x870b0000, | |
| .mask = 0xff3f0000, | |
| }, | |
| { }, | |
| }; | |
| 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) | |
| { | |
| if (soc.little_endian) | |
| return ioread32(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); | |
| } | |
| static const struct fsl_soc_die_attr *fsl_soc_die_match( | |
| u32 svr, const struct fsl_soc_die_attr *matches) | |
| { | |
| while (matches->svr) { | |
| if (fsl_soc_die_match_one(svr, matches)) | |
| return matches; | |
| matches++; | |
| } | |
| 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; | |
| void __iomem *sfp_base; | |
| u64 uid; | |
| np = of_find_compatible_node(NULL, NULL, compat); | |
| if (!np) | |
| return 0; | |
| sfp_base = of_iomap(np, 0); | |
| if (!sfp_base) { | |
| of_node_put(np); | |
| return 0; | |
| } | |
| uid = ioread32(sfp_base + offset); | |
| uid <<= 32; | |
| uid |= ioread32(sfp_base + offset + 4); | |
| iounmap(sfp_base); | |
| of_node_put(np); | |
| 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, | |
| }; | |
| /* | |
| * Table for matching compatible strings, for device tree | |
| * guts node, for Freescale QorIQ SOCs. | |
| */ | |
| static const struct of_device_id fsl_guts_of_match[] = { | |
| { .compatible = "fsl,qoriq-device-config-1.0", }, | |
| { .compatible = "fsl,qoriq-device-config-2.0", }, | |
| { .compatible = "fsl,p1010-guts", }, | |
| { .compatible = "fsl,p1020-guts", }, | |
| { .compatible = "fsl,p1021-guts", }, | |
| { .compatible = "fsl,p1022-guts", }, | |
| { .compatible = "fsl,p1023-guts", }, | |
| { .compatible = "fsl,p2020-guts", }, | |
| { .compatible = "fsl,bsc9131-guts", }, | |
| { .compatible = "fsl,bsc9132-guts", }, | |
| { .compatible = "fsl,mpc8536-guts", }, | |
| { .compatible = "fsl,mpc8544-guts", }, | |
| { .compatible = "fsl,mpc8548-guts", }, | |
| { .compatible = "fsl,mpc8568-guts", }, | |
| { .compatible = "fsl,mpc8569-guts", }, | |
| { .compatible = "fsl,mpc8572-guts", }, | |
| { .compatible = "fsl,ls1021a-dcfg", }, | |
| { .compatible = "fsl,ls1043a-dcfg", }, | |
| { .compatible = "fsl,ls2080a-dcfg", .data = &ls2088a_data}, | |
| { .compatible = "fsl,ls1088a-dcfg", .data = &ls1088a_data}, | |
| { .compatible = "fsl,ls1012a-dcfg", }, | |
| { .compatible = "fsl,ls1046a-dcfg", .data = &ls1046a_data}, | |
| { .compatible = "fsl,lx2160a-dcfg", }, | |
| { .compatible = "fsl,ls1028a-dcfg", .data = &ls1028a_data}, | |
| {} | |
| }; | |
| static int __init fsl_guts_init(void) | |
| { | |
| struct soc_device_attribute *soc_dev_attr = NULL; | |
| static struct soc_device *soc_dev; | |
| const struct fsl_soc_die_attr *soc_die; | |
| const struct of_device_id *match; | |
| struct device_node *np; | |
| 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; | |
| soc.data = match->data; | |
| soc.dcfg_ccsr = of_iomap(np, DCFG_CCSR); | |
| if (!soc.dcfg_ccsr) { | |
| of_node_put(np); | |
| 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); | |
| of_node_put(np); | |
| /* Register soc device */ | |
| soc_dev_attr = kzalloc_obj(*soc_dev_attr); | |
| if (!soc_dev_attr) { | |
| ret = -ENOMEM; | |
| goto err_unmap_dcfg_ccsr_dcsr; | |
| } | |
| ret = soc_attr_read_machine(soc_dev_attr); | |
| if (ret) | |
| of_machine_read_compatible(&soc_dev_attr->machine, 0); | |
| soc_die = fsl_soc_die_match(soc.svr, fsl_soc_die); | |
| if (soc_die) { | |
| soc_dev_attr->family = kasprintf(GFP_KERNEL, "QorIQ %s", | |
| soc_die->die); | |
| } else { | |
| soc_dev_attr->family = kasprintf(GFP_KERNEL, "QorIQ"); | |
| } | |
| if (!soc_dev_attr->family) { | |
| ret = -ENOMEM; | |
| goto err_free_soc_dev_attr; | |
| } | |
| soc_dev_attr->soc_id = kasprintf(GFP_KERNEL, "svr:0x%08x", soc.svr); | |
| if (!soc_dev_attr->soc_id) { | |
| ret = -ENOMEM; | |
| goto err_free_family; | |
| } | |
| soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d.%d", | |
| (soc.svr >> 4) & 0xf, soc.svr & 0xf); | |
| if (!soc_dev_attr->revision) { | |
| ret = -ENOMEM; | |
| goto err_free_soc_id; | |
| } | |
| if (soc.data) | |
| soc_uid = fsl_guts_get_soc_uid(soc.data->sfp_compat, | |
| soc.data->uid_offset); | |
| if (soc_uid) | |
| soc_dev_attr->serial_number = kasprintf(GFP_KERNEL, "%016llX", | |
| soc_uid); | |
| soc_dev = soc_device_register(soc_dev_attr); | |
| if (IS_ERR(soc_dev)) { | |
| ret = PTR_ERR(soc_dev); | |
| goto err_free_serial_number; | |
| } | |
| pr_info("Machine: %s\n", soc_dev_attr->machine); | |
| pr_info("SoC family: %s\n", soc_dev_attr->family); | |
| pr_info("SoC ID: %s, Revision: %s\n", | |
| soc_dev_attr->soc_id, soc_dev_attr->revision); | |
| return 0; | |
| err_free_serial_number: | |
| kfree(soc_dev_attr->serial_number); | |
| kfree(soc_dev_attr->revision); | |
| err_free_soc_id: | |
| kfree(soc_dev_attr->soc_id); | |
| err_free_family: | |
| kfree(soc_dev_attr->family); | |
| err_free_soc_dev_attr: | |
| kfree(soc_dev_attr); | |
| 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: | |
| soc.data = NULL; | |
| return ret; | |
| } | |
| core_initcall(fsl_guts_init); |