| // 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); |