diff options
| author | Rosen Penev <rosenp@gmail.com> | 2026-09-27 13:35:01 -0700 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2026-10-01 11:04:53 +0200 |
| commit | 8167c1f071426706c233e93ecfd13aba7d5c06c8 (patch) | |
| tree | 2c0cd5f5e1cc35eadee98d49fe034f96a88856ce /drivers/nvmem | |
| download | linux-stable-8167c1f071426706c233e93ecfd13aba7d5c06c8.tar.gz linux-stable-8167c1f071426706c233e93ecfd13aba7d5c06c8.zip | |
tty: serial: mpc52xx_uart: move static declarations up.grafted
Avoid a compilation error so that they're not used before being
declared.
Fixes: 4d105880666a ("tty: serial: mpc52xx_uart: add bounds check for psc_num array index")
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202609260356.tJWbd1WU-lkp@intel.com/
Signed-off-by: Rosen Penev <rosenp@gmail.com>
Link: https://patch.msgid.link/20260927203501.14105-1-rosenp@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/nvmem')
62 files changed, 16527 insertions, 0 deletions
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig new file mode 100644 index 000000000..c36c2a4c2 --- /dev/null +++ b/drivers/nvmem/Kconfig @@ -0,0 +1,586 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig NVMEM + bool "NVMEM Support" + imply NVMEM_LAYOUTS + help + Support for NVMEM(Non Volatile Memory) devices like EEPROM, EFUSES... + + This framework is designed to provide a generic interface to NVMEM + from both the Linux Kernel and the userspace. + + If unsure, say no. + +if NVMEM + +config NVMEM_SYSFS + bool "/sys/bus/nvmem/devices/*/nvmem (sysfs interface)" + depends on SYSFS + default y + help + Say Y here to add a sysfs interface for NVMEM. + + This interface is mostly used by userspace applications to + read/write directly into nvmem. + +# Layouts + +source "drivers/nvmem/layouts/Kconfig" + +# Devices + +if NVMEM_SYSFS + +config EEPROM_AT24 + tristate "I2C EEPROMs / RAMs / ROMs from most vendors" + depends on I2C && SYSFS + select REGMAP + select REGMAP_I2C + help + Enable this driver to get read/write support to most I2C EEPROMs + and compatible devices like FRAMs, SRAMs, ROMs etc. After you + configure the driver to know about each chip on your target + board. Use these generic chip names, instead of vendor-specific + ones like at24c64, 24lc02 or fm24c04: + + 24c00, 24c01, 24c02, spd (readonly 24c02), 24c04, 24c08, + 24c16, 24c32, 24c64, 24c128, 24c256, 24c512, 24c1024, 24c2048 + + Unless you like data loss puzzles, always be sure that any chip + you configure as a 24c32 (32 kbit) or larger is NOT really a + 24c16 (16 kbit) or smaller, and vice versa. Marking the chip + as read-only won't help recover from this. Also, if your chip + has any software write-protect mechanism you may want to review the + code to make sure this driver won't turn it on by accident. + + If you use this with an SMBus adapter instead of an I2C adapter, + full functionality is not available. Only smaller devices are + supported (24c16 and below, max 4 kByte). + + This driver can also be built as a module. If so, the module + will be called at24. + +config EEPROM_AT25 + tristate "SPI EEPROMs (FRAMs) from most vendors" + depends on SPI && SYSFS + select SPI_MEM + help + Enable this driver to get read/write support to most SPI EEPROMs + and Cypress FRAMs, + after you configure the board init code to know about each eeprom + on your target board. + + This driver can also be built as a module. If so, the module + will be called at25. + +config EEPROM_93XX46 + tristate "Microwire EEPROM 93XX46 support" + depends on SPI && SYSFS + select REGMAP + help + Driver for the microwire EEPROM chipsets 93xx46x. The driver + supports both read and write commands and also the command to + erase the whole EEPROM. + + This driver can also be built as a module. If so, the module + will be called eeprom_93xx46. + + If unsure, say N. + +config EEPROM_EE1004 + tristate "SPD EEPROMs on DDR4 memory modules" + depends on I2C && SYSFS + help + Enable this driver to get read support to SPD EEPROMs following + the JEDEC EE1004 standard. These are typically found on DDR4 + SDRAM memory modules. + + This driver can also be built as a module. If so, the module + will be called ee1004. + +config EEPROM_M24LR + tristate "STMicroelectronics M24LR RFID/NFC EEPROM support" + depends on I2C && SYSFS + select REGMAP_I2C + help + This enables support for STMicroelectronics M24LR RFID/NFC EEPROM + chips. These dual-interface devices expose two I2C addresses: + one for EEPROM memory access and another for control and system + configuration (e.g. UID, password handling). + + This driver provides a sysfs interface for control functions and + integrates with the nvmem subsystem for EEPROM access. + + To compile this driver as a module, choose M here: the + module will be called m24lr. + +endif # NVMEM_SYSFS + +config NVMEM_AIROHA_SMC_EFUSES + tristate "Airoha SMC eFuse support" + depends on ARM64 + depends on ARCH_AIROHA || COMPILE_TEST + depends on HAVE_ARM_SMCCC + default ARCH_AIROHA + help + Say y here to enable support for reading eFuses on Airoha AN7581 + SoCs. These are e.g. used to store factory programmed + calibration data required for the PCIe or the USB-C PHY or Thermal. + + This driver can also be built as a module. If so, the module will + be called nvmem-airoha-smc-efuses. + +config NVMEM_AN8855_EFUSE + tristate "Airoha AN8855 eFuse support" + depends on COMPILE_TEST + help + Say y here to enable support for reading eFuses on Airoha AN8855 + Switch. These are e.g. used to store factory programmed + calibration data required for the PHY. + + This driver can also be built as a module. If so, the module will + be called nvmem-an8855-efuse. + +config NVMEM_APPLE_EFUSES + tristate "Apple eFuse support" + depends on ARCH_APPLE || COMPILE_TEST + help + Say y here to enable support for reading eFuses on Apple SoCs + such as the M1. These are e.g. used to store factory programmed + calibration data required for the PCIe or the USB-C PHY. + + This driver can also be built as a module. If so, the module will + be called nvmem-apple-efuses. + +config NVMEM_APPLE_SPMI + tristate "Apple SPMI NVMEM" + depends on ARCH_APPLE || COMPILE_TEST + depends on SPMI + select REGMAP_SPMI + help + Say y here to build a driver to expose NVMEM cells for a set of power + and RTC-related settings on a SPMI-attached PMIC present on Apple + devices, such as Apple Silicon Macs. + + This driver can also be built as a module. If so, the module + will be called apple-nvmem-spmi. + +config NVMEM_BCM_OCOTP + tristate "Broadcom On-Chip OTP Controller support" + depends on ARCH_BCM_IPROC || COMPILE_TEST + depends on HAS_IOMEM + default ARCH_BCM_IPROC + help + Say y here to enable read/write access to the Broadcom OTP + controller. + + This driver can also be built as a module. If so, the module + will be called nvmem-bcm-ocotp. + +config NVMEM_BRCM_NVRAM + tristate "Broadcom's NVRAM support" + depends on ARCH_BCM_5301X || COMPILE_TEST + depends on HAS_IOMEM + select GENERIC_NET_UTILS + help + This driver provides support for Broadcom's NVRAM that can be accessed + using I/O mapping. + +config NVMEM_IMX_IIM + tristate "i.MX IC Identification Module support" + depends on ARCH_MXC || COMPILE_TEST + help + This is a driver for the IC Identification Module (IIM) available on + i.MX SoCs, providing access to 4 Kbits of programmable + eFuses. + + This driver can also be built as a module. If so, the module + will be called nvmem-imx-iim. + +config NVMEM_IMX_OCOTP + tristate "i.MX 6/7/8 On-Chip OTP Controller support" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM + help + This is a driver for the On-Chip OTP Controller (OCOTP) available on + i.MX6 SoCs, providing access to 4 Kbits of one-time programmable + eFuses. + + This driver can also be built as a module. If so, the module + will be called nvmem-imx-ocotp. + +config NVMEM_IMX_OCOTP_ELE + tristate "i.MX On-Chip OTP Controller support" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM + depends on OF + help + This is a driver for the On-Chip OTP Controller (OCOTP) + available on i.MX SoCs which has ELE. + +config NVMEM_IMX_OCOTP_SCU + tristate "i.MX8 SCU On-Chip OTP Controller support" + depends on IMX_SCU + depends on HAVE_ARM_SMCCC + help + This is a driver for the SCU On-Chip OTP Controller (OCOTP) + available on i.MX8 SoCs. + +config NVMEM_JZ4780_EFUSE + tristate "JZ4780 EFUSE Memory Support" + depends on MACH_INGENIC || COMPILE_TEST + depends on HAS_IOMEM + depends on OF + select REGMAP_MMIO + help + Say Y here to include support for JZ4780 efuse memory found on + all JZ4780 SoC based devices. + To compile this driver as a module, choose M here: the module + will be called nvmem_jz4780_efuse. + +config NVMEM_LAN9662_OTPC + tristate "Microchip LAN9662 OTP controller support" + depends on SOC_LAN966 || ARCH_LAN969X || COMPILE_TEST + depends on HAS_IOMEM + help + This driver enables the OTP controller available on Microchip LAN9662 + SoCs. It controls the access to the OTP memory connected to it. + +config NVMEM_LAYERSCAPE_SFP + tristate "Layerscape SFP (Security Fuse Processor) support" + depends on ARCH_LAYERSCAPE || COMPILE_TEST + depends on HAS_IOMEM + select REGMAP_MMIO + help + This driver provides support to read the eFuses on Freescale + Layerscape SoC's. For example, the vendor provides a per part + unique ID there. + + This driver can also be built as a module. If so, the module + will be called layerscape-sfp. + +config NVMEM_LPC18XX_EEPROM + tristate "NXP LPC18XX EEPROM Memory Support" + depends on ARCH_LPC18XX || COMPILE_TEST + depends on HAS_IOMEM + help + Say Y here to include support for NXP LPC18xx EEPROM memory found in + NXP LPC185x/3x and LPC435x/3x/2x/1x devices. + To compile this driver as a module, choose M here: the module + will be called nvmem_lpc18xx_eeprom. + +config NVMEM_LPC18XX_OTP + tristate "NXP LPC18XX OTP Memory Support" + depends on ARCH_LPC18XX || COMPILE_TEST + depends on HAS_IOMEM + help + Say Y here to include support for NXP LPC18xx OTP memory found on + all LPC18xx and LPC43xx devices. + To compile this driver as a module, choose M here: the module + will be called nvmem_lpc18xx_otp. + +config NVMEM_MAX77759 + tristate "Maxim Integrated MAX77759 NVMEM Support" + depends on MFD_MAX77759 + default MFD_MAX77759 + help + Say Y here to include support for the user-accessible storage found + in Maxim Integrated MAX77759 PMICs. This IC provides space for 30 + bytes of storage. + + This driver can also be built as a module. If so, the module + will be called nvmem-max77759. + +config NVMEM_MESON_EFUSE + tristate "Amlogic Meson GX eFuse Support" + depends on (ARCH_MESON || COMPILE_TEST) && MESON_SM + help + This is a driver to retrieve specific values from the eFuse found on + the Amlogic Meson GX SoCs. + + This driver can also be built as a module. If so, the module + will be called nvmem_meson_efuse. + +config NVMEM_MESON_MX_EFUSE + tristate "Amlogic Meson6/Meson8/Meson8b eFuse Support" + depends on ARCH_MESON || COMPILE_TEST + help + This is a driver to retrieve specific values from the eFuse found on + the Amlogic Meson6, Meson8 and Meson8b SoCs. + + This driver can also be built as a module. If so, the module + will be called nvmem_meson_mx_efuse. + +config NVMEM_MICROCHIP_OTPC + tristate "Microchip OTPC support" + depends on ARCH_AT91 || COMPILE_TEST + help + This driver enable the OTP controller available on Microchip SAMA7G5 + SoCs. It controls the access to the OTP memory connected to it. + +config NVMEM_MTK_EFUSE + tristate "Mediatek SoCs EFUSE support" + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on HAS_IOMEM + help + This is a driver to access hardware related data like sensor + calibration, HDMI impedance etc. + + This driver can also be built as a module. If so, the module + will be called efuse-mtk. + +config NVMEM_MXS_OCOTP + tristate "Freescale MXS On-Chip OTP Memory Support" + depends on ARCH_MXS || COMPILE_TEST + depends on HAS_IOMEM + help + If you say Y here, you will get readonly access to the + One Time Programmable memory pages that are stored + on the Freescale i.MX23/i.MX28 processor. + + This driver can also be built as a module. If so, the module + will be called nvmem-mxs-ocotp. + +config NVMEM_NINTENDO_OTP + tristate "Nintendo Wii and Wii U OTP Support" + depends on WII || COMPILE_TEST + help + This is a driver exposing the OTP of a Nintendo Wii or Wii U console. + + This memory contains common and per-console keys, signatures and + related data required to access peripherals. + + This driver can also be built as a module. If so, the module + will be called nvmem-nintendo-otp. + +config NVMEM_S32G_OCOTP + tristate "S32G SoC OCOTP support" + depends on ARCH_S32 + help + This is a driver for the 'OCOTP' peripheral available on S32G + platforms. + + If you say Y here, you will get support for the One Time + Programmable memory pages. + +config NVMEM_QCOM_QFPROM + tristate "Qualcomm QFPROM Support" + depends on ARCH_QCOM || COMPILE_TEST + depends on HAS_IOMEM + help + Say y here to enable QFPROM support. The QFPROM provides access + functions for QFPROM data to rest of the drivers via nvmem interface. + + This driver can also be built as a module. If so, the module + will be called nvmem_qfprom. + +config NVMEM_QCOM_SEC_QFPROM + tristate "Qualcomm SECURE QFPROM Support" + depends on ARCH_QCOM || COMPILE_TEST + depends on HAS_IOMEM + depends on OF + select QCOM_SCM + help + Say y here to enable secure QFPROM support. The secure QFPROM provides access + functions for QFPROM data to rest of the drivers via nvmem interface. + + This driver can also be built as a module. If so, the module will be called + nvmem_sec_qfprom. + +config NVMEM_QNAP_MCU_EEPROM + tristate "QNAP MCU EEPROM Support" + depends on MFD_QNAP_MCU + help + Say y here to enable support for accessing the EEPROM attached to + QNAP MCU devices. This EEPROM contains additional runtime device + information, like MAC addresses for ethernet devices that do not + contain their own mac storage. + +config NVMEM_RAVE_SP_EEPROM + tristate "Rave SP EEPROM Support" + depends on RAVE_SP_CORE + help + Say y here to enable Rave SP EEPROM support. + +config NVMEM_RCAR_EFUSE + tristate "Renesas R-Car Gen4 E-FUSE support" + depends on (ARCH_RENESAS && ARM64) || COMPILE_TEST + help + Enable support for reading the fuses in the E-FUSE or OTP + non-volatile memory block on Renesas R-Car Gen4 SoCs. + + This driver can also be built as a module. If so, the module + will be called nvmem-rcar-efuse. + +config NVMEM_RMEM + tristate "Reserved Memory Based Driver Support" + depends on HAS_IOMEM + select CRC32 + help + This driver maps reserved memory into an nvmem device. It might be + useful to expose information left by firmware in memory. + + This driver can also be built as a module. If so, the module + will be called nvmem-rmem. + +config NVMEM_ROCKCHIP_EFUSE + tristate "Rockchip eFuse Support" + depends on ARCH_ROCKCHIP || COMPILE_TEST + depends on HAS_IOMEM + help + This is a simple driver to dump specified values of Rockchip SoC + from eFuse, such as cpu-leakage. + + This driver can also be built as a module. If so, the module + will be called nvmem_rockchip_efuse. + +config NVMEM_ROCKCHIP_OTP + tristate "Rockchip OTP controller support" + depends on ARCH_ROCKCHIP || COMPILE_TEST + depends on HAS_IOMEM + help + This is a simple driver to dump specified values of Rockchip SoC + from OTP, such as cpu-leakage. + + This driver can also be built as a module. If so, the module + will be called nvmem_rockchip_otp. + +config NVMEM_SC27XX_EFUSE + tristate "Spreadtrum SC27XX eFuse Support" + depends on MFD_SC27XX_PMIC || COMPILE_TEST + depends on HAS_IOMEM + help + This is a simple driver to dump specified values of Spreadtrum + SC27XX PMICs from eFuse. + + This driver can also be built as a module. If so, the module + will be called nvmem-sc27xx-efuse. + +config NVMEM_SNVS_LPGPR + tristate "Support for Low Power General Purpose Register" + depends on ARCH_MXC || COMPILE_TEST + help + This is a driver for Low Power General Purpose Register (LPGPR) available on + i.MX6 and i.MX7 SoCs in Secure Non-Volatile Storage (SNVS) of this chip. + + This driver can also be built as a module. If so, the module + will be called nvmem-snvs-lpgpr. + +config NVMEM_SPMI_SDAM + tristate "SPMI SDAM Support" + depends on SPMI + help + This driver supports the Shared Direct Access Memory Module on + Qualcomm Technologies, Inc. PMICs. It provides the clients + an interface to read/write to the SDAM module's shared memory. + +config NVMEM_SPRD_EFUSE + tristate "Spreadtrum SoC eFuse Support" + depends on ARCH_SPRD || COMPILE_TEST + depends on HAS_IOMEM + help + This is a simple driver to dump specified values of Spreadtrum + SoCs from eFuse. + + This driver can also be built as a module. If so, the module + will be called nvmem-sprd-efuse. + +config NVMEM_STM32_BSEC_OPTEE_TA + def_bool NVMEM_STM32_ROMEM && OPTEE + help + Say y here to enable the accesses to STM32MP SoC OTPs by the OP-TEE + trusted application STM32MP BSEC. + + This library is a used by stm32-romem driver or included in the module + called nvmem-stm32-romem. + +config NVMEM_STM32_ROMEM + tristate "STMicroelectronics STM32 factory-programmed memory support" + depends on ARCH_STM32 || COMPILE_TEST + depends on OPTEE || !OPTEE + help + Say y here to enable read-only access for STMicroelectronics STM32 + factory-programmed memory area. + + This driver can also be built as a module. If so, the module + will be called nvmem-stm32-romem. + +config NVMEM_SUNPLUS_OCOTP + tristate "Sunplus SoC OTP support" + depends on SOC_SP7021 || COMPILE_TEST + depends on HAS_IOMEM + help + This is a driver for the On-chip OTP controller (OCOTP) available + on Sunplus SoCs. It provides access to 128 bytes of one-time + programmable eFuse. + + This driver can also be built as a module. If so, the module + will be called nvmem-sunplus-ocotp. + +config NVMEM_SUNXI_SID + tristate "Allwinner SoCs SID support" + depends on ARCH_SUNXI + help + This is a driver for the 'security ID' available on various Allwinner + devices. + + This driver can also be built as a module. If so, the module + will be called nvmem_sunxi_sid. + +config NVMEM_U_BOOT_ENV + tristate "U-Boot environment variables support" + depends on OF && MTD + select NVMEM_LAYOUT_U_BOOT_ENV + help + U-Boot stores its setup as environment variables. This driver adds + support for verifying & exporting such data. It also exposes variables + as NVMEM cells so they can be referenced by other drivers. + + Currently this drivers works only with env variables on top of MTD. + + If compiled as module it will be called nvmem_u-boot-env. + +config NVMEM_UNIPHIER_EFUSE + tristate "UniPhier SoCs eFuse support" + depends on ARCH_UNIPHIER || COMPILE_TEST + depends on HAS_IOMEM + help + This is a simple driver to dump specified values of UniPhier SoC + from eFuse. + + This driver can also be built as a module. If so, the module + will be called nvmem-uniphier-efuse. + +config NVMEM_VF610_OCOTP + tristate "VF610 SoC OCOTP support" + depends on SOC_VF610 || COMPILE_TEST + depends on HAS_IOMEM + help + This is a driver for the 'OCOTP' peripheral available on Vybrid + devices like VF5xx and VF6xx. + + This driver can also be build as a module. If so, the module will + be called nvmem-vf610-ocotp. + +config NVMEM_ZYNQMP + tristate "Xilinx ZYNQMP SoC nvmem firmware support" + depends on ARCH_ZYNQMP + help + This is a driver to access hardware related data like + soc revision, IDCODE... etc by using the firmware + interface. + + If sure, say yes. If unsure, say no. + +config NVMEM_QORIQ_EFUSE + tristate "NXP QorIQ eFuse support" + depends on PPC_85xx || COMPILE_TEST + depends on HAS_IOMEM + help + This driver provides read support for the eFuses (SFP) on NXP QorIQ + series SoC's. This includes secure boot settings, the globally unique + NXP ID 'FUIDR' and the OEM unique ID 'OUIDR'. + + This driver can also be built as a module. If so, the module + will be called nvmem_qoriq_efuse. + +endif # NVMEM diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile new file mode 100644 index 000000000..2bbfb9ff1 --- /dev/null +++ b/drivers/nvmem/Makefile @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for nvmem drivers. +# + +obj-$(CONFIG_NVMEM) += nvmem_core.o +nvmem_core-y := core.o +obj-$(CONFIG_NVMEM_LAYOUTS) += nvmem_layouts.o +nvmem_layouts-y := layouts.o +obj-y += layouts/ + +# Devices +obj-$(CONFIG_EEPROM_AT24) += at24.o +obj-$(CONFIG_EEPROM_AT25) += at25.o +obj-$(CONFIG_EEPROM_93XX46) += eeprom_93xx46.o +obj-$(CONFIG_EEPROM_EE1004) += ee1004.o +obj-$(CONFIG_EEPROM_M24LR) += m24lr.o +obj-$(CONFIG_NVMEM_AIROHA_SMC_EFUSES) += nvmem-airoha-smc-efuses.o +nvmem-airoha-smc-efuses-y := airoha-smc-efuses.o +obj-$(CONFIG_NVMEM_AN8855_EFUSE) += nvmem-an8855-efuse.o +nvmem-an8855-efuse-y := an8855-efuse.o +obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o +nvmem-apple-efuses-y := apple-efuses.o +obj-$(CONFIG_NVMEM_APPLE_SPMI) += apple_nvmem_spmi.o +apple_nvmem_spmi-y := apple-spmi-nvmem.o +obj-$(CONFIG_NVMEM_BCM_OCOTP) += nvmem-bcm-ocotp.o +nvmem-bcm-ocotp-y := bcm-ocotp.o +obj-$(CONFIG_NVMEM_BRCM_NVRAM) += nvmem_brcm_nvram.o +nvmem_brcm_nvram-y := brcm_nvram.o +obj-$(CONFIG_NVMEM_IMX_IIM) += nvmem-imx-iim.o +nvmem-imx-iim-y := imx-iim.o +obj-$(CONFIG_NVMEM_IMX_OCOTP) += nvmem-imx-ocotp.o +nvmem-imx-ocotp-y := imx-ocotp.o +obj-$(CONFIG_NVMEM_IMX_OCOTP_ELE) += nvmem-imx-ocotp-ele.o +nvmem-imx-ocotp-ele-y := imx-ocotp-ele.o +obj-$(CONFIG_NVMEM_IMX_OCOTP_SCU) += nvmem-imx-ocotp-scu.o +nvmem-imx-ocotp-scu-y := imx-ocotp-scu.o +obj-$(CONFIG_NVMEM_JZ4780_EFUSE) += nvmem_jz4780_efuse.o +nvmem_jz4780_efuse-y := jz4780-efuse.o +obj-$(CONFIG_NVMEM_LAN9662_OTPC) += nvmem-lan9662-otpc.o +nvmem-lan9662-otpc-y := lan9662-otpc.o +obj-$(CONFIG_NVMEM_LAYERSCAPE_SFP) += nvmem-layerscape-sfp.o +nvmem-layerscape-sfp-y := layerscape-sfp.o +obj-$(CONFIG_NVMEM_LPC18XX_EEPROM) += nvmem_lpc18xx_eeprom.o +nvmem_lpc18xx_eeprom-y := lpc18xx_eeprom.o +obj-$(CONFIG_NVMEM_LPC18XX_OTP) += nvmem_lpc18xx_otp.o +nvmem_lpc18xx_otp-y := lpc18xx_otp.o +obj-$(CONFIG_NVMEM_MAX77759) += nvmem-max77759.o +nvmem-max77759-y := max77759-nvmem.o +obj-$(CONFIG_NVMEM_MESON_EFUSE) += nvmem_meson_efuse.o +nvmem_meson_efuse-y := meson-efuse.o +obj-$(CONFIG_NVMEM_MESON_MX_EFUSE) += nvmem_meson_mx_efuse.o +nvmem_meson_mx_efuse-y := meson-mx-efuse.o +obj-$(CONFIG_NVMEM_MICROCHIP_OTPC) += nvmem-microchip-otpc.o +nvmem-microchip-otpc-y := microchip-otpc.o +obj-$(CONFIG_NVMEM_MTK_EFUSE) += nvmem_mtk-efuse.o +nvmem_mtk-efuse-y := mtk-efuse.o +obj-$(CONFIG_NVMEM_MXS_OCOTP) += nvmem-mxs-ocotp.o +nvmem-mxs-ocotp-y := mxs-ocotp.o +obj-$(CONFIG_NVMEM_NINTENDO_OTP) += nvmem-nintendo-otp.o +nvmem-nintendo-otp-y := nintendo-otp.o +obj-$(CONFIG_NVMEM_QCOM_QFPROM) += nvmem_qfprom.o +nvmem_qfprom-y := qfprom.o +obj-$(CONFIG_NVMEM_QCOM_SEC_QFPROM) += nvmem_sec_qfprom.o +nvmem_sec_qfprom-y := sec-qfprom.o +obj-$(CONFIG_NVMEM_QNAP_MCU_EEPROM) += nvmem-qnap-mcu-eeprom.o +nvmem-qnap-mcu-eeprom-y := qnap-mcu-eeprom.o +obj-$(CONFIG_NVMEM_RAVE_SP_EEPROM) += nvmem-rave-sp-eeprom.o +nvmem-rave-sp-eeprom-y := rave-sp-eeprom.o +obj-$(CONFIG_NVMEM_RCAR_EFUSE) += nvmem-rcar-efuse.o +nvmem-rcar-efuse-y := rcar-efuse.o +obj-$(CONFIG_NVMEM_RMEM) += nvmem-rmem.o +nvmem-rmem-y := rmem.o +obj-$(CONFIG_NVMEM_ROCKCHIP_EFUSE) += nvmem_rockchip_efuse.o +nvmem_rockchip_efuse-y := rockchip-efuse.o +obj-$(CONFIG_NVMEM_ROCKCHIP_OTP) += nvmem-rockchip-otp.o +nvmem-rockchip-otp-y := rockchip-otp.o +obj-$(CONFIG_NVMEM_SC27XX_EFUSE) += nvmem-sc27xx-efuse.o +nvmem-sc27xx-efuse-y := sc27xx-efuse.o +obj-$(CONFIG_NVMEM_SNVS_LPGPR) += nvmem_snvs_lpgpr.o +nvmem_snvs_lpgpr-y := snvs_lpgpr.o +obj-$(CONFIG_NVMEM_SPMI_SDAM) += nvmem_qcom-spmi-sdam.o +nvmem_qcom-spmi-sdam-y += qcom-spmi-sdam.o +obj-$(CONFIG_NVMEM_SPRD_EFUSE) += nvmem_sprd_efuse.o +nvmem_sprd_efuse-y := sprd-efuse.o +obj-$(CONFIG_NVMEM_STM32_ROMEM) += nvmem_stm32_romem.o +nvmem_stm32_romem-y := stm32-romem.o +nvmem_stm32_romem-$(CONFIG_NVMEM_STM32_BSEC_OPTEE_TA) += stm32-bsec-optee-ta.o +obj-$(CONFIG_NVMEM_SUNPLUS_OCOTP) += nvmem_sunplus_ocotp.o +nvmem_sunplus_ocotp-y := sunplus-ocotp.o +obj-$(CONFIG_NVMEM_SUNXI_SID) += nvmem_sunxi_sid.o +nvmem_sunxi_sid-y := sunxi_sid.o +obj-$(CONFIG_NVMEM_S32G_OCOTP) += nvmem-s32g-ocotp-nvmem.o +nvmem-s32g-ocotp-nvmem-y := s32g-ocotp-nvmem.o +obj-$(CONFIG_NVMEM_U_BOOT_ENV) += nvmem_u-boot-env.o +nvmem_u-boot-env-y := u-boot-env.o +obj-$(CONFIG_NVMEM_UNIPHIER_EFUSE) += nvmem-uniphier-efuse.o +nvmem-uniphier-efuse-y := uniphier-efuse.o +obj-$(CONFIG_NVMEM_VF610_OCOTP) += nvmem-vf610-ocotp.o +nvmem-vf610-ocotp-y := vf610-ocotp.o +obj-$(CONFIG_NVMEM_ZYNQMP) += nvmem_zynqmp_nvmem.o +nvmem_zynqmp_nvmem-y := zynqmp_nvmem.o +obj-$(CONFIG_NVMEM_QORIQ_EFUSE) += nvmem-qoriq-efuse.o +nvmem-qoriq-efuse-y := qoriq-efuse.o diff --git a/drivers/nvmem/airoha-smc-efuses.c b/drivers/nvmem/airoha-smc-efuses.c new file mode 100644 index 000000000..e56a99f4a --- /dev/null +++ b/drivers/nvmem/airoha-smc-efuses.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: Christian Marangi <ansuelsmth@gmail.com> + */ + +#include <linux/arm-smccc.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/regmap.h> + +#define AIROHA_SMC_EFUSE_FID 0x82000001 +#define AIROHA_SMC_EFUSE_SUB_ID_READ 0x44414552 + +#define AIROHA_EFUSE_CELLS 64 + +struct airoha_efuse_bank_priv { + u32 bank_index; +}; + +static int airoha_efuse_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct regmap *regmap = context; + + return regmap_bulk_read(regmap, offset, + val, bytes / sizeof(u32)); +} + +static int airoha_efuse_reg_read(void *context, unsigned int offset, + unsigned int *val) +{ + struct airoha_efuse_bank_priv *priv = context; + struct arm_smccc_res res; + + arm_smccc_1_1_invoke(AIROHA_SMC_EFUSE_FID, + AIROHA_SMC_EFUSE_SUB_ID_READ, + priv->bank_index, offset, 0, 0, 0, 0, &res); + + /* check if SMC reported an error */ + if (res.a0) + return -EIO; + + *val = res.a1; + return 0; +} + +static int airoha_efuse_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret; + + for_each_child_of_node_scoped(dev->of_node, child) { + struct nvmem_config nvmem_config = { + .size = AIROHA_EFUSE_CELLS * sizeof(u32), + .stride = sizeof(u32), + .word_size = sizeof(u32), + .reg_read = airoha_efuse_read, + }; + struct regmap_config regmap_config = { + .reg_read = airoha_efuse_reg_read, + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + }; + struct airoha_efuse_bank_priv *priv; + struct nvmem_device *nvmem; + struct regmap *regmap; + const char *name; + u32 bank; + + ret = of_property_read_u32(child, "reg", &bank); + if (ret) + return ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + name = devm_kasprintf(dev, GFP_KERNEL, "airoha-efuse-%u", + bank); + if (!name) + return -ENOMEM; + + priv->bank_index = bank; + + regmap_config.name = name; + regmap = devm_regmap_init(dev, NULL, priv, + ®map_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + nvmem_config.name = name; + nvmem_config.priv = regmap; + nvmem_config.dev = dev; + nvmem_config.id = bank; + nvmem_config.of_node = child; + nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + } + + return 0; +} + +static const struct of_device_id airoha_efuse_of_match[] = { + { .compatible = "airoha,an7581-efuses", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, airoha_efuse_of_match); + +static struct platform_driver airoha_efuse_driver = { + .probe = airoha_efuse_probe, + .driver = { + .name = "airoha-efuse", + .of_match_table = airoha_efuse_of_match, + }, +}; +module_platform_driver(airoha_efuse_driver); + +MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>"); +MODULE_DESCRIPTION("Driver for Airoha SMC eFUSEs"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/an8855-efuse.c b/drivers/nvmem/an8855-efuse.c new file mode 100644 index 000000000..ed0840f79 --- /dev/null +++ b/drivers/nvmem/an8855-efuse.c @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Airoha AN8855 Switch EFUSE Driver + */ + +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define AN8855_EFUSE_CELL 50 + +#define AN8855_EFUSE_DATA0 0x1000a500 +#define AN8855_EFUSE_R50O GENMASK(30, 24) + +static int an8855_efuse_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct regmap *regmap = context; + + return regmap_bulk_read(regmap, AN8855_EFUSE_DATA0 + offset, + val, bytes / sizeof(u32)); +} + +static int an8855_efuse_probe(struct platform_device *pdev) +{ + struct nvmem_config an8855_nvmem_config = { + .name = "an8855-efuse", + .size = AN8855_EFUSE_CELL * sizeof(u32), + .stride = sizeof(u32), + .word_size = sizeof(u32), + .reg_read = an8855_efuse_read, + }; + struct device *dev = &pdev->dev; + struct nvmem_device *nvmem; + struct regmap *regmap; + + /* Assign NVMEM priv to MFD regmap */ + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENOENT; + + an8855_nvmem_config.priv = regmap; + an8855_nvmem_config.dev = dev; + nvmem = devm_nvmem_register(dev, &an8855_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id an8855_efuse_of_match[] = { + { .compatible = "airoha,an8855-efuse", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, an8855_efuse_of_match); + +static struct platform_driver an8855_efuse_driver = { + .probe = an8855_efuse_probe, + .driver = { + .name = "an8855-efuse", + .of_match_table = an8855_efuse_of_match, + }, +}; +module_platform_driver(an8855_efuse_driver); + +MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>"); +MODULE_DESCRIPTION("Driver for AN8855 Switch EFUSE"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/apple-efuses.c b/drivers/nvmem/apple-efuses.c new file mode 100644 index 000000000..9913e77b8 --- /dev/null +++ b/drivers/nvmem/apple-efuses.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Apple SoC eFuse driver + * + * Copyright (C) The Asahi Linux Contributors + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> + +struct apple_efuses_priv { + void __iomem *fuses; +}; + +static int apple_efuses_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct apple_efuses_priv *priv = context; + u32 *dst = val; + + while (bytes >= sizeof(u32)) { + *dst++ = readl_relaxed(priv->fuses + offset); + bytes -= sizeof(u32); + offset += sizeof(u32); + } + + return 0; +} + +static int apple_efuses_probe(struct platform_device *pdev) +{ + struct apple_efuses_priv *priv; + struct resource *res; + struct nvmem_config config = { + .dev = &pdev->dev, + .add_legacy_fixed_of_cells = true, + .read_only = true, + .reg_read = apple_efuses_read, + .stride = sizeof(u32), + .word_size = sizeof(u32), + .name = "apple_efuses_nvmem", + .id = NVMEM_DEVID_AUTO, + .root_only = true, + }; + + priv = devm_kzalloc(config.dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->fuses = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(priv->fuses)) + return PTR_ERR(priv->fuses); + + config.priv = priv; + config.size = resource_size(res); + + return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config)); +} + +static const struct of_device_id apple_efuses_of_match[] = { + { .compatible = "apple,efuses", }, + {} +}; + +MODULE_DEVICE_TABLE(of, apple_efuses_of_match); + +static struct platform_driver apple_efuses_driver = { + .driver = { + .name = "apple_efuses", + .of_match_table = apple_efuses_of_match, + }, + .probe = apple_efuses_probe, +}; + +module_platform_driver(apple_efuses_driver); + +MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>"); +MODULE_DESCRIPTION("Apple SoC eFuse driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/apple-spmi-nvmem.c b/drivers/nvmem/apple-spmi-nvmem.c new file mode 100644 index 000000000..7acb0c07d --- /dev/null +++ b/drivers/nvmem/apple-spmi-nvmem.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SPMI NVMEM driver + * + * Copyright The Asahi Linux Contributors + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/spmi.h> +#include <linux/regmap.h> + +static const struct regmap_config apple_spmi_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xffff, +}; + +static int apple_spmi_nvmem_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct regmap *map = priv; + + return regmap_bulk_read(map, offset, val, bytes); +} + +static int apple_spmi_nvmem_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct regmap *map = priv; + + return regmap_bulk_write(map, offset, val, bytes); +} + +static int apple_spmi_nvmem_probe(struct spmi_device *sdev) +{ + struct regmap *regmap; + struct nvmem_config nvmem_cfg = { + .dev = &sdev->dev, + .name = "spmi_nvmem", + .id = NVMEM_DEVID_AUTO, + .word_size = 1, + .stride = 1, + .size = 0xffff, + .reg_read = apple_spmi_nvmem_read, + .reg_write = apple_spmi_nvmem_write, + }; + + regmap = devm_regmap_init_spmi_ext(sdev, &apple_spmi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + nvmem_cfg.priv = regmap; + + return PTR_ERR_OR_ZERO(devm_nvmem_register(&sdev->dev, &nvmem_cfg)); +} + +static const struct of_device_id apple_spmi_nvmem_id_table[] = { + { .compatible = "apple,spmi-nvmem" }, + { }, +}; +MODULE_DEVICE_TABLE(of, apple_spmi_nvmem_id_table); + +static struct spmi_driver apple_spmi_nvmem_driver = { + .probe = apple_spmi_nvmem_probe, + .driver = { + .name = "apple-spmi-nvmem", + .of_match_table = apple_spmi_nvmem_id_table, + }, +}; + +module_spmi_driver(apple_spmi_nvmem_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_DESCRIPTION("Apple SPMI NVMEM driver"); diff --git a/drivers/nvmem/at24.c b/drivers/nvmem/at24.c new file mode 100644 index 000000000..772c4d9fa --- /dev/null +++ b/drivers/nvmem/at24.c @@ -0,0 +1,871 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * at24.c - handle most I2C EEPROMs + * + * Copyright (C) 2005-2007 David Brownell + * Copyright (C) 2008 Wolfram Sang, Pengutronix + */ + +#include <linux/acpi.h> +#include <linux/bitops.h> +#include <linux/capability.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/nvmem-provider.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +/* Address pointer is 16 bit. */ +#define AT24_FLAG_ADDR16 BIT(7) +/* sysfs-entry will be read-only. */ +#define AT24_FLAG_READONLY BIT(6) +/* sysfs-entry will be world-readable. */ +#define AT24_FLAG_IRUGO BIT(5) +/* Take always 8 addresses (24c00). */ +#define AT24_FLAG_TAKE8ADDR BIT(4) +/* Factory-programmed serial number. */ +#define AT24_FLAG_SERIAL BIT(3) +/* Factory-programmed mac address. */ +#define AT24_FLAG_MAC BIT(2) +/* Does not auto-rollover reads to the next slave address. */ +#define AT24_FLAG_NO_RDROL BIT(1) + +/* + * I2C EEPROMs from most vendors are inexpensive and mostly interchangeable. + * Differences between different vendor product lines (like Atmel AT24C or + * MicroChip 24LC, etc) won't much matter for typical read/write access. + * There are also I2C RAM chips, likewise interchangeable. One example + * would be the PCF8570, which acts like a 24c02 EEPROM (256 bytes). + * + * However, misconfiguration can lose data. "Set 16-bit memory address" + * to a part with 8-bit addressing will overwrite data. Writing with too + * big a page size also loses data. And it's not safe to assume that the + * conventional addresses 0x50..0x57 only hold eeproms; a PCF8563 RTC + * uses 0x51, for just one example. + * + * Accordingly, explicit board-specific configuration data should be used + * in almost all cases. (One partial exception is an SMBus used to access + * "SPD" data for DRAM sticks. Those only use 24c02 EEPROMs.) + * + * So this driver uses "new style" I2C driver binding, expecting to be + * told what devices exist. That may be in arch/X/mach-Y/board-Z.c or + * similar kernel-resident tables; or, configuration data coming from + * a bootloader. + * + * Other than binding model, current differences from "eeprom" driver are + * that this one handles write access and isn't restricted to 24c02 devices. + * It also handles larger devices (32 kbit and up) with two-byte addresses, + * which won't work on pure SMBus systems. + */ + +struct at24_data { + /* + * Lock protects against activities from other Linux tasks, + * but not from changes by other I2C masters. + */ + struct mutex lock; + + unsigned int write_max; + unsigned int num_addresses; + unsigned int offset_adj; + + u32 byte_len; + u16 page_size; + u8 flags; + + struct nvmem_device *nvmem; + struct regulator *vcc_reg; + void (*read_post)(unsigned int off, char *buf, size_t count); + + /* + * Some chips tie up multiple I2C addresses; dummy devices reserve + * them for us. + */ + u8 bank_addr_shift; + struct regmap *client_regmaps[] __counted_by(num_addresses); +}; + +/* + * This parameter is to help this driver avoid blocking other drivers out + * of I2C for potentially troublesome amounts of time. With a 100 kHz I2C + * clock, one 256 byte read takes about 1/43 second which is excessive; + * but the 1/170 second it takes at 400 kHz may be quite reasonable; and + * at 1 MHz (Fm+) a 1/430 second delay could easily be invisible. + * + * This value is forced to be a power of two so that writes align on pages. + */ +static unsigned int at24_io_limit = 128; +module_param_named(io_limit, at24_io_limit, uint, 0); +MODULE_PARM_DESC(at24_io_limit, "Maximum bytes per I/O (default 128)"); + +/* + * Specs often allow 5 msec for a page write, sometimes 20 msec; + * it's important to recover from write timeouts. + */ +static unsigned int at24_write_timeout = 25; +module_param_named(write_timeout, at24_write_timeout, uint, 0); +MODULE_PARM_DESC(at24_write_timeout, "Time (in ms) to try writes (default 25)"); + +struct at24_chip_data { + u32 byte_len; + u8 flags; + u8 bank_addr_shift; + void (*read_post)(unsigned int off, char *buf, size_t count); +}; + +#define AT24_CHIP_DATA(_name, _len, _flags) \ + static const struct at24_chip_data _name = { \ + .byte_len = _len, .flags = _flags, \ + } + +#define AT24_CHIP_DATA_CB(_name, _len, _flags, _read_post) \ + static const struct at24_chip_data _name = { \ + .byte_len = _len, .flags = _flags, \ + .read_post = _read_post, \ + } + +#define AT24_CHIP_DATA_BS(_name, _len, _flags, _bank_addr_shift) \ + static const struct at24_chip_data _name = { \ + .byte_len = _len, .flags = _flags, \ + .bank_addr_shift = _bank_addr_shift \ + } + +static void at24_read_post_vaio(unsigned int off, char *buf, size_t count) +{ + int i; + + if (capable(CAP_SYS_ADMIN)) + return; + + /* + * Hide VAIO private settings to regular users: + * - BIOS passwords: bytes 0x00 to 0x0f + * - UUID: bytes 0x10 to 0x1f + * - Serial number: 0xc0 to 0xdf + */ + for (i = 0; i < count; i++) { + if ((off + i <= 0x1f) || + (off + i >= 0xc0 && off + i <= 0xdf)) + buf[i] = 0; + } +} + +/* needs 8 addresses as A0-A2 are ignored */ +AT24_CHIP_DATA(at24_data_24c00, 128 / 8, AT24_FLAG_TAKE8ADDR); +/* old variants can't be handled with this generic entry! */ +AT24_CHIP_DATA(at24_data_24c01, 1024 / 8, 0); +AT24_CHIP_DATA(at24_data_24cs01, 16, + AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24c02, 2048 / 8, 0); +AT24_CHIP_DATA(at24_data_24cs02, 16, + AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24mac402, 48 / 8, + AT24_FLAG_MAC | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24mac602, 64 / 8, + AT24_FLAG_MAC | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24aa025e48, 48 / 8, + AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24aa025e64, 64 / 8, + AT24_FLAG_READONLY); +/* spd is a 24c02 in memory DIMMs */ +AT24_CHIP_DATA(at24_data_spd, 2048 / 8, + AT24_FLAG_READONLY | AT24_FLAG_IRUGO); +/* 24c02_vaio is a 24c02 on some Sony laptops */ +AT24_CHIP_DATA_CB(at24_data_24c02_vaio, 2048 / 8, + AT24_FLAG_READONLY | AT24_FLAG_IRUGO, + at24_read_post_vaio); +AT24_CHIP_DATA(at24_data_24c04, 4096 / 8, 0); +AT24_CHIP_DATA(at24_data_24cs04, 16, + AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +/* 24rf08 quirk is handled at i2c-core */ +AT24_CHIP_DATA(at24_data_24c08, 8192 / 8, 0); +AT24_CHIP_DATA(at24_data_24cs08, 16, + AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24c16, 16384 / 8, 0); +AT24_CHIP_DATA(at24_data_24cs16, 16, + AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24c32, 32768 / 8, AT24_FLAG_ADDR16); +/* M24C32-D Additional Write lockable page (M24C32-D order codes) */ +AT24_CHIP_DATA(at24_data_24c32d_wlp, 32, AT24_FLAG_ADDR16); +AT24_CHIP_DATA(at24_data_24cs32, 16, + AT24_FLAG_ADDR16 | AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24c64, 65536 / 8, AT24_FLAG_ADDR16); +/* M24C64-D Additional Write lockable page (M24C64-D order codes) */ +AT24_CHIP_DATA(at24_data_24c64d_wlp, 32, AT24_FLAG_ADDR16); +AT24_CHIP_DATA(at24_data_24cs64, 16, + AT24_FLAG_ADDR16 | AT24_FLAG_SERIAL | AT24_FLAG_READONLY); +AT24_CHIP_DATA(at24_data_24c128, 131072 / 8, AT24_FLAG_ADDR16); +AT24_CHIP_DATA(at24_data_24c256, 262144 / 8, AT24_FLAG_ADDR16); +/* M24256E Additional Write lockable page (M24256E-F order codes) */ +AT24_CHIP_DATA(at24_data_24256e_wlp, 64, AT24_FLAG_ADDR16); +AT24_CHIP_DATA(at24_data_24c512, 524288 / 8, AT24_FLAG_ADDR16); +AT24_CHIP_DATA(at24_data_24c1024, 1048576 / 8, AT24_FLAG_ADDR16); +AT24_CHIP_DATA_BS(at24_data_24c1025, 1048576 / 8, AT24_FLAG_ADDR16, 2); +AT24_CHIP_DATA(at24_data_24c2048, 2097152 / 8, AT24_FLAG_ADDR16); +/* identical to 24c08 ? */ +AT24_CHIP_DATA(at24_data_INT3499, 8192 / 8, 0); + +static const struct i2c_device_id at24_ids[] = { + { .name = "24c00", .driver_data = (kernel_ulong_t)&at24_data_24c00 }, + { .name = "24c01", .driver_data = (kernel_ulong_t)&at24_data_24c01 }, + { .name = "24cs01", .driver_data = (kernel_ulong_t)&at24_data_24cs01 }, + { .name = "24c02", .driver_data = (kernel_ulong_t)&at24_data_24c02 }, + { .name = "24cs02", .driver_data = (kernel_ulong_t)&at24_data_24cs02 }, + { .name = "24mac402", .driver_data = (kernel_ulong_t)&at24_data_24mac402 }, + { .name = "24mac602", .driver_data = (kernel_ulong_t)&at24_data_24mac602 }, + { .name = "24aa025e48", .driver_data = (kernel_ulong_t)&at24_data_24aa025e48 }, + { .name = "24aa025e64", .driver_data = (kernel_ulong_t)&at24_data_24aa025e64 }, + { .name = "spd", .driver_data = (kernel_ulong_t)&at24_data_spd }, + { .name = "24c02-vaio", .driver_data = (kernel_ulong_t)&at24_data_24c02_vaio }, + { .name = "24c04", .driver_data = (kernel_ulong_t)&at24_data_24c04 }, + { .name = "24cs04", .driver_data = (kernel_ulong_t)&at24_data_24cs04 }, + { .name = "24c08", .driver_data = (kernel_ulong_t)&at24_data_24c08 }, + { .name = "24cs08", .driver_data = (kernel_ulong_t)&at24_data_24cs08 }, + { .name = "24c16", .driver_data = (kernel_ulong_t)&at24_data_24c16 }, + { .name = "24cs16", .driver_data = (kernel_ulong_t)&at24_data_24cs16 }, + { .name = "24c32", .driver_data = (kernel_ulong_t)&at24_data_24c32 }, + { .name = "24c32d-wl", .driver_data = (kernel_ulong_t)&at24_data_24c32d_wlp }, + { .name = "24cs32", .driver_data = (kernel_ulong_t)&at24_data_24cs32 }, + { .name = "24c64", .driver_data = (kernel_ulong_t)&at24_data_24c64 }, + { .name = "24c64-wl", .driver_data = (kernel_ulong_t)&at24_data_24c64d_wlp }, + { .name = "24cs64", .driver_data = (kernel_ulong_t)&at24_data_24cs64 }, + { .name = "24c128", .driver_data = (kernel_ulong_t)&at24_data_24c128 }, + { .name = "24c256", .driver_data = (kernel_ulong_t)&at24_data_24c256 }, + { .name = "24256e-wl", .driver_data = (kernel_ulong_t)&at24_data_24256e_wlp }, + { .name = "24c512", .driver_data = (kernel_ulong_t)&at24_data_24c512 }, + { .name = "24c1024", .driver_data = (kernel_ulong_t)&at24_data_24c1024 }, + { .name = "24c1025", .driver_data = (kernel_ulong_t)&at24_data_24c1025 }, + { .name = "24c2048", .driver_data = (kernel_ulong_t)&at24_data_24c2048 }, + { .name = "at24", .driver_data = 0 }, + { /* END OF LIST */ } +}; +MODULE_DEVICE_TABLE(i2c, at24_ids); + +static const struct of_device_id at24_of_match[] = { + { .compatible = "atmel,24c00", .data = &at24_data_24c00 }, + { .compatible = "atmel,24c01", .data = &at24_data_24c01 }, + { .compatible = "atmel,24cs01", .data = &at24_data_24cs01 }, + { .compatible = "atmel,24c02", .data = &at24_data_24c02 }, + { .compatible = "atmel,24cs02", .data = &at24_data_24cs02 }, + { .compatible = "atmel,24mac402", .data = &at24_data_24mac402 }, + { .compatible = "atmel,24mac602", .data = &at24_data_24mac602 }, + { .compatible = "atmel,spd", .data = &at24_data_spd }, + { .compatible = "atmel,24c04", .data = &at24_data_24c04 }, + { .compatible = "atmel,24cs04", .data = &at24_data_24cs04 }, + { .compatible = "atmel,24c08", .data = &at24_data_24c08 }, + { .compatible = "atmel,24cs08", .data = &at24_data_24cs08 }, + { .compatible = "atmel,24c16", .data = &at24_data_24c16 }, + { .compatible = "atmel,24cs16", .data = &at24_data_24cs16 }, + { .compatible = "atmel,24c32", .data = &at24_data_24c32 }, + { .compatible = "atmel,24c32d-wl", .data = &at24_data_24c32d_wlp }, + { .compatible = "atmel,24cs32", .data = &at24_data_24cs32 }, + { .compatible = "atmel,24c64", .data = &at24_data_24c64 }, + { .compatible = "atmel,24c64d-wl", .data = &at24_data_24c64d_wlp }, + { .compatible = "atmel,24cs64", .data = &at24_data_24cs64 }, + { .compatible = "atmel,24c128", .data = &at24_data_24c128 }, + { .compatible = "atmel,24c256", .data = &at24_data_24c256 }, + { .compatible = "atmel,24c512", .data = &at24_data_24c512 }, + { .compatible = "atmel,24c1024", .data = &at24_data_24c1024 }, + { .compatible = "atmel,24c1025", .data = &at24_data_24c1025 }, + { .compatible = "atmel,24c2048", .data = &at24_data_24c2048 }, + { .compatible = "microchip,24aa025e48", .data = &at24_data_24aa025e48 }, + { .compatible = "microchip,24aa025e64", .data = &at24_data_24aa025e64 }, + { .compatible = "st,24256e-wl", .data = &at24_data_24256e_wlp }, + { /* END OF LIST */ }, +}; +MODULE_DEVICE_TABLE(of, at24_of_match); + +static const struct acpi_device_id at24_acpi_ids[] = { + { "INT3499", (kernel_ulong_t)&at24_data_INT3499 }, + { "TPF0001", (kernel_ulong_t)&at24_data_24c1024 }, + { /* END OF LIST */ } +}; +MODULE_DEVICE_TABLE(acpi, at24_acpi_ids); + +/* + * This routine supports chips which consume multiple I2C addresses. It + * computes the addressing information to be used for a given r/w request. + * Assumes that sanity checks for offset happened at sysfs-layer. + * + * Slave address and byte offset derive from the offset. Always + * set the byte address; on a multi-master board, another master + * may have changed the chip's "current" address pointer. + */ +static struct regmap *at24_translate_offset(struct at24_data *at24, + unsigned int *offset) +{ + unsigned int i; + + if (at24->flags & AT24_FLAG_ADDR16) { + i = *offset >> 16; + *offset &= 0xffff; + } else { + i = *offset >> 8; + *offset &= 0xff; + } + + return at24->client_regmaps[i]; +} + +static struct device *at24_base_client_dev(struct at24_data *at24) +{ + return regmap_get_device(at24->client_regmaps[0]); +} + +static size_t at24_adjust_read_count(struct at24_data *at24, + unsigned int offset, size_t count) +{ + unsigned int bits; + size_t remainder; + + /* + * In case of multi-address chips that don't rollover reads to + * the next slave address: truncate the count to the slave boundary, + * so that the read never straddles slaves. + */ + if (at24->flags & AT24_FLAG_NO_RDROL) { + bits = (at24->flags & AT24_FLAG_ADDR16) ? 16 : 8; + remainder = BIT(bits) - offset; + if (count > remainder) + count = remainder; + } + + if (count > at24_io_limit) + count = at24_io_limit; + + return count; +} + +static ssize_t at24_regmap_read(struct at24_data *at24, char *buf, + unsigned int offset, size_t count) +{ + unsigned long timeout, read_time; + struct regmap *regmap; + int ret; + + regmap = at24_translate_offset(at24, &offset); + count = at24_adjust_read_count(at24, offset, count); + + /* adjust offset for mac and serial read ops */ + offset += at24->offset_adj; + + timeout = jiffies + msecs_to_jiffies(at24_write_timeout); + do { + /* + * The timestamp shall be taken before the actual operation + * to avoid a premature timeout in case of high CPU load. + */ + read_time = jiffies; + + ret = regmap_bulk_read(regmap, offset, buf, count); + dev_dbg(regmap_get_device(regmap), "read %zu@%d --> %d (%ld)\n", + count, offset, ret, jiffies); + if (!ret) + return count; + + usleep_range(1000, 1500); + } while (time_before(read_time, timeout)); + + return -ETIMEDOUT; +} + +/* + * Note that if the hardware write-protect pin is pulled high, the whole + * chip is normally write protected. But there are plenty of product + * variants here, including OTP fuses and partial chip protect. + * + * We only use page mode writes; the alternative is sloooow. These routines + * write at most one page. + */ + +static size_t at24_adjust_write_count(struct at24_data *at24, + unsigned int offset, size_t count) +{ + unsigned int next_page; + + /* write_max is at most a page */ + if (count > at24->write_max) + count = at24->write_max; + + /* Never roll over backwards, to the start of this page */ + next_page = roundup(offset + 1, at24->page_size); + if (offset + count > next_page) + count = next_page - offset; + + return count; +} + +static ssize_t at24_regmap_write(struct at24_data *at24, const char *buf, + unsigned int offset, size_t count) +{ + unsigned long timeout, write_time; + struct regmap *regmap; + int ret; + + regmap = at24_translate_offset(at24, &offset); + count = at24_adjust_write_count(at24, offset, count); + timeout = jiffies + msecs_to_jiffies(at24_write_timeout); + + do { + /* + * The timestamp shall be taken before the actual operation + * to avoid a premature timeout in case of high CPU load. + */ + write_time = jiffies; + + ret = regmap_bulk_write(regmap, offset, buf, count); + dev_dbg(regmap_get_device(regmap), "write %zu@%d --> %d (%ld)\n", + count, offset, ret, jiffies); + if (!ret) + return count; + + usleep_range(1000, 1500); + } while (time_before(write_time, timeout)); + + return -ETIMEDOUT; +} + +static int at24_read(void *priv, unsigned int off, void *val, size_t count) +{ + struct at24_data *at24; + struct device *dev; + char *buf = val; + int i, ret; + + at24 = priv; + dev = at24_base_client_dev(at24); + + if (unlikely(!count)) + return count; + + if (off + count > at24->byte_len) + return -EINVAL; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + /* + * Read data from chip, protecting against concurrent updates + * from this host, but not from other I2C masters. + */ + mutex_lock(&at24->lock); + + for (i = 0; count; i += ret, count -= ret) { + ret = at24_regmap_read(at24, buf + i, off + i, count); + if (ret < 0) { + mutex_unlock(&at24->lock); + pm_runtime_put(dev); + return ret; + } + } + + mutex_unlock(&at24->lock); + + pm_runtime_put(dev); + + if (unlikely(at24->read_post)) + at24->read_post(off, buf, i); + + return 0; +} + +static int at24_write(void *priv, unsigned int off, void *val, size_t count) +{ + struct at24_data *at24; + struct device *dev; + char *buf = val; + int ret; + + at24 = priv; + dev = at24_base_client_dev(at24); + + if (unlikely(!count)) + return -EINVAL; + + if (off + count > at24->byte_len) + return -EINVAL; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + /* + * Write data to chip, protecting against concurrent updates + * from this host, but not from other I2C masters. + */ + mutex_lock(&at24->lock); + + while (count) { + ret = at24_regmap_write(at24, buf, off, count); + if (ret < 0) { + mutex_unlock(&at24->lock); + pm_runtime_put(dev); + return ret; + } + buf += ret; + off += ret; + count -= ret; + } + + mutex_unlock(&at24->lock); + + pm_runtime_put(dev); + + return 0; +} + +static int at24_make_dummy_client(struct at24_data *at24, unsigned int index, + struct i2c_client *base_client, + struct regmap_config *regmap_config) +{ + struct i2c_client *dummy_client; + struct regmap *regmap; + + dummy_client = devm_i2c_new_dummy_device(&base_client->dev, + base_client->adapter, + base_client->addr + + (index << at24->bank_addr_shift)); + if (IS_ERR(dummy_client)) + return PTR_ERR(dummy_client); + + regmap = devm_regmap_init_i2c(dummy_client, regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + at24->client_regmaps[index] = regmap; + + return 0; +} + +static unsigned int at24_get_offset_adj(u8 flags, unsigned int byte_len) +{ + if (flags & AT24_FLAG_MAC) { + /* EUI-48 starts from 0x9a, EUI-64 from 0x98 */ + return 0xa0 - byte_len; + } else if (flags & AT24_FLAG_SERIAL && flags & AT24_FLAG_ADDR16) { + /* + * For 16 bit address pointers, the word address must contain + * a '10' sequence in bits 11 and 10 regardless of the + * intended position of the address pointer. + */ + return 0x0800; + } else if (flags & AT24_FLAG_SERIAL) { + /* + * Otherwise the word address must begin with a '10' sequence, + * regardless of the intended address. + */ + return 0x0080; + } else { + return 0; + } +} + +static void at24_probe_temp_sensor(struct i2c_client *client) +{ + struct at24_data *at24 = i2c_get_clientdata(client); + struct i2c_board_info info = { .type = "jc42" }; + int ret; + u8 val; + + /* + * Byte 2 has value 11 for DDR3, earlier versions don't + * support the thermal sensor present flag + */ + ret = at24_read(at24, 2, &val, 1); + if (ret || val != 11) + return; + + /* Byte 32, bit 7 is set if temp sensor is present */ + ret = at24_read(at24, 32, &val, 1); + if (ret || !(val & BIT(7))) + return; + + info.addr = 0x18 | (client->addr & 7); + + i2c_new_client_device(client->adapter, &info); +} + +static int at24_probe(struct i2c_client *client) +{ + struct regmap_config regmap_config = { }; + struct nvmem_config nvmem_config = { }; + u32 byte_len, page_size, flags, addrw; + const struct at24_chip_data *cdata; + struct device *dev = &client->dev; + bool i2c_fn_i2c, i2c_fn_block; + unsigned int i, num_addresses; + struct at24_data *at24; + bool full_power; + struct regmap *regmap; + bool writable; + u8 test_byte; + int err; + + i2c_fn_i2c = i2c_check_functionality(client->adapter, I2C_FUNC_I2C); + i2c_fn_block = i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_WRITE_I2C_BLOCK); + + cdata = i2c_get_match_data(client); + if (!cdata) + return -ENODEV; + + err = device_property_read_u32(dev, "pagesize", &page_size); + if (err) + /* + * This is slow, but we can't know all eeproms, so we better + * play safe. Specifying custom eeprom-types via device tree + * or properties is recommended anyhow. + */ + page_size = 1; + + flags = cdata->flags; + if (device_property_present(dev, "read-only")) + flags |= AT24_FLAG_READONLY; + if (device_property_present(dev, "no-read-rollover")) + flags |= AT24_FLAG_NO_RDROL; + + err = device_property_read_u32(dev, "address-width", &addrw); + if (!err) { + switch (addrw) { + case 8: + if (flags & AT24_FLAG_ADDR16) + dev_warn(dev, + "Override address width to be 8, while default is 16\n"); + flags &= ~AT24_FLAG_ADDR16; + break; + case 16: + flags |= AT24_FLAG_ADDR16; + break; + default: + dev_warn(dev, "Bad \"address-width\" property: %u\n", + addrw); + } + } + + err = device_property_read_u32(dev, "size", &byte_len); + if (err) + byte_len = cdata->byte_len; + + if (!i2c_fn_i2c && !i2c_fn_block) + page_size = 1; + + if (!page_size) + return dev_err_probe(dev, -EINVAL, "page_size must not be 0!\n"); + + if (!is_power_of_2(page_size)) + dev_warn(dev, "page_size looks suspicious (no power of 2)!\n"); + + err = device_property_read_u32(dev, "num-addresses", &num_addresses); + if (err) { + if (flags & AT24_FLAG_TAKE8ADDR) + num_addresses = 8; + else + num_addresses = DIV_ROUND_UP(byte_len, + (flags & AT24_FLAG_ADDR16) ? 65536 : 256); + } + + if ((flags & AT24_FLAG_SERIAL) && (flags & AT24_FLAG_MAC)) + return dev_err_probe(dev, -EINVAL, + "invalid device data - cannot have both AT24_FLAG_SERIAL & AT24_FLAG_MAC."); + + regmap_config.val_bits = 8; + regmap_config.reg_bits = (flags & AT24_FLAG_ADDR16) ? 16 : 8; + regmap_config.disable_locking = true; + + regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + at24 = devm_kzalloc(dev, struct_size(at24, client_regmaps, num_addresses), + GFP_KERNEL); + if (!at24) + return -ENOMEM; + + mutex_init(&at24->lock); + at24->byte_len = byte_len; + at24->page_size = page_size; + at24->flags = flags; + at24->read_post = cdata->read_post; + at24->bank_addr_shift = cdata->bank_addr_shift; + at24->num_addresses = num_addresses; + at24->offset_adj = at24_get_offset_adj(flags, byte_len); + at24->client_regmaps[0] = regmap; + + at24->vcc_reg = devm_regulator_get(dev, "vcc"); + if (IS_ERR(at24->vcc_reg)) + return PTR_ERR(at24->vcc_reg); + + writable = !(flags & AT24_FLAG_READONLY); + if (writable) { + at24->write_max = min_t(unsigned int, + page_size, at24_io_limit); + if (!i2c_fn_i2c && at24->write_max > I2C_SMBUS_BLOCK_MAX) + at24->write_max = I2C_SMBUS_BLOCK_MAX; + } + + /* use dummy devices for multiple-address chips */ + for (i = 1; i < num_addresses; i++) { + err = at24_make_dummy_client(at24, i, client, ®map_config); + if (err) + return err; + } + + /* + * We initialize nvmem_config.id to NVMEM_DEVID_AUTO even if the + * label property is set as some platform can have multiple eeproms + * with same label and we can not register each of those with same + * label. Failing to register those eeproms trigger cascade failure + * on such platform. + */ + nvmem_config.id = NVMEM_DEVID_AUTO; + + if (device_property_present(dev, "label")) { + err = device_property_read_string(dev, "label", + &nvmem_config.name); + if (err) + return err; + } else { + nvmem_config.name = dev_name(dev); + } + + nvmem_config.type = NVMEM_TYPE_EEPROM; + nvmem_config.dev = dev; + nvmem_config.read_only = !writable; + nvmem_config.root_only = !(flags & AT24_FLAG_IRUGO); + nvmem_config.owner = THIS_MODULE; + nvmem_config.compat = true; + nvmem_config.base_dev = dev; + nvmem_config.reg_read = at24_read; + nvmem_config.reg_write = at24_write; + nvmem_config.priv = at24; + nvmem_config.stride = 1; + nvmem_config.word_size = 1; + nvmem_config.size = byte_len; + + i2c_set_clientdata(client, at24); + + full_power = acpi_dev_state_d0(&client->dev); + if (full_power) { + err = regulator_enable(at24->vcc_reg); + if (err) + return dev_err_probe(dev, err, "Failed to enable vcc regulator\n"); + + pm_runtime_set_active(dev); + } + pm_runtime_enable(dev); + + /* + * Perform a one-byte test read to verify that the chip is functional, + * unless powering on the device is to be avoided during probe (i.e. + * it's powered off right now). + */ + if (full_power) { + err = at24_read(at24, 0, &test_byte, 1); + if (err) { + pm_runtime_disable(dev); + if (!pm_runtime_status_suspended(dev)) + regulator_disable(at24->vcc_reg); + return -ENODEV; + } + } + + at24->nvmem = devm_nvmem_register(dev, &nvmem_config); + if (IS_ERR(at24->nvmem)) { + pm_runtime_disable(dev); + if (!pm_runtime_status_suspended(dev)) + regulator_disable(at24->vcc_reg); + return dev_err_probe(dev, PTR_ERR(at24->nvmem), + "failed to register nvmem\n"); + } + + /* If this a SPD EEPROM, probe for DDR3 thermal sensor */ + if (cdata == &at24_data_spd) + at24_probe_temp_sensor(client); + + pm_runtime_idle(dev); + + if (writable) + dev_info(dev, "%u byte %s EEPROM, writable, %u bytes/write\n", + byte_len, client->name, at24->write_max); + else + dev_info(dev, "%u byte %s EEPROM, read-only\n", + byte_len, client->name); + + return 0; +} + +static void at24_remove(struct i2c_client *client) +{ + struct at24_data *at24 = i2c_get_clientdata(client); + + pm_runtime_disable(&client->dev); + if (acpi_dev_state_d0(&client->dev)) { + if (!pm_runtime_status_suspended(&client->dev)) + regulator_disable(at24->vcc_reg); + pm_runtime_set_suspended(&client->dev); + } +} + +static int __maybe_unused at24_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct at24_data *at24 = i2c_get_clientdata(client); + + return regulator_disable(at24->vcc_reg); +} + +static int __maybe_unused at24_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct at24_data *at24 = i2c_get_clientdata(client); + + return regulator_enable(at24->vcc_reg); +} + +static const struct dev_pm_ops at24_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(at24_suspend, at24_resume, NULL) +}; + +static struct i2c_driver at24_driver = { + .driver = { + .name = "at24", + .pm = &at24_pm_ops, + .of_match_table = at24_of_match, + .acpi_match_table = at24_acpi_ids, + }, + .probe = at24_probe, + .remove = at24_remove, + .id_table = at24_ids, + .flags = I2C_DRV_ACPI_WAIVE_D0_PROBE, +}; + +static int __init at24_init(void) +{ + if (!at24_io_limit) { + pr_err("at24: at24_io_limit must not be 0!\n"); + return -EINVAL; + } + + at24_io_limit = rounddown_pow_of_two(at24_io_limit); + return i2c_add_driver(&at24_driver); +} +module_init(at24_init); + +static void __exit at24_exit(void) +{ + i2c_del_driver(&at24_driver); +} +module_exit(at24_exit); + +MODULE_DESCRIPTION("Driver for most I2C EEPROMs"); +MODULE_AUTHOR("David Brownell and Wolfram Sang"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/at25.c b/drivers/nvmem/at25.c new file mode 100644 index 000000000..bc2cfb75d --- /dev/null +++ b/drivers/nvmem/at25.c @@ -0,0 +1,585 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for most of the SPI EEPROMs, such as Atmel AT25 models + * and Cypress FRAMs FM25 models. + * + * Copyright (C) 2006 David Brownell + */ + +#include <linux/bits.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/sched.h> +#include <linux/slab.h> + +#include <linux/spi/eeprom.h> +#include <linux/spi/spi.h> +#include <linux/spi/spi-mem.h> + +#include <linux/nvmem-provider.h> + +/* + * NOTE: this is an *EEPROM* driver. The vagaries of product naming + * mean that some AT25 products are EEPROMs, and others are FLASH. + * Handle FLASH chips with the drivers/mtd/devices/m25p80.c driver, + * not this one! + * + * EEPROMs that can be used with this driver include, for example: + * AT25M02, AT25128B + */ + +#define FM25_SN_LEN 8 /* serial number length */ +#define FM25_MAX_ID_LEN 9 /* ID length */ +#define EE_MAXADDRLEN 3 /* 24 bit addresses, up to 2 MBytes */ + +struct at25_data { + struct spi_eeprom chip; + struct spi_mem *spimem; + struct mutex lock; + unsigned addrlen; + struct nvmem_config nvmem_config; + struct nvmem_device *nvmem; + u8 sernum[FM25_SN_LEN]; + u8 id[FM25_MAX_ID_LEN]; + u8 id_len; +}; + +#define AT25_WREN 0x06 /* latch the write enable */ +#define AT25_WRDI 0x04 /* reset the write enable */ +#define AT25_RDSR 0x05 /* read status register */ +#define AT25_WRSR 0x01 /* write status register */ +#define AT25_READ 0x03 /* read byte(s) */ +#define AT25_WRITE 0x02 /* write byte(s)/sector */ +#define FM25_SLEEP 0xb9 /* enter sleep mode */ +#define FM25_RDID 0x9f /* read device ID */ +#define FM25_RDSN 0xc3 /* read S/N */ + +#define AT25_SR_nRDY 0x01 /* nRDY = write-in-progress */ +#define AT25_SR_WEN 0x02 /* write enable (latched) */ +#define AT25_SR_BP0 0x04 /* BP for software writeprotect */ +#define AT25_SR_BP1 0x08 +#define AT25_SR_WPEN 0x80 /* writeprotect enable */ + +#define AT25_INSTR_BIT3 0x08 /* additional address bit in instr */ + +/* + * Specs often allow 5ms for a page write, sometimes 20ms; + * it's important to recover from write timeouts. + */ +#define EE_TIMEOUT 25 + +/*-------------------------------------------------------------------------*/ + +#define io_limit PAGE_SIZE /* bytes */ + +/* Handle the address MSB as part of instruction byte */ +static u8 at25_instr(struct at25_data *at25, u8 instr, unsigned int off) +{ + if (!(at25->chip.flags & EE_INSTR_BIT3_IS_ADDR)) + return instr; + if (off < BIT(at25->addrlen * 8)) + return instr; + return instr | AT25_INSTR_BIT3; +} + +static int at25_ee_read(void *priv, unsigned int offset, + void *val, size_t count) +{ + u8 *bounce __free(kfree) = kmalloc(min(count, io_limit), GFP_KERNEL); + struct at25_data *at25 = priv; + char *buf = val; + unsigned int msg_offset = offset; + size_t bytes_left = count; + size_t segment; + int status; + + if (!bounce) + return -ENOMEM; + + if (unlikely(offset >= at25->chip.byte_len)) + return -EINVAL; + if ((offset + count) > at25->chip.byte_len) + count = at25->chip.byte_len - offset; + if (unlikely(!count)) + return -EINVAL; + + do { + struct spi_mem_op op; + + segment = min(bytes_left, io_limit); + + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(at25_instr(at25, AT25_READ, + msg_offset), 1), + SPI_MEM_OP_ADDR(at25->addrlen, msg_offset, 1), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(segment, bounce, 1)); + + status = spi_mem_adjust_op_size(at25->spimem, &op); + if (status) + return status; + segment = op.data.nbytes; + + mutex_lock(&at25->lock); + status = spi_mem_exec_op(at25->spimem, &op); + mutex_unlock(&at25->lock); + if (status) + return status; + memcpy(buf, bounce, segment); + + msg_offset += segment; + buf += segment; + bytes_left -= segment; + } while (bytes_left > 0); + + dev_dbg(&at25->spimem->spi->dev, "read %zu bytes at %d\n", + count, offset); + return 0; +} + +/* + * Read extra registers as ID or serial number + * + * Allow for the callers to provide @buf on stack (not necessary DMA-capable) + * by allocating a bounce buffer internally. + */ +static int fm25_aux_read(struct at25_data *at25, u8 *buf, uint8_t command, + int len) +{ + u8 *bounce __free(kfree) = kmalloc(len, GFP_KERNEL); + struct spi_mem_op op; + int status; + + if (!bounce) + return -ENOMEM; + + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(command, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(len, bounce, 1)); + + status = spi_mem_exec_op(at25->spimem, &op); + dev_dbg(&at25->spimem->spi->dev, "read %d aux bytes --> %d\n", len, status); + if (status) + return status; + + memcpy(buf, bounce, len); + + return 0; +} + +static ssize_t sernum_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct at25_data *at25; + + at25 = dev_get_drvdata(dev); + return sysfs_emit(buf, "%*ph\n", (int)sizeof(at25->sernum), at25->sernum); +} +static DEVICE_ATTR_RO(sernum); + +static ssize_t jedec_id_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct at25_data *at25; + + at25 = dev_get_drvdata(dev); + + if (!at25->id_len) + return -EOPNOTSUPP; + + return sysfs_emit(buf, "%*phN\n", at25->id_len, at25->id); +} +static DEVICE_ATTR_RO(jedec_id); + +static struct attribute *at25_attrs[] = { + &dev_attr_sernum.attr, + &dev_attr_jedec_id.attr, + NULL, +}; +ATTRIBUTE_GROUPS(at25); + +/* + * Poll Read Status Register with timeout + * + * Return: + * 0, if the chip is ready + * [positive] Status Register value as-is, if the chip is busy + * [negative] error code in case of read failure + */ +static int at25_wait_ready(struct at25_data *at25) +{ + u8 *bounce __free(kfree) = kmalloc(1, GFP_KERNEL); + struct spi_mem_op op; + int status; + + if (!bounce) + return -ENOMEM; + + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(AT25_RDSR, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_IN(1, bounce, 1)); + + read_poll_timeout(spi_mem_exec_op, status, + status || !(bounce[0] & AT25_SR_nRDY), false, + USEC_PER_MSEC, USEC_PER_MSEC * EE_TIMEOUT, + at25->spimem, &op); + if (status < 0) + return status; + if (!(bounce[0] & AT25_SR_nRDY)) + return 0; + + return bounce[0]; +} + +static int at25_ee_write(void *priv, unsigned int off, void *val, size_t count) +{ + u8 *bounce __free(kfree) = kmalloc(min(count, io_limit), GFP_KERNEL); + struct at25_data *at25 = priv; + const char *buf = val; + unsigned int buf_size; + int status; + + if (unlikely(off >= at25->chip.byte_len)) + return -EFBIG; + if ((off + count) > at25->chip.byte_len) + count = at25->chip.byte_len - off; + if (unlikely(!count)) + return -EINVAL; + + buf_size = at25->chip.page_size; + + if (!bounce) + return -ENOMEM; + + /* + * For write, rollover is within the page ... so we write at + * most one page, then manually roll over to the next page. + */ + guard(mutex)(&at25->lock); + do { + struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(AT25_WREN, 1), + SPI_MEM_OP_NO_ADDR, + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_NO_DATA); + unsigned int segment; + + status = spi_mem_exec_op(at25->spimem, &op); + if (status < 0) { + dev_dbg(&at25->spimem->spi->dev, "WREN --> %d\n", status); + return status; + } + + /* Write as much of a page as we can */ + segment = buf_size - (off % buf_size); + if (segment > count) + segment = count; + if (segment > io_limit) + segment = io_limit; + + op = (struct spi_mem_op)SPI_MEM_OP(SPI_MEM_OP_CMD(at25_instr(at25, AT25_WRITE, off), + 1), + SPI_MEM_OP_ADDR(at25->addrlen, off, 1), + SPI_MEM_OP_NO_DUMMY, + SPI_MEM_OP_DATA_OUT(segment, bounce, 1)); + + status = spi_mem_adjust_op_size(at25->spimem, &op); + if (status) + return status; + segment = op.data.nbytes; + + memcpy(bounce, buf, segment); + + status = spi_mem_exec_op(at25->spimem, &op); + dev_dbg(&at25->spimem->spi->dev, "write %u bytes at %u --> %d\n", + segment, off, status); + if (status) + return status; + + /* + * REVISIT this should detect (or prevent) failed writes + * to read-only sections of the EEPROM... + */ + + status = at25_wait_ready(at25); + if (status < 0) { + dev_err_probe(&at25->spimem->spi->dev, status, + "Read Status Redister command failed\n"); + return status; + } + if (status) { + dev_dbg(&at25->spimem->spi->dev, + "Status %02x\n", status); + dev_err(&at25->spimem->spi->dev, + "write %u bytes offset %u, timeout after %u msecs\n", + segment, off, EE_TIMEOUT); + return -ETIMEDOUT; + } + + off += segment; + buf += segment; + count -= segment; + + } while (count > 0); + + return status; +} + +/*-------------------------------------------------------------------------*/ + +static int at25_fw_to_chip(struct device *dev, struct spi_eeprom *chip) +{ + u32 val; + int err; + + strscpy(chip->name, "at25", sizeof(chip->name)); + + err = device_property_read_u32(dev, "size", &val); + if (err) + err = device_property_read_u32(dev, "at25,byte-len", &val); + if (err) { + dev_err(dev, "Error: missing \"size\" property\n"); + return err; + } + chip->byte_len = val; + + err = device_property_read_u32(dev, "pagesize", &val); + if (err) + err = device_property_read_u32(dev, "at25,page-size", &val); + if (err) { + dev_err(dev, "Error: missing \"pagesize\" property\n"); + return err; + } + chip->page_size = val; + + err = device_property_read_u32(dev, "address-width", &val); + if (err) { + err = device_property_read_u32(dev, "at25,addr-mode", &val); + if (err) { + dev_err(dev, "Error: missing \"address-width\" property\n"); + return err; + } + chip->flags = (u16)val; + } else { + switch (val) { + case 9: + chip->flags |= EE_INSTR_BIT3_IS_ADDR; + fallthrough; + case 8: + chip->flags |= EE_ADDR1; + break; + case 16: + chip->flags |= EE_ADDR2; + break; + case 24: + chip->flags |= EE_ADDR3; + break; + default: + dev_err(dev, + "Error: bad \"address-width\" property: %u\n", + val); + return -ENODEV; + } + if (device_property_present(dev, "read-only")) + chip->flags |= EE_READONLY; + } + return 0; +} + +static int at25_fram_to_chip(struct device *dev, struct spi_eeprom *chip) +{ + struct at25_data *at25 = container_of(chip, struct at25_data, chip); + u8 sernum[FM25_SN_LEN]; + u8 id[FM25_MAX_ID_LEN]; + u32 val; + int i; + + strscpy(chip->name, "fm25", sizeof(chip->name)); + + if (!device_property_read_u32(dev, "size", &val)) { + chip->byte_len = val; + } else { + /* Get ID of chip */ + fm25_aux_read(at25, id, FM25_RDID, FM25_MAX_ID_LEN); + + /* Store the unprocessed ID for exposing via sysfs */ + memcpy(at25->id, id, FM25_MAX_ID_LEN); + at25->id_len = FM25_MAX_ID_LEN; + + /* There are inside-out FRAM variations, detect them and reverse the ID bytes */ + if (id[6] == 0x7f && id[2] == 0xc2) + for (i = 0; i < ARRAY_SIZE(id) / 2; i++) { + u8 tmp = id[i]; + int j = ARRAY_SIZE(id) - i - 1; + + id[i] = id[j]; + id[j] = tmp; + } + + if (id[6] == 0xc2) { + at25->id_len = 9; + switch (id[7]) { + case 0x21 ... 0x26: + chip->byte_len = BIT(id[7] - 0x21 + 4) * 1024; + break; + case 0x2a ... 0x30: + /* CY15B102QN ... CY15B116QN */ + chip->byte_len = BIT(((id[7] >> 1) & 0xf) + 13); + break; + default: + dev_err(dev, "Error: unsupported size (id %02x)\n", id[7]); + return -ENODEV; + } + } else if (id[2] == 0x82 && id[3] == 0x06) { + at25->id_len = 8; + switch (id[1]) { + case 0x51 ... 0x54: + /* CY15B102QSN ... CY15B204QSN */ + chip->byte_len = BIT(((id[0] >> 3) & 0x1F) + 9); + break; + default: + dev_err(dev, "Error: unsupported product id %02x\n", id[1]); + return -ENODEV; + } + } else { + dev_err(dev, "Error: unrecognized JEDEC ID format: %*ph\n", + FM25_MAX_ID_LEN, id); + return -ENODEV; + } + + fm25_aux_read(at25, sernum, FM25_RDSN, FM25_SN_LEN); + /* Swap byte order */ + for (i = 0; i < FM25_SN_LEN; i++) + at25->sernum[i] = sernum[FM25_SN_LEN - 1 - i]; + } + + if (chip->byte_len > 64 * 1024) + chip->flags |= EE_ADDR3; + else + chip->flags |= EE_ADDR2; + + chip->page_size = PAGE_SIZE; + return 0; +} + +static const struct of_device_id at25_of_match[] = { + { .compatible = "atmel,at25" }, + { .compatible = "cypress,fm25" }, + { } +}; +MODULE_DEVICE_TABLE(of, at25_of_match); + +static const struct spi_device_id at25_spi_ids[] = { + { .name = "at25" }, + { .name = "fm25" }, + { } +}; +MODULE_DEVICE_TABLE(spi, at25_spi_ids); + +static int at25_probe(struct spi_mem *mem) +{ + struct spi_device *spi = mem->spi; + struct spi_eeprom *pdata; + struct at25_data *at25; + bool is_fram; + int err; + + at25 = devm_kzalloc(&spi->dev, sizeof(*at25), GFP_KERNEL); + if (!at25) + return -ENOMEM; + + at25->spimem = mem; + + /* + * Ping the chip ... the status register is pretty portable, + * unlike probing manufacturer IDs. + */ + err = at25_wait_ready(at25); + if (err < 0) + return dev_err_probe(&spi->dev, err, "Read Status Register command failed\n"); + if (err) { + dev_err(&spi->dev, "Not ready (%02x)\n", err); + return -ENXIO; + } + + mutex_init(&at25->lock); + spi_set_drvdata(spi, at25); + + is_fram = fwnode_device_is_compatible(dev_fwnode(&spi->dev), "cypress,fm25"); + + /* Chip description */ + pdata = dev_get_platdata(&spi->dev); + if (pdata) { + at25->chip = *pdata; + } else { + if (is_fram) + err = at25_fram_to_chip(&spi->dev, &at25->chip); + else + err = at25_fw_to_chip(&spi->dev, &at25->chip); + if (err) + return err; + } + + /* For now we only support 8/16/24 bit addressing */ + if (at25->chip.flags & EE_ADDR1) + at25->addrlen = 1; + else if (at25->chip.flags & EE_ADDR2) + at25->addrlen = 2; + else if (at25->chip.flags & EE_ADDR3) + at25->addrlen = 3; + else { + dev_dbg(&spi->dev, "unsupported address type\n"); + return -EINVAL; + } + + at25->nvmem_config.type = is_fram ? NVMEM_TYPE_FRAM : NVMEM_TYPE_EEPROM; + at25->nvmem_config.name = dev_name(&spi->dev); + at25->nvmem_config.dev = &spi->dev; + at25->nvmem_config.read_only = at25->chip.flags & EE_READONLY; + at25->nvmem_config.root_only = true; + at25->nvmem_config.owner = THIS_MODULE; + at25->nvmem_config.compat = true; + at25->nvmem_config.base_dev = &spi->dev; + at25->nvmem_config.reg_read = at25_ee_read; + at25->nvmem_config.reg_write = at25_ee_write; + at25->nvmem_config.priv = at25; + at25->nvmem_config.stride = 1; + at25->nvmem_config.word_size = 1; + at25->nvmem_config.size = at25->chip.byte_len; + + at25->nvmem = devm_nvmem_register(&spi->dev, &at25->nvmem_config); + if (IS_ERR(at25->nvmem)) + return PTR_ERR(at25->nvmem); + + dev_info(&spi->dev, "%d %s %s %s%s, pagesize %u\n", + (at25->chip.byte_len < 1024) ? + at25->chip.byte_len : (at25->chip.byte_len / 1024), + (at25->chip.byte_len < 1024) ? "Byte" : "KByte", + at25->chip.name, is_fram ? "fram" : "eeprom", + (at25->chip.flags & EE_READONLY) ? " (readonly)" : "", + at25->chip.page_size); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static struct spi_mem_driver at25_driver = { + .spidrv = { + .driver = { + .name = "at25", + .of_match_table = at25_of_match, + .dev_groups = at25_groups, + }, + .id_table = at25_spi_ids, + }, + .probe = at25_probe, +}; + +module_spi_mem_driver(at25_driver); + +MODULE_DESCRIPTION("Driver for most SPI EEPROMs"); +MODULE_AUTHOR("David Brownell"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/bcm-ocotp.c b/drivers/nvmem/bcm-ocotp.c new file mode 100644 index 000000000..2490f44ca --- /dev/null +++ b/drivers/nvmem/bcm-ocotp.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2016 Broadcom + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +/* + * # of tries for OTP Status. The time to execute a command varies. The slowest + * commands are writes which also vary based on the # of bits turned on. Writing + * 0xffffffff takes ~3800 us. + */ +#define OTPC_RETRIES 5000 + +/* Sequence to enable OTP program */ +#define OTPC_PROG_EN_SEQ { 0xf, 0x4, 0x8, 0xd } + +/* OTPC Commands */ +#define OTPC_CMD_READ 0x0 +#define OTPC_CMD_OTP_PROG_ENABLE 0x2 +#define OTPC_CMD_OTP_PROG_DISABLE 0x3 +#define OTPC_CMD_PROGRAM 0x8 + +/* OTPC Status Bits */ +#define OTPC_STAT_CMD_DONE BIT(1) +#define OTPC_STAT_PROG_OK BIT(2) + +/* OTPC register definition */ +#define OTPC_MODE_REG_OFFSET 0x0 +#define OTPC_MODE_REG_OTPC_MODE 0 +#define OTPC_COMMAND_OFFSET 0x4 +#define OTPC_COMMAND_COMMAND_WIDTH 6 +#define OTPC_CMD_START_OFFSET 0x8 +#define OTPC_CMD_START_START 0 +#define OTPC_CPU_STATUS_OFFSET 0xc +#define OTPC_CPUADDR_REG_OFFSET 0x28 +#define OTPC_CPUADDR_REG_OTPC_CPU_ADDRESS_WIDTH 16 +#define OTPC_CPU_WRITE_REG_OFFSET 0x2c + +#define OTPC_CMD_MASK (BIT(OTPC_COMMAND_COMMAND_WIDTH) - 1) +#define OTPC_ADDR_MASK (BIT(OTPC_CPUADDR_REG_OTPC_CPU_ADDRESS_WIDTH) - 1) + + +struct otpc_map { + /* in words. */ + u32 otpc_row_size; + /* 128 bit row / 4 words support. */ + u16 data_r_offset[4]; + /* 128 bit row / 4 words support. */ + u16 data_w_offset[4]; +}; + +static struct otpc_map otp_map = { + .otpc_row_size = 1, + .data_r_offset = {0x10}, + .data_w_offset = {0x2c}, +}; + +static struct otpc_map otp_map_v2 = { + .otpc_row_size = 2, + .data_r_offset = {0x10, 0x5c}, + .data_w_offset = {0x2c, 0x64}, +}; + +struct otpc_priv { + struct device *dev; + void __iomem *base; + const struct otpc_map *map; + struct nvmem_config *config; +}; + +static inline void set_command(void __iomem *base, u32 command) +{ + writel(command & OTPC_CMD_MASK, base + OTPC_COMMAND_OFFSET); +} + +static inline void set_cpu_address(void __iomem *base, u32 addr) +{ + writel(addr & OTPC_ADDR_MASK, base + OTPC_CPUADDR_REG_OFFSET); +} + +static inline void set_start_bit(void __iomem *base) +{ + writel(1 << OTPC_CMD_START_START, base + OTPC_CMD_START_OFFSET); +} + +static inline void reset_start_bit(void __iomem *base) +{ + writel(0, base + OTPC_CMD_START_OFFSET); +} + +static inline void write_cpu_data(void __iomem *base, u32 value) +{ + writel(value, base + OTPC_CPU_WRITE_REG_OFFSET); +} + +static int poll_cpu_status(void __iomem *base, u32 value) +{ + u32 status; + u32 retries; + + for (retries = 0; retries < OTPC_RETRIES; retries++) { + status = readl(base + OTPC_CPU_STATUS_OFFSET); + if (status & value) + break; + udelay(1); + } + if (retries == OTPC_RETRIES) + return -EAGAIN; + + return 0; +} + +static int enable_ocotp_program(void __iomem *base) +{ + static const u32 vals[] = OTPC_PROG_EN_SEQ; + int i; + int ret; + + /* Write the magic sequence to enable programming */ + set_command(base, OTPC_CMD_OTP_PROG_ENABLE); + for (i = 0; i < ARRAY_SIZE(vals); i++) { + write_cpu_data(base, vals[i]); + set_start_bit(base); + ret = poll_cpu_status(base, OTPC_STAT_CMD_DONE); + reset_start_bit(base); + if (ret) + return ret; + } + + return poll_cpu_status(base, OTPC_STAT_PROG_OK); +} + +static int disable_ocotp_program(void __iomem *base) +{ + int ret; + + set_command(base, OTPC_CMD_OTP_PROG_DISABLE); + set_start_bit(base); + ret = poll_cpu_status(base, OTPC_STAT_PROG_OK); + reset_start_bit(base); + + return ret; +} + +static int bcm_otpc_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct otpc_priv *priv = context; + u32 *buf = val; + u32 bytes_read; + u32 address = offset / priv->config->word_size; + int i, ret; + + for (bytes_read = 0; bytes_read < bytes;) { + set_command(priv->base, OTPC_CMD_READ); + set_cpu_address(priv->base, address++); + set_start_bit(priv->base); + ret = poll_cpu_status(priv->base, OTPC_STAT_CMD_DONE); + if (ret) { + dev_err(priv->dev, "otp read error: 0x%x", ret); + return -EIO; + } + + for (i = 0; i < priv->map->otpc_row_size; i++) { + *buf++ = readl(priv->base + + priv->map->data_r_offset[i]); + bytes_read += sizeof(*buf); + } + + reset_start_bit(priv->base); + } + + return 0; +} + +static int bcm_otpc_write(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct otpc_priv *priv = context; + u32 *buf = val; + u32 bytes_written; + u32 address = offset / priv->config->word_size; + int i, ret; + + if (offset % priv->config->word_size) + return -EINVAL; + + ret = enable_ocotp_program(priv->base); + if (ret) + return -EIO; + + for (bytes_written = 0; bytes_written < bytes;) { + set_command(priv->base, OTPC_CMD_PROGRAM); + set_cpu_address(priv->base, address++); + for (i = 0; i < priv->map->otpc_row_size; i++) { + writel(*buf, priv->base + priv->map->data_w_offset[i]); + buf++; + bytes_written += sizeof(*buf); + } + set_start_bit(priv->base); + ret = poll_cpu_status(priv->base, OTPC_STAT_CMD_DONE); + reset_start_bit(priv->base); + if (ret) { + dev_err(priv->dev, "otp write error: 0x%x", ret); + return -EIO; + } + } + + disable_ocotp_program(priv->base); + + return 0; +} + +static struct nvmem_config bcm_otpc_nvmem_config = { + .name = "bcm-ocotp", + .read_only = false, + .word_size = 4, + .stride = 4, + .reg_read = bcm_otpc_read, + .reg_write = bcm_otpc_write, +}; + +static const struct of_device_id bcm_otpc_dt_ids[] = { + { .compatible = "brcm,ocotp", .data = &otp_map }, + { .compatible = "brcm,ocotp-v2", .data = &otp_map_v2 }, + { }, +}; +MODULE_DEVICE_TABLE(of, bcm_otpc_dt_ids); + +static const struct acpi_device_id bcm_otpc_acpi_ids[] __maybe_unused = { + { .id = "BRCM0700", .driver_data = (kernel_ulong_t)&otp_map }, + { .id = "BRCM0701", .driver_data = (kernel_ulong_t)&otp_map_v2 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(acpi, bcm_otpc_acpi_ids); + +static int bcm_otpc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct otpc_priv *priv; + struct nvmem_device *nvmem; + int err; + u32 num_words; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->map = device_get_match_data(dev); + if (!priv->map) + return -ENODEV; + + /* Get OTP base address register. */ + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) { + dev_err(dev, "unable to map I/O memory\n"); + return PTR_ERR(priv->base); + } + + /* Enable CPU access to OTPC. */ + writel(readl(priv->base + OTPC_MODE_REG_OFFSET) | + BIT(OTPC_MODE_REG_OTPC_MODE), + priv->base + OTPC_MODE_REG_OFFSET); + reset_start_bit(priv->base); + + /* Read size of memory in words. */ + err = device_property_read_u32(dev, "brcm,ocotp-size", &num_words); + if (err) { + dev_err(dev, "size parameter not specified\n"); + return -EINVAL; + } else if (num_words == 0) { + dev_err(dev, "size must be > 0\n"); + return -EINVAL; + } + + bcm_otpc_nvmem_config.size = 4 * num_words; + bcm_otpc_nvmem_config.dev = dev; + bcm_otpc_nvmem_config.priv = priv; + + if (priv->map == &otp_map_v2) { + bcm_otpc_nvmem_config.word_size = 8; + bcm_otpc_nvmem_config.stride = 8; + } + + priv->config = &bcm_otpc_nvmem_config; + + nvmem = devm_nvmem_register(dev, &bcm_otpc_nvmem_config); + if (IS_ERR(nvmem)) { + dev_err(dev, "error registering nvmem config\n"); + return PTR_ERR(nvmem); + } + + return 0; +} + +static struct platform_driver bcm_otpc_driver = { + .probe = bcm_otpc_probe, + .driver = { + .name = "brcm-otpc", + .of_match_table = bcm_otpc_dt_ids, + .acpi_match_table = ACPI_PTR(bcm_otpc_acpi_ids), + }, +}; +module_platform_driver(bcm_otpc_driver); + +MODULE_DESCRIPTION("Broadcom OTPC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/brcm_nvram.c b/drivers/nvmem/brcm_nvram.c new file mode 100644 index 000000000..9f77aee37 --- /dev/null +++ b/drivers/nvmem/brcm_nvram.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl> + */ + +#include <linux/bcm47xx_nvram.h> +#include <linux/etherdevice.h> +#include <linux/hex.h> +#include <linux/if_ether.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define NVRAM_MAGIC "FLSH" + +/** + * struct brcm_nvram - driver state internal struct + * + * @dev: NVMEM device pointer + * @nvmem_size: Size of the whole space available for NVRAM + * @data: NVRAM data copy stored to avoid poking underlying flash controller + * @data_len: NVRAM data size + * @padding_byte: Padding value used to fill remaining space + * @cells: Array of discovered NVMEM cells + * @ncells: Number of elements in cells + */ +struct brcm_nvram { + struct device *dev; + size_t nvmem_size; + uint8_t *data; + size_t data_len; + uint8_t padding_byte; + struct nvmem_cell_info *cells; + int ncells; +}; + +struct brcm_nvram_header { + char magic[4]; + __le32 len; + __le32 crc_ver_init; /* 0:7 crc, 8:15 ver, 16:31 sdram_init */ + __le32 config_refresh; /* 0:15 sdram_config, 16:31 sdram_refresh */ + __le32 config_ncdl; /* ncdl values for memc */ +}; + +static int brcm_nvram_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct brcm_nvram *priv = context; + size_t to_copy; + + if (offset + bytes > priv->data_len) + to_copy = max_t(ssize_t, (ssize_t)priv->data_len - offset, 0); + else + to_copy = bytes; + + memcpy(val, priv->data + offset, to_copy); + + memset((uint8_t *)val + to_copy, priv->padding_byte, bytes - to_copy); + + return 0; +} + +static int brcm_nvram_copy_data(struct brcm_nvram *priv, struct platform_device *pdev) +{ + struct resource *res; + void __iomem *base; + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(base)) + return PTR_ERR(base); + + priv->nvmem_size = resource_size(res); + + priv->padding_byte = readb(base + priv->nvmem_size - 1); + for (priv->data_len = priv->nvmem_size; + priv->data_len; + priv->data_len--) { + if (readb(base + priv->data_len - 1) != priv->padding_byte) + break; + } + WARN(priv->data_len > SZ_128K, "Unexpected (big) NVRAM size: %zu B\n", priv->data_len); + + if (priv->data_len < sizeof(struct brcm_nvram_header)) { + dev_err(priv->dev, "NVRAM data too small (%zu)\n", priv->data_len); + return -EINVAL; + } + + priv->data = devm_kzalloc(priv->dev, priv->data_len, GFP_KERNEL); + if (!priv->data) + return -ENOMEM; + + memcpy_fromio(priv->data, base, priv->data_len); + + bcm47xx_nvram_init_from_iomem(base, priv->data_len); + + return 0; +} + +static int brcm_nvram_read_post_process_macaddr(void *context, const char *id, int index, + unsigned int offset, void *buf, size_t bytes) +{ + u8 mac[ETH_ALEN]; + + if (bytes != MAC_ADDR_STR_LEN) + return -EINVAL; + + if (!mac_pton(buf, mac)) + return -EINVAL; + + if (index) + eth_addr_add(mac, index); + + ether_addr_copy(buf, mac); + + return 0; +} + +static int brcm_nvram_add_cells(struct brcm_nvram *priv, uint8_t *data, + size_t len) +{ + struct device *dev = priv->dev; + char *var, *value; + uint8_t tmp; + int idx; + int err = 0; + + tmp = priv->data[len - 1]; + priv->data[len - 1] = '\0'; + + priv->ncells = 0; + for (var = data + sizeof(struct brcm_nvram_header); + var < (char *)data + len && *var; + var += strlen(var) + 1) { + priv->ncells++; + } + + priv->cells = devm_kcalloc(dev, priv->ncells, sizeof(*priv->cells), GFP_KERNEL); + if (!priv->cells) { + err = -ENOMEM; + goto out; + } + + for (var = data + sizeof(struct brcm_nvram_header), idx = 0; + var < (char *)data + len && *var; + var = value + strlen(value) + 1, idx++) { + char *eq, *name; + + eq = strchr(var, '='); + if (!eq) + break; + *eq = '\0'; + name = devm_kstrdup(dev, var, GFP_KERNEL); + *eq = '='; + if (!name) { + err = -ENOMEM; + goto out; + } + value = eq + 1; + + priv->cells[idx].name = name; + priv->cells[idx].offset = value - (char *)data; + priv->cells[idx].bytes = strlen(value); + priv->cells[idx].np = of_get_child_by_name(dev->of_node, priv->cells[idx].name); + if (!strcmp(name, "et0macaddr") || + !strcmp(name, "et1macaddr") || + !strcmp(name, "et2macaddr")) { + priv->cells[idx].raw_len = strlen(value); + priv->cells[idx].bytes = ETH_ALEN; + priv->cells[idx].read_post_process = brcm_nvram_read_post_process_macaddr; + } + } + +out: + priv->data[len - 1] = tmp; + return err; +} + +static int brcm_nvram_parse(struct brcm_nvram *priv) +{ + struct brcm_nvram_header *header = (struct brcm_nvram_header *)priv->data; + struct device *dev = priv->dev; + size_t len; + int err; + + if (memcmp(header->magic, NVRAM_MAGIC, 4)) { + dev_err(dev, "Invalid NVRAM magic\n"); + return -EINVAL; + } + + len = le32_to_cpu(header->len); + if (len < sizeof(*header)) { + dev_err(dev, "NVRAM length (%zd) too small\n", len); + return -EINVAL; + } + if (len > priv->data_len) { + dev_err(dev, "NVRAM length (%zd) exceeds data size (%zd)\n", len, + priv->data_len); + return -EINVAL; + } + + err = brcm_nvram_add_cells(priv, priv->data, len); + if (err) + dev_err(dev, "Failed to add cells: %d\n", err); + + return 0; +} + +static int brcm_nvram_probe(struct platform_device *pdev) +{ + struct nvmem_config config = { + .name = "brcm-nvram", + .reg_read = brcm_nvram_read, + }; + struct device *dev = &pdev->dev; + struct brcm_nvram *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->dev = dev; + + err = brcm_nvram_copy_data(priv, pdev); + if (err) + return err; + + err = brcm_nvram_parse(priv); + if (err) + return err; + + config.dev = dev; + config.cells = priv->cells; + config.ncells = priv->ncells; + config.priv = priv; + config.size = priv->nvmem_size; + + return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &config)); +} + +static const struct of_device_id brcm_nvram_of_match_table[] = { + { .compatible = "brcm,nvram", }, + {}, +}; + +static struct platform_driver brcm_nvram_driver = { + .probe = brcm_nvram_probe, + .driver = { + .name = "brcm_nvram", + .of_match_table = brcm_nvram_of_match_table, + }, +}; + +static int __init brcm_nvram_init(void) +{ + return platform_driver_register(&brcm_nvram_driver); +} + +subsys_initcall_sync(brcm_nvram_init); + +MODULE_AUTHOR("Rafał Miłecki"); +MODULE_DESCRIPTION("Broadcom I/O-mapped NVRAM support driver"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, brcm_nvram_of_match_table); diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c new file mode 100644 index 000000000..0556d1401 --- /dev/null +++ b/drivers/nvmem/core.c @@ -0,0 +1,2179 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * nvmem framework core. + * + * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + * Copyright (C) 2013 Maxime Ripard <maxime.ripard@free-electrons.com> + */ + +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/export.h> +#include <linux/fs.h> +#include <linux/idr.h> +#include <linux/init.h> +#include <linux/kref.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> +#include <linux/gpio/consumer.h> +#include <linux/of.h> +#include <linux/slab.h> + +#include "internals.h" + +#define to_nvmem_device(d) container_of(d, struct nvmem_device, dev) + +#define FLAG_COMPAT BIT(0) +struct nvmem_cell_entry { + const char *name; + int offset; + size_t raw_len; + int bytes; + int bit_offset; + int nbits; + nvmem_cell_post_process_t read_post_process; + void *priv; + struct device_node *np; + struct nvmem_device *nvmem; + struct list_head node; +}; + +struct nvmem_cell { + struct nvmem_cell_entry *entry; + const char *id; + int index; +}; + +static DEFINE_MUTEX(nvmem_mutex); +static DEFINE_IDA(nvmem_ida); + +static DEFINE_MUTEX(nvmem_lookup_mutex); +static LIST_HEAD(nvmem_lookup_list); + +static BLOCKING_NOTIFIER_HEAD(nvmem_notifier); + +static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, + void *val, size_t bytes) +{ + struct nvmem_operations *ops = nvmem->ops; + + if (!ops->reg_read) + return -EOPNOTSUPP; + + return ops->reg_read(nvmem->priv, offset, val, bytes); +} + +static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset, + void *val, size_t bytes) +{ + struct nvmem_operations *ops = nvmem->ops; + int ret, wr_ok; + + if (!ops->reg_write) + return -EOPNOTSUPP; + + ret = gpiod_set_value_cansleep(nvmem->wp_gpio, 0); + if (ret) + return ret; + + wr_ok = ops->reg_write(nvmem->priv, offset, val, bytes); + + ret = gpiod_set_value_cansleep(nvmem->wp_gpio, 1); + if (ret) + return ret; + + return wr_ok; +} + +static int nvmem_access_with_keepouts(struct nvmem_device *nvmem, + unsigned int offset, void *val, + size_t bytes, int write) +{ + + unsigned int end = offset + bytes; + unsigned int kend, ksize; + const struct nvmem_keepout *keepout = nvmem->keepout; + const struct nvmem_keepout *keepoutend = keepout + nvmem->nkeepout; + int rc; + + /* + * Skip all keepouts before the range being accessed. + * Keepouts are sorted. + */ + while ((keepout < keepoutend) && (keepout->end <= offset)) + keepout++; + + while ((offset < end) && (keepout < keepoutend)) { + /* Access the valid portion before the keepout. */ + if (offset < keepout->start) { + kend = min(end, keepout->start); + ksize = kend - offset; + if (write) + rc = __nvmem_reg_write(nvmem, offset, val, ksize); + else + rc = __nvmem_reg_read(nvmem, offset, val, ksize); + + if (rc) + return rc; + + offset += ksize; + val += ksize; + } + + /* + * Now we're aligned to the start of this keepout zone. Go + * through it. + */ + kend = min(end, keepout->end); + ksize = kend - offset; + if (!write) + memset(val, keepout->value, ksize); + + val += ksize; + offset += ksize; + keepout++; + } + + /* + * If we ran out of keepouts but there's still stuff to do, send it + * down directly + */ + if (offset < end) { + ksize = end - offset; + if (write) + return __nvmem_reg_write(nvmem, offset, val, ksize); + else + return __nvmem_reg_read(nvmem, offset, val, ksize); + } + + return 0; +} + +static int nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, + void *val, size_t bytes) +{ + if (!nvmem->nkeepout) + return __nvmem_reg_read(nvmem, offset, val, bytes); + + return nvmem_access_with_keepouts(nvmem, offset, val, bytes, false); +} + +static int nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset, + void *val, size_t bytes) +{ + if (!nvmem->nkeepout) + return __nvmem_reg_write(nvmem, offset, val, bytes); + + return nvmem_access_with_keepouts(nvmem, offset, val, bytes, true); +} + +#ifdef CONFIG_NVMEM_SYSFS +static const char * const nvmem_type_str[] = { + [NVMEM_TYPE_UNKNOWN] = "Unknown", + [NVMEM_TYPE_EEPROM] = "EEPROM", + [NVMEM_TYPE_OTP] = "OTP", + [NVMEM_TYPE_BATTERY_BACKED] = "Battery backed", + [NVMEM_TYPE_FRAM] = "FRAM", +}; + +#ifdef CONFIG_DEBUG_LOCK_ALLOC +static struct lock_class_key eeprom_lock_key; +#endif + +static ssize_t type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct nvmem_device *nvmem = to_nvmem_device(dev); + + return sysfs_emit(buf, "%s\n", nvmem_type_str[nvmem->type]); +} + +static DEVICE_ATTR_RO(type); + +static ssize_t force_ro_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nvmem_device *nvmem = to_nvmem_device(dev); + + return sysfs_emit(buf, "%d\n", nvmem->read_only); +} + +static ssize_t force_ro_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nvmem_device *nvmem = to_nvmem_device(dev); + int ret = kstrtobool(buf, &nvmem->read_only); + + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(force_ro); + +static struct attribute *nvmem_attrs[] = { + &dev_attr_force_ro.attr, + &dev_attr_type.attr, + NULL, +}; + +static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t pos, size_t count) +{ + struct device *dev; + struct nvmem_device *nvmem; + int rc; + + if (attr->private) + dev = attr->private; + else + dev = kobj_to_dev(kobj); + nvmem = to_nvmem_device(dev); + + if (!IS_ALIGNED(pos, nvmem->stride)) + return -EINVAL; + + if (count < nvmem->word_size) + return -EINVAL; + + count = round_down(count, nvmem->word_size); + + rc = nvmem_reg_read(nvmem, pos, buf, count); + if (rc) { + if (rc == -EOPNOTSUPP) + return -EPERM; + return rc; + } + + return count; +} + +static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t pos, size_t count) +{ + struct device *dev; + struct nvmem_device *nvmem; + int rc; + + if (attr->private) + dev = attr->private; + else + dev = kobj_to_dev(kobj); + nvmem = to_nvmem_device(dev); + + if (!IS_ALIGNED(pos, nvmem->stride)) + return -EINVAL; + + if (count < nvmem->word_size) + return -EINVAL; + + count = round_down(count, nvmem->word_size); + + if (nvmem->read_only) + return -EPERM; + + rc = nvmem_reg_write(nvmem, pos, buf, count); + if (rc) { + if (rc == -EOPNOTSUPP) + return -EPERM; + return rc; + } + + return count; +} + +static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem) +{ + struct nvmem_operations *ops = nvmem->ops; + + umode_t mode = 0400; + + if (!nvmem->root_only) + mode |= 0044; + + if (!nvmem->read_only) + mode |= 0200; + + if (!ops->reg_write) + mode &= ~0200; + + if (!ops->reg_read) + mode &= ~0444; + + return mode; +} + +static umode_t nvmem_bin_attr_is_visible(struct kobject *kobj, + const struct bin_attribute *attr, + int i) +{ + struct device *dev = kobj_to_dev(kobj); + struct nvmem_device *nvmem = to_nvmem_device(dev); + + return nvmem_bin_attr_get_umode(nvmem); +} + +static size_t nvmem_bin_attr_size(struct kobject *kobj, + const struct bin_attribute *attr, + int i) +{ + struct device *dev = kobj_to_dev(kobj); + struct nvmem_device *nvmem = to_nvmem_device(dev); + + return nvmem->size; +} + +static umode_t nvmem_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int i) +{ + struct device *dev = kobj_to_dev(kobj); + struct nvmem_device *nvmem = to_nvmem_device(dev); + struct nvmem_operations *ops = nvmem->ops; + + /* + * If the device has no .reg_write operation, do not allow + * configuration as read-write. + * If the device is set as read-only by configuration, it + * can be forced into read-write mode using the 'force_ro' + * attribute. + */ + if (attr == &dev_attr_force_ro.attr && !ops->reg_write) + return 0; /* Attribute not visible */ + + return attr->mode; +} + +static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry, + const char *id, int index); + +static ssize_t nvmem_cell_attr_read(struct file *filp, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t pos, size_t count) +{ + struct nvmem_cell_entry *entry; + struct nvmem_cell *cell = NULL; + size_t cell_sz, read_len; + void *content; + + entry = attr->private; + cell = nvmem_create_cell(entry, entry->name, 0); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + if (!cell) + return -EINVAL; + + content = nvmem_cell_read(cell, &cell_sz); + if (IS_ERR(content)) { + read_len = PTR_ERR(content); + goto destroy_cell; + } + + read_len = min_t(unsigned int, cell_sz - pos, count); + memcpy(buf, content + pos, read_len); + kfree(content); + +destroy_cell: + kfree_const(cell->id); + kfree(cell); + + return read_len; +} + +/* default read/write permissions */ +static const struct bin_attribute bin_attr_rw_nvmem = { + .attr = { + .name = "nvmem", + .mode = 0644, + }, + .read = bin_attr_nvmem_read, + .write = bin_attr_nvmem_write, +}; + +static const struct bin_attribute *const nvmem_bin_attributes[] = { + &bin_attr_rw_nvmem, + NULL, +}; + +static const struct attribute_group nvmem_bin_group = { + .bin_attrs = nvmem_bin_attributes, + .attrs = nvmem_attrs, + .is_bin_visible = nvmem_bin_attr_is_visible, + .bin_size = nvmem_bin_attr_size, + .is_visible = nvmem_attr_is_visible, +}; + +static const struct attribute_group *nvmem_dev_groups[] = { + &nvmem_bin_group, + NULL, +}; + +static const struct bin_attribute bin_attr_nvmem_eeprom_compat = { + .attr = { + .name = "eeprom", + }, + .read = bin_attr_nvmem_read, + .write = bin_attr_nvmem_write, +}; + +/* + * nvmem_setup_compat() - Create an additional binary entry in + * drivers sys directory, to be backwards compatible with the older + * drivers/misc/eeprom drivers. + */ +static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, + const struct nvmem_config *config) +{ + int rval; + + if (!config->compat) + return 0; + + if (!config->base_dev) + return -EINVAL; + + nvmem->eeprom = bin_attr_nvmem_eeprom_compat; + if (config->type == NVMEM_TYPE_FRAM) + nvmem->eeprom.attr.name = "fram"; + nvmem->eeprom.attr.mode = nvmem_bin_attr_get_umode(nvmem); + nvmem->eeprom.size = nvmem->size; +#ifdef CONFIG_DEBUG_LOCK_ALLOC + nvmem->eeprom.attr.key = &eeprom_lock_key; +#endif + nvmem->eeprom.private = &nvmem->dev; + nvmem->base_dev = config->base_dev; + + rval = device_create_bin_file(nvmem->base_dev, &nvmem->eeprom); + if (rval) { + dev_err(&nvmem->dev, + "Failed to create eeprom binary file %d\n", rval); + return rval; + } + + nvmem->flags |= FLAG_COMPAT; + + return 0; +} + +static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem) +{ + if (nvmem->flags & FLAG_COMPAT) + device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom); +} + +static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem) +{ + struct attribute_group group = { + .name = "cells", + }; + struct nvmem_cell_entry *entry; + const struct bin_attribute **pattrs; + struct bin_attribute *attrs; + unsigned int ncells = 0, i = 0; + int ret; + + guard(mutex)(&nvmem_mutex); + + if (list_empty(&nvmem->cells) || nvmem->sysfs_cells_populated) + return 0; + + /* Allocate an array of attributes with a sentinel */ + ncells = list_count_nodes(&nvmem->cells); + pattrs = devm_kcalloc(&nvmem->dev, ncells + 1, + sizeof(struct bin_attribute *), GFP_KERNEL); + if (!pattrs) + return -ENOMEM; + + attrs = devm_kcalloc(&nvmem->dev, ncells, sizeof(struct bin_attribute), GFP_KERNEL); + if (!attrs) + return -ENOMEM; + + /* Initialize each attribute to take the name and size of the cell */ + list_for_each_entry(entry, &nvmem->cells, node) { + sysfs_bin_attr_init(&attrs[i]); + attrs[i].attr.name = devm_kasprintf(&nvmem->dev, GFP_KERNEL, + "%s@%x,%x", entry->name, + entry->offset, + entry->bit_offset); + attrs[i].attr.mode = 0444 & nvmem_bin_attr_get_umode(nvmem); + attrs[i].size = entry->bytes; + attrs[i].read = &nvmem_cell_attr_read; + attrs[i].private = entry; + if (!attrs[i].attr.name) + return -ENOMEM; + + pattrs[i] = &attrs[i]; + i++; + } + + group.bin_attrs = pattrs; + + ret = device_add_group(&nvmem->dev, &group); + if (ret) + return ret; + + nvmem->sysfs_cells_populated = true; + + return ret; +} + +#else /* CONFIG_NVMEM_SYSFS */ + +static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, + const struct nvmem_config *config) +{ + return -ENOSYS; +} +static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem) +{ +} + +#endif /* CONFIG_NVMEM_SYSFS */ + +static void nvmem_release(struct device *dev) +{ + struct nvmem_device *nvmem = to_nvmem_device(dev); + + ida_free(&nvmem_ida, nvmem->id); + gpiod_put(nvmem->wp_gpio); + kfree(nvmem->ops); + kfree(nvmem); +} + +static const struct device_type nvmem_provider_type = { + .release = nvmem_release, +}; + +static const struct bus_type nvmem_bus_type = { + .name = "nvmem", +}; + +static void nvmem_cell_entry_drop(struct nvmem_cell_entry *cell) +{ + blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_REMOVE, cell); + scoped_guard(mutex, &nvmem_mutex) + list_del(&cell->node); + of_node_put(cell->np); + kfree_const(cell->name); + kfree(cell); +} + +static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem) +{ + struct nvmem_cell_entry *cell, *p; + + list_for_each_entry_safe(cell, p, &nvmem->cells, node) + nvmem_cell_entry_drop(cell); +} + +static void nvmem_cell_entry_add(struct nvmem_cell_entry *cell) +{ + scoped_guard(mutex, &nvmem_mutex) + list_add_tail(&cell->node, &cell->nvmem->cells); + blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_ADD, cell); +} + +static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem, + const struct nvmem_cell_info *info, + struct nvmem_cell_entry *cell) +{ + cell->nvmem = nvmem; + cell->offset = info->offset; + cell->raw_len = info->raw_len ?: info->bytes; + cell->bytes = info->bytes; + cell->name = info->name; + cell->read_post_process = info->read_post_process; + cell->priv = info->priv; + + cell->bit_offset = info->bit_offset; + cell->nbits = info->nbits; + cell->np = info->np; + + if (cell->nbits) { + cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset, + BITS_PER_BYTE); + cell->raw_len = ALIGN(cell->bytes, nvmem->word_size); + } + + if (!IS_ALIGNED(cell->offset, nvmem->stride)) { + dev_err(&nvmem->dev, + "cell %s unaligned to nvmem stride %d\n", + cell->name ?: "<unknown>", nvmem->stride); + return -EINVAL; + } + + if (!IS_ALIGNED(cell->raw_len, nvmem->word_size)) { + dev_err(&nvmem->dev, + "cell %s raw len %zd unaligned to nvmem word size %d\n", + cell->name ?: "<unknown>", cell->raw_len, + nvmem->word_size); + + if (info->raw_len) + return -EINVAL; + + cell->raw_len = ALIGN(cell->raw_len, nvmem->word_size); + } + + return 0; +} + +static int nvmem_cell_info_to_nvmem_cell_entry(struct nvmem_device *nvmem, + const struct nvmem_cell_info *info, + struct nvmem_cell_entry *cell) +{ + int err; + + err = nvmem_cell_info_to_nvmem_cell_entry_nodup(nvmem, info, cell); + if (err) + return err; + + cell->name = kstrdup_const(info->name, GFP_KERNEL); + if (!cell->name) + return -ENOMEM; + + return 0; +} + +/** + * nvmem_add_one_cell() - Add one cell information to an nvmem device + * + * @nvmem: nvmem device to add cells to. + * @info: nvmem cell info to add to the device + * + * Return: 0 or negative error code on failure. + */ +int nvmem_add_one_cell(struct nvmem_device *nvmem, + const struct nvmem_cell_info *info) +{ + struct nvmem_cell_entry *cell; + int rval; + + cell = kzalloc_obj(*cell); + if (!cell) + return -ENOMEM; + + rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, info, cell); + if (rval) { + kfree(cell); + return rval; + } + + nvmem_cell_entry_add(cell); + + return 0; +} +EXPORT_SYMBOL_GPL(nvmem_add_one_cell); + +/** + * nvmem_add_cells() - Add cell information to an nvmem device + * + * @nvmem: nvmem device to add cells to. + * @info: nvmem cell info to add to the device + * @ncells: number of cells in info + * + * Return: 0 or negative error code on failure. + */ +static int nvmem_add_cells(struct nvmem_device *nvmem, + const struct nvmem_cell_info *info, + int ncells) +{ + int i, rval; + + for (i = 0; i < ncells; i++) { + rval = nvmem_add_one_cell(nvmem, &info[i]); + if (rval) + return rval; + } + + return 0; +} + +/** + * nvmem_register_notifier() - Register a notifier block for nvmem events. + * + * @nb: notifier block to be called on nvmem events. + * + * Return: 0 on success, negative error number on failure. + */ +int nvmem_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&nvmem_notifier, nb); +} +EXPORT_SYMBOL_GPL(nvmem_register_notifier); + +/** + * nvmem_unregister_notifier() - Unregister a notifier block for nvmem events. + * + * @nb: notifier block to be unregistered. + * + * Return: 0 on success, negative error number on failure. + */ +int nvmem_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&nvmem_notifier, nb); +} +EXPORT_SYMBOL_GPL(nvmem_unregister_notifier); + +static struct nvmem_cell_entry * +nvmem_find_cell_entry_by_name(struct nvmem_device *nvmem, const char *cell_id) +{ + struct nvmem_cell_entry *iter, *cell = NULL; + + guard(mutex)(&nvmem_mutex); + + list_for_each_entry(iter, &nvmem->cells, node) { + if (strcmp(cell_id, iter->name) == 0) { + cell = iter; + break; + } + } + + return cell; +} + +static int nvmem_validate_keepouts(struct nvmem_device *nvmem) +{ + unsigned int cur = 0; + const struct nvmem_keepout *keepout = nvmem->keepout; + const struct nvmem_keepout *keepoutend = keepout + nvmem->nkeepout; + + while (keepout < keepoutend) { + /* Ensure keepouts are sorted and don't overlap. */ + if (keepout->start < cur) { + dev_err(&nvmem->dev, + "Keepout regions aren't sorted or overlap.\n"); + + return -ERANGE; + } + + if (keepout->end < keepout->start) { + dev_err(&nvmem->dev, + "Invalid keepout region.\n"); + + return -EINVAL; + } + + /* + * Validate keepouts (and holes between) don't violate + * word_size constraints. + */ + if ((keepout->end - keepout->start < nvmem->word_size) || + ((keepout->start != cur) && + (keepout->start - cur < nvmem->word_size))) { + + dev_err(&nvmem->dev, + "Keepout regions violate word_size constraints.\n"); + + return -ERANGE; + } + + /* Validate keepouts don't violate stride (alignment). */ + if (!IS_ALIGNED(keepout->start, nvmem->stride) || + !IS_ALIGNED(keepout->end, nvmem->stride)) { + + dev_err(&nvmem->dev, + "Keepout regions violate stride.\n"); + + return -EINVAL; + } + + cur = keepout->end; + keepout++; + } + + return 0; +} + +int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np) +{ + struct device *dev = &nvmem->dev; + const __be32 *addr; + int len, ret; + + for_each_child_of_node_scoped(np, child) { + struct nvmem_cell_info info = {0}; + + addr = of_get_property(child, "reg", &len); + if (!addr) + continue; + if (len < 2 * sizeof(u32)) { + dev_err(dev, "nvmem: invalid reg on %pOF\n", child); + return -EINVAL; + } + + info.offset = be32_to_cpup(addr++); + info.bytes = be32_to_cpup(addr); + info.name = kasprintf(GFP_KERNEL, "%pOFn", child); + + addr = of_get_property(child, "bits", &len); + if (addr && len == (2 * sizeof(u32))) { + info.bit_offset = be32_to_cpup(addr++); + info.nbits = be32_to_cpup(addr); + if (info.bit_offset >= BITS_PER_BYTE * info.bytes || + info.nbits < 1 || + info.bit_offset + info.nbits > BITS_PER_BYTE * info.bytes) { + dev_err(dev, "nvmem: invalid bits on %pOF\n", child); + return -EINVAL; + } + } + + info.np = of_node_get(child); + + if (nvmem->fixup_dt_cell_info) + nvmem->fixup_dt_cell_info(nvmem, &info); + + ret = nvmem_add_one_cell(nvmem, &info); + kfree(info.name); + if (ret) { + of_node_put(info.np); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(nvmem_add_cells_from_dt); + +static int nvmem_add_cells_from_legacy_of(struct nvmem_device *nvmem) +{ + return nvmem_add_cells_from_dt(nvmem, nvmem->dev.of_node); +} + +int nvmem_layout_register(struct nvmem_layout *layout) +{ + int ret; + + if (!layout->add_cells) + return -EINVAL; + + /* Populate the cells */ + ret = layout->add_cells(layout); + if (ret) + return ret; + +#ifdef CONFIG_NVMEM_SYSFS + ret = nvmem_populate_sysfs_cells(layout->nvmem); + if (ret) { + nvmem_device_remove_all_cells(layout->nvmem); + return ret; + } +#endif + + return 0; +} +EXPORT_SYMBOL_GPL(nvmem_layout_register); + +void nvmem_layout_unregister(struct nvmem_layout *layout) +{ + /* Keep the API even with an empty stub in case we need it later */ +} +EXPORT_SYMBOL_GPL(nvmem_layout_unregister); + +/** + * nvmem_register() - Register a nvmem device for given nvmem_config. + * Also creates a binary entry in /sys/bus/nvmem/devices/dev-name/nvmem + * + * @config: nvmem device configuration with which nvmem device is created. + * + * Return: Will be an ERR_PTR() on error or a valid pointer to nvmem_device + * on success. + */ + +struct nvmem_device *nvmem_register(const struct nvmem_config *config) +{ + struct nvmem_operations *ops; + struct nvmem_device *nvmem; + int rval; + + if (!config->dev) + return ERR_PTR(-EINVAL); + + if (!config->reg_read && !config->reg_write) + return ERR_PTR(-EINVAL); + + nvmem = kzalloc_obj(*nvmem); + if (!nvmem) + return ERR_PTR(-ENOMEM); + + ops = kzalloc_obj(*ops); + if (!ops) { + kfree(nvmem); + return ERR_PTR(-ENOMEM); + } + + rval = ida_alloc(&nvmem_ida, GFP_KERNEL); + if (rval < 0) { + kfree(ops); + kfree(nvmem); + return ERR_PTR(rval); + } + + nvmem->id = rval; + + nvmem->dev.type = &nvmem_provider_type; + nvmem->dev.bus = &nvmem_bus_type; + nvmem->dev.parent = config->dev; + nvmem->ops = ops; + + device_initialize(&nvmem->dev); + + if (!config->ignore_wp) + nvmem->wp_gpio = gpiod_get_optional(config->dev, "wp", + GPIOD_OUT_HIGH); + if (IS_ERR(nvmem->wp_gpio)) { + rval = PTR_ERR(nvmem->wp_gpio); + nvmem->wp_gpio = NULL; + goto err_put_device; + } + + kref_init(&nvmem->refcnt); + INIT_LIST_HEAD(&nvmem->cells); + nvmem->fixup_dt_cell_info = config->fixup_dt_cell_info; + + ops->reg_read = config->reg_read; + ops->reg_write = config->reg_write; + + nvmem->owner = config->owner; + if (!nvmem->owner && config->dev->driver) + nvmem->owner = config->dev->driver->owner; + nvmem->stride = config->stride ?: 1; + nvmem->word_size = config->word_size ?: 1; + nvmem->size = config->size; + nvmem->root_only = config->root_only; + nvmem->priv = config->priv; + nvmem->type = config->type; + nvmem->keepout = config->keepout; + nvmem->nkeepout = config->nkeepout; + if (config->of_node) + nvmem->dev.of_node = config->of_node; + else + nvmem->dev.of_node = config->dev->of_node; + + switch (config->id) { + case NVMEM_DEVID_NONE: + rval = dev_set_name(&nvmem->dev, "%s", config->name); + break; + case NVMEM_DEVID_AUTO: + rval = dev_set_name(&nvmem->dev, "%s%d", config->name, nvmem->id); + break; + default: + rval = dev_set_name(&nvmem->dev, "%s%d", + config->name ? : "nvmem", + config->name ? config->id : nvmem->id); + break; + } + + if (rval) + goto err_put_device; + + nvmem->read_only = device_property_present(config->dev, "read-only") || + config->read_only || !ops->reg_write; + +#ifdef CONFIG_NVMEM_SYSFS + nvmem->dev.groups = nvmem_dev_groups; +#endif + + if (nvmem->nkeepout) { + rval = nvmem_validate_keepouts(nvmem); + if (rval) + goto err_put_device; + } + + if (config->compat) { + rval = nvmem_sysfs_setup_compat(nvmem, config); + if (rval) + goto err_put_device; + } + + if (config->cells) { + rval = nvmem_add_cells(nvmem, config->cells, config->ncells); + if (rval) + goto err_remove_cells; + } + + if (config->add_legacy_fixed_of_cells) { + rval = nvmem_add_cells_from_legacy_of(nvmem); + if (rval) + goto err_remove_cells; + } + + dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name); + + rval = device_add(&nvmem->dev); + if (rval) + goto err_remove_cells; + + rval = nvmem_populate_layout(nvmem); + if (rval) + goto err_remove_dev; + + /* If the device has WP GPIO, default to read-only */ + if (nvmem->wp_gpio) + nvmem->read_only = true; + +#ifdef CONFIG_NVMEM_SYSFS + rval = nvmem_populate_sysfs_cells(nvmem); + if (rval) + goto err_destroy_layout; +#endif + + blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem); + + return nvmem; + +#ifdef CONFIG_NVMEM_SYSFS +err_destroy_layout: + nvmem_destroy_layout(nvmem); +#endif +err_remove_dev: + device_del(&nvmem->dev); +err_remove_cells: + nvmem_device_remove_all_cells(nvmem); + nvmem_sysfs_remove_compat(nvmem); +err_put_device: + put_device(&nvmem->dev); + + return ERR_PTR(rval); +} +EXPORT_SYMBOL_GPL(nvmem_register); + +static void nvmem_device_release(struct kref *kref) +{ + struct nvmem_device *nvmem; + + nvmem = container_of(kref, struct nvmem_device, refcnt); + + blocking_notifier_call_chain(&nvmem_notifier, NVMEM_REMOVE, nvmem); + + nvmem_sysfs_remove_compat(nvmem); + + nvmem_device_remove_all_cells(nvmem); + nvmem_destroy_layout(nvmem); + device_unregister(&nvmem->dev); +} + +/** + * nvmem_unregister() - Unregister previously registered nvmem device + * + * @nvmem: Pointer to previously registered nvmem device. + */ +void nvmem_unregister(struct nvmem_device *nvmem) +{ + if (nvmem) + kref_put(&nvmem->refcnt, nvmem_device_release); +} +EXPORT_SYMBOL_GPL(nvmem_unregister); + +static void devm_nvmem_unregister(void *nvmem) +{ + nvmem_unregister(nvmem); +} + +/** + * devm_nvmem_register() - Register a managed nvmem device for given + * nvmem_config. + * Also creates a binary entry in /sys/bus/nvmem/devices/dev-name/nvmem + * + * @dev: Device that uses the nvmem device. + * @config: nvmem device configuration with which nvmem device is created. + * + * Return: Will be an ERR_PTR() on error or a valid pointer to nvmem_device + * on success. + */ +struct nvmem_device *devm_nvmem_register(struct device *dev, + const struct nvmem_config *config) +{ + struct nvmem_device *nvmem; + int ret; + + nvmem = nvmem_register(config); + if (IS_ERR(nvmem)) + return nvmem; + + ret = devm_add_action_or_reset(dev, devm_nvmem_unregister, nvmem); + if (ret) + return ERR_PTR(ret); + + return nvmem; +} +EXPORT_SYMBOL_GPL(devm_nvmem_register); + +static struct nvmem_device *nvmem_device_match(void *data, + int (*match)(struct device *dev, const void *data)) +{ + struct nvmem_device *nvmem = NULL; + struct device *dev; + + scoped_guard(mutex, &nvmem_mutex) { + dev = bus_find_device(&nvmem_bus_type, NULL, data, match); + if (dev) + nvmem = to_nvmem_device(dev); + } + if (!nvmem) + return ERR_PTR(-EPROBE_DEFER); + + if (!try_module_get(nvmem->owner)) { + dev_err(&nvmem->dev, + "could not increase module refcount for cell %s\n", + nvmem_dev_name(nvmem)); + + put_device(&nvmem->dev); + return ERR_PTR(-EINVAL); + } + + kref_get(&nvmem->refcnt); + + return nvmem; +} + +#if IS_ENABLED(CONFIG_OF) +/** + * of_nvmem_device_get() - Get nvmem device from a given id + * + * @np: Device tree node that uses the nvmem device. + * @id: nvmem name from nvmem-names property. + * + * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device + * on success. + */ +struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id) +{ + + struct device_node *nvmem_np; + struct nvmem_device *nvmem; + int index = 0; + + if (id) + index = of_property_match_string(np, "nvmem-names", id); + + nvmem_np = of_parse_phandle(np, "nvmem", index); + if (!nvmem_np) + return ERR_PTR(-ENOENT); + + nvmem = nvmem_device_match(nvmem_np, device_match_of_node); + of_node_put(nvmem_np); + return nvmem; +} +EXPORT_SYMBOL_GPL(of_nvmem_device_get); +#endif + +/** + * nvmem_device_get() - Get nvmem device from a given id + * + * @dev: Device that uses the nvmem device. + * @dev_name: name of the requested nvmem device. + * + * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device + * on success. + */ +struct nvmem_device *nvmem_device_get(struct device *dev, const char *dev_name) +{ + if (dev->of_node) { /* try dt first */ + struct nvmem_device *nvmem; + + nvmem = of_nvmem_device_get(dev->of_node, dev_name); + + if (!IS_ERR(nvmem) || PTR_ERR(nvmem) == -EPROBE_DEFER) + return nvmem; + + } + + return nvmem_device_match((void *)dev_name, device_match_name); +} +EXPORT_SYMBOL_GPL(nvmem_device_get); + +/** + * nvmem_device_find() - Find nvmem device with matching function + * + * @data: Data to pass to match function + * @match: Callback function to check device + * + * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device + * on success. + */ +struct nvmem_device *nvmem_device_find(void *data, + int (*match)(struct device *dev, const void *data)) +{ + return nvmem_device_match(data, match); +} +EXPORT_SYMBOL_GPL(nvmem_device_find); + +static int devm_nvmem_device_match(struct device *dev, void *res, void *data) +{ + struct nvmem_device **nvmem = res; + + if (WARN_ON(!nvmem || !*nvmem)) + return 0; + + return *nvmem == data; +} + +static void devm_nvmem_device_release(struct device *dev, void *res) +{ + nvmem_device_put(*(struct nvmem_device **)res); +} + +/** + * devm_nvmem_device_put() - put already got nvmem device + * + * @dev: Device that uses the nvmem device. + * @nvmem: pointer to nvmem device allocated by devm_nvmem_cell_get(), + * that needs to be released. + */ +void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem) +{ + int ret; + + ret = devres_release(dev, devm_nvmem_device_release, + devm_nvmem_device_match, nvmem); + + WARN_ON(ret); +} +EXPORT_SYMBOL_GPL(devm_nvmem_device_put); + +/** + * nvmem_device_put() - put already got nvmem device + * + * @nvmem: pointer to nvmem device that needs to be released. + */ +void nvmem_device_put(struct nvmem_device *nvmem) +{ + put_device(&nvmem->dev); + module_put(nvmem->owner); + kref_put(&nvmem->refcnt, nvmem_device_release); +} +EXPORT_SYMBOL_GPL(nvmem_device_put); + +/** + * devm_nvmem_device_get() - Get nvmem device of device from a given id + * + * @dev: Device that requests the nvmem device. + * @id: name id for the requested nvmem device. + * + * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device + * on success. The nvmem_device will be freed by the automatically once the + * device is freed. + */ +struct nvmem_device *devm_nvmem_device_get(struct device *dev, const char *id) +{ + struct nvmem_device **ptr, *nvmem; + + ptr = devres_alloc(devm_nvmem_device_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + nvmem = nvmem_device_get(dev, id); + if (!IS_ERR(nvmem)) { + *ptr = nvmem; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return nvmem; +} +EXPORT_SYMBOL_GPL(devm_nvmem_device_get); + +static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry, + const char *id, int index) +{ + struct nvmem_cell *cell; + const char *name = NULL; + + cell = kzalloc_obj(*cell); + if (!cell) + return ERR_PTR(-ENOMEM); + + if (id) { + name = kstrdup_const(id, GFP_KERNEL); + if (!name) { + kfree(cell); + return ERR_PTR(-ENOMEM); + } + } + + cell->id = name; + cell->entry = entry; + cell->index = index; + + return cell; +} + +static struct nvmem_cell * +nvmem_cell_get_from_lookup(struct device *dev, const char *con_id) +{ + struct nvmem_cell_entry *cell_entry; + struct nvmem_cell *cell = ERR_PTR(-ENOENT); + struct nvmem_cell_lookup *lookup; + struct nvmem_device *nvmem; + const char *dev_id; + + if (!dev) + return ERR_PTR(-EINVAL); + + dev_id = dev_name(dev); + + guard(mutex)(&nvmem_lookup_mutex); + + list_for_each_entry(lookup, &nvmem_lookup_list, node) { + if ((strcmp(lookup->dev_id, dev_id) == 0) && + (strcmp(lookup->con_id, con_id) == 0)) { + /* This is the right entry. */ + nvmem = nvmem_device_match((void *)lookup->nvmem_name, + device_match_name); + if (IS_ERR(nvmem)) + /* Provider may not be registered yet. */ + return ERR_CAST(nvmem); + + cell_entry = nvmem_find_cell_entry_by_name(nvmem, + lookup->cell_name); + if (!cell_entry) { + nvmem_device_put(nvmem); + cell = ERR_PTR(-ENOENT); + } else { + cell = nvmem_create_cell(cell_entry, con_id, 0); + if (IS_ERR(cell)) + nvmem_device_put(nvmem); + } + break; + } + } + + return cell; +} + +static void nvmem_layout_module_put(struct nvmem_device *nvmem) +{ + if (nvmem->layout && nvmem->layout->dev.driver) + module_put(nvmem->layout->dev.driver->owner); +} + +#if IS_ENABLED(CONFIG_OF) +static struct nvmem_cell_entry * +nvmem_find_cell_entry_by_node(struct nvmem_device *nvmem, struct device_node *np) +{ + struct nvmem_cell_entry *cell; + + guard(mutex)(&nvmem_mutex); + + list_for_each_entry(cell, &nvmem->cells, node) { + if (np == cell->np) + return cell; + } + + return NULL; +} + +static int nvmem_layout_module_get_optional(struct nvmem_device *nvmem) +{ + if (!nvmem->layout) + return 0; + + if (!nvmem->layout->dev.driver || + !try_module_get(nvmem->layout->dev.driver->owner)) + return -EPROBE_DEFER; + + return 0; +} + +/** + * of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id + * + * @np: Device tree node that uses the nvmem cell. + * @id: nvmem cell name from nvmem-cell-names property, or NULL + * for the cell at index 0 (the lone cell with no accompanying + * nvmem-cell-names property). + * + * Return: Will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_cell. The nvmem_cell will be freed by the + * nvmem_cell_put(). + */ +struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id) +{ + struct device_node *cell_np, *nvmem_np; + struct nvmem_device *nvmem; + struct nvmem_cell_entry *cell_entry; + struct nvmem_cell *cell; + struct of_phandle_args cell_spec; + int index = 0; + int cell_index = 0; + int ret; + + /* if cell name exists, find index to the name */ + if (id) + index = of_property_match_string(np, "nvmem-cell-names", id); + + ret = of_parse_phandle_with_optional_args(np, "nvmem-cells", + "#nvmem-cell-cells", + index, &cell_spec); + if (ret) + return ERR_PTR(-ENOENT); + + if (cell_spec.args_count > 1) + return ERR_PTR(-EINVAL); + + cell_np = cell_spec.np; + if (cell_spec.args_count) + cell_index = cell_spec.args[0]; + + nvmem_np = of_get_parent(cell_np); + if (!nvmem_np) { + of_node_put(cell_np); + return ERR_PTR(-EINVAL); + } + + /* nvmem layouts produce cells within the nvmem-layout container */ + if (of_node_name_eq(nvmem_np, "nvmem-layout")) { + nvmem_np = of_get_next_parent(nvmem_np); + if (!nvmem_np) { + of_node_put(cell_np); + return ERR_PTR(-EINVAL); + } + } + + nvmem = nvmem_device_match(nvmem_np, device_match_of_node); + of_node_put(nvmem_np); + if (IS_ERR(nvmem)) { + of_node_put(cell_np); + return ERR_CAST(nvmem); + } + + ret = nvmem_layout_module_get_optional(nvmem); + if (ret) { + of_node_put(cell_np); + nvmem_device_put(nvmem); + return ERR_PTR(ret); + } + + cell_entry = nvmem_find_cell_entry_by_node(nvmem, cell_np); + of_node_put(cell_np); + if (!cell_entry) { + nvmem_layout_module_put(nvmem); + ret = nvmem->layout ? -EPROBE_DEFER : -ENOENT; + nvmem_device_put(nvmem); + return ERR_PTR(ret); + } + + cell = nvmem_create_cell(cell_entry, id, cell_index); + if (IS_ERR(cell)) { + nvmem_layout_module_put(nvmem); + nvmem_device_put(nvmem); + } + + return cell; +} +EXPORT_SYMBOL_GPL(of_nvmem_cell_get); +#endif + +/** + * nvmem_cell_get() - Get nvmem cell of device from a given cell name + * + * @dev: Device that requests the nvmem cell. + * @id: nvmem cell name to get (this corresponds with the name from the + * nvmem-cell-names property for DT systems and with the con_id from + * the lookup entry for non-DT systems). + * + * Return: Will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_cell. The nvmem_cell will be freed by the + * nvmem_cell_put(). + */ +struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *id) +{ + struct nvmem_cell *cell; + + if (dev->of_node) { /* try dt first */ + cell = of_nvmem_cell_get(dev->of_node, id); + if (!IS_ERR(cell) || PTR_ERR(cell) == -EPROBE_DEFER) + return cell; + } + + /* NULL cell id only allowed for device tree; invalid otherwise */ + if (!id) + return ERR_PTR(-EINVAL); + + return nvmem_cell_get_from_lookup(dev, id); +} +EXPORT_SYMBOL_GPL(nvmem_cell_get); + +static void devm_nvmem_cell_release(struct device *dev, void *res) +{ + nvmem_cell_put(*(struct nvmem_cell **)res); +} + +/** + * devm_nvmem_cell_get() - Get nvmem cell of device from a given id + * + * @dev: Device that requests the nvmem cell. + * @id: nvmem cell name id to get. + * + * Return: Will be an ERR_PTR() on error or a valid pointer + * to a struct nvmem_cell. The nvmem_cell will be freed by the + * automatically once the device is freed. + */ +struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, const char *id) +{ + struct nvmem_cell **ptr, *cell; + + ptr = devres_alloc(devm_nvmem_cell_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + cell = nvmem_cell_get(dev, id); + if (!IS_ERR(cell)) { + *ptr = cell; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return cell; +} +EXPORT_SYMBOL_GPL(devm_nvmem_cell_get); + +static int devm_nvmem_cell_match(struct device *dev, void *res, void *data) +{ + struct nvmem_cell **c = res; + + if (WARN_ON(!c || !*c)) + return 0; + + return *c == data; +} + +/** + * devm_nvmem_cell_put() - Release previously allocated nvmem cell + * from devm_nvmem_cell_get. + * + * @dev: Device that requests the nvmem cell. + * @cell: Previously allocated nvmem cell by devm_nvmem_cell_get(). + */ +void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell) +{ + int ret; + + ret = devres_release(dev, devm_nvmem_cell_release, + devm_nvmem_cell_match, cell); + + WARN_ON(ret); +} +EXPORT_SYMBOL(devm_nvmem_cell_put); + +/** + * nvmem_cell_put() - Release previously allocated nvmem cell. + * + * @cell: Previously allocated nvmem cell by nvmem_cell_get(). + */ +void nvmem_cell_put(struct nvmem_cell *cell) +{ + struct nvmem_device *nvmem = cell->entry->nvmem; + + if (cell->id) + kfree_const(cell->id); + + kfree(cell); + nvmem_layout_module_put(nvmem); + nvmem_device_put(nvmem); +} +EXPORT_SYMBOL_GPL(nvmem_cell_put); + +static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void *buf) +{ + u8 *p, *b; + int i, extra, bytes_offset; + int bit_offset = cell->bit_offset; + + p = b = buf; + + bytes_offset = bit_offset / BITS_PER_BYTE; + b += bytes_offset; + bit_offset %= BITS_PER_BYTE; + + if (bit_offset % BITS_PER_BYTE) { + /* First shift */ + *p = *b++ >> bit_offset; + + /* setup rest of the bytes if any */ + for (i = 1; i < cell->bytes; i++) { + /* Get bits from next byte and shift them towards msb */ + *p++ |= *b << (BITS_PER_BYTE - bit_offset); + + *p = *b++ >> bit_offset; + } + } else if (p != b) { + memmove(p, b, cell->bytes - bytes_offset); + p += cell->bytes - 1; + } else { + /* point to the msb */ + p += cell->bytes - 1; + } + + /* result fits in less bytes */ + extra = cell->bytes - DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE); + while (--extra >= 0) + *p-- = 0; + + /* clear msb bits if any leftover in the last byte */ + if (cell->nbits % BITS_PER_BYTE) + *p &= GENMASK((cell->nbits % BITS_PER_BYTE) - 1, 0); +} + +static int __nvmem_cell_read(struct nvmem_device *nvmem, + struct nvmem_cell_entry *cell, + void *buf, size_t *len, const char *id, int index) +{ + int rc; + + rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->raw_len); + + if (rc) + return rc; + + /* shift bits in-place */ + if (cell->bit_offset || cell->nbits) + nvmem_shift_read_buffer_in_place(cell, buf); + + if (cell->read_post_process) { + rc = cell->read_post_process(cell->priv, id, index, + cell->offset, buf, cell->raw_len); + if (rc) + return rc; + } + + if (len) + *len = cell->bytes; + + return 0; +} + +/** + * nvmem_cell_read() - Read a given nvmem cell + * + * @cell: nvmem cell to be read. + * @len: pointer to length of cell which will be populated on successful read; + * can be NULL. + * + * Return: ERR_PTR() on error or a valid pointer to a buffer on success. The + * buffer should be freed by the consumer with a kfree(). + */ +void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len) +{ + struct nvmem_cell_entry *entry = cell->entry; + struct nvmem_device *nvmem = entry->nvmem; + u8 *buf; + int rc; + + if (!nvmem) + return ERR_PTR(-EINVAL); + + buf = kzalloc(max_t(size_t, entry->raw_len, entry->bytes), GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + rc = __nvmem_cell_read(nvmem, cell->entry, buf, len, cell->id, cell->index); + if (rc) { + kfree(buf); + return ERR_PTR(rc); + } + + return buf; +} +EXPORT_SYMBOL_GPL(nvmem_cell_read); + +static void *nvmem_cell_prepare_write_buffer(struct nvmem_cell_entry *cell, + u8 *_buf, int len) +{ + struct nvmem_device *nvmem = cell->nvmem; + int i, rc, nbits, bit_offset = cell->bit_offset; + u8 v, *p, *buf, *b, pbyte, pbits; + + nbits = cell->nbits; + buf = kzalloc(cell->bytes, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + memcpy(buf, _buf, len); + p = b = buf; + + if (bit_offset) { + pbyte = *b; + *b <<= bit_offset; + + /* setup the first byte with lsb bits from nvmem */ + rc = nvmem_reg_read(nvmem, cell->offset, &v, 1); + if (rc) + goto err; + *b++ |= GENMASK(bit_offset - 1, 0) & v; + + /* setup rest of the byte if any */ + for (i = 1; i < cell->bytes; i++) { + /* Get last byte bits and shift them towards lsb */ + pbits = pbyte >> (BITS_PER_BYTE - 1 - bit_offset); + pbyte = *b; + p = b; + *b <<= bit_offset; + *b++ |= pbits; + } + } + + /* if it's not end on byte boundary */ + if ((nbits + bit_offset) % BITS_PER_BYTE) { + /* setup the last byte with msb bits from nvmem */ + rc = nvmem_reg_read(nvmem, + cell->offset + cell->bytes - 1, &v, 1); + if (rc) + goto err; + *p |= GENMASK(7, (nbits + bit_offset) % BITS_PER_BYTE) & v; + + } + + return buf; +err: + kfree(buf); + return ERR_PTR(rc); +} + +static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, void *buf, size_t len) +{ + struct nvmem_device *nvmem = cell->nvmem; + int rc; + + if (!nvmem || nvmem->read_only || + (cell->bit_offset == 0 && len != cell->bytes)) + return -EINVAL; + + /* + * Any cells which have a read_post_process hook are read-only because + * we cannot reverse the operation and it might affect other cells, + * too. + */ + if (cell->read_post_process) + return -EINVAL; + + if (cell->bit_offset || cell->nbits) { + if (len != BITS_TO_BYTES(cell->nbits) && len != cell->bytes) + return -EINVAL; + buf = nvmem_cell_prepare_write_buffer(cell, buf, len); + if (IS_ERR(buf)) + return PTR_ERR(buf); + } + + rc = nvmem_reg_write(nvmem, cell->offset, buf, cell->bytes); + + /* free the tmp buffer */ + if (cell->bit_offset || cell->nbits) + kfree(buf); + + if (rc) + return rc; + + return len; +} + +/** + * nvmem_cell_write() - Write to a given nvmem cell + * + * @cell: nvmem cell to be written. + * @buf: Buffer to be written. + * @len: length of buffer to be written to nvmem cell. + * + * Return: length of bytes written or negative on failure. + */ +int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len) +{ + return __nvmem_cell_entry_write(cell->entry, buf, len); +} + +EXPORT_SYMBOL_GPL(nvmem_cell_write); + +static int nvmem_cell_read_common(struct device *dev, const char *cell_id, + void *val, size_t count) +{ + struct nvmem_cell *cell; + void *buf; + size_t len; + + cell = nvmem_cell_get(dev, cell_id); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + buf = nvmem_cell_read(cell, &len); + if (IS_ERR(buf)) { + nvmem_cell_put(cell); + return PTR_ERR(buf); + } + if (len != count) { + kfree(buf); + nvmem_cell_put(cell); + return -EINVAL; + } + memcpy(val, buf, count); + kfree(buf); + nvmem_cell_put(cell); + + return 0; +} + +/** + * nvmem_cell_read_u8() - Read a cell value as a u8 + * + * @dev: Device that requests the nvmem cell. + * @cell_id: Name of nvmem cell to read. + * @val: pointer to output value. + * + * Return: 0 on success or negative errno. + */ +int nvmem_cell_read_u8(struct device *dev, const char *cell_id, u8 *val) +{ + return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val)); +} +EXPORT_SYMBOL_GPL(nvmem_cell_read_u8); + +/** + * nvmem_cell_read_u16() - Read a cell value as a u16 + * + * @dev: Device that requests the nvmem cell. + * @cell_id: Name of nvmem cell to read. + * @val: pointer to output value. + * + * Return: 0 on success or negative errno. + */ +int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val) +{ + return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val)); +} +EXPORT_SYMBOL_GPL(nvmem_cell_read_u16); + +/** + * nvmem_cell_read_u32() - Read a cell value as a u32 + * + * @dev: Device that requests the nvmem cell. + * @cell_id: Name of nvmem cell to read. + * @val: pointer to output value. + * + * Return: 0 on success or negative errno. + */ +int nvmem_cell_read_u32(struct device *dev, const char *cell_id, u32 *val) +{ + return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val)); +} +EXPORT_SYMBOL_GPL(nvmem_cell_read_u32); + +/** + * nvmem_cell_read_u64() - Read a cell value as a u64 + * + * @dev: Device that requests the nvmem cell. + * @cell_id: Name of nvmem cell to read. + * @val: pointer to output value. + * + * Return: 0 on success or negative errno. + */ +int nvmem_cell_read_u64(struct device *dev, const char *cell_id, u64 *val) +{ + return nvmem_cell_read_common(dev, cell_id, val, sizeof(*val)); +} +EXPORT_SYMBOL_GPL(nvmem_cell_read_u64); + +static const void *nvmem_cell_read_variable_common(struct device *dev, + const char *cell_id, + size_t max_len, size_t *len) +{ + struct nvmem_cell *cell; + int nbits; + void *buf; + + cell = nvmem_cell_get(dev, cell_id); + if (IS_ERR(cell)) + return cell; + + nbits = cell->entry->nbits; + buf = nvmem_cell_read(cell, len); + nvmem_cell_put(cell); + if (IS_ERR(buf)) + return buf; + + /* + * If nbits is set then nvmem_cell_read() can significantly exaggerate + * the length of the real data. Throw away the extra junk. + */ + if (nbits) + *len = DIV_ROUND_UP(nbits, 8); + + if (*len > max_len) { + kfree(buf); + return ERR_PTR(-ERANGE); + } + + return buf; +} + +/** + * nvmem_cell_read_variable_le_u32() - Read up to 32-bits of data as a little endian number. + * + * @dev: Device that requests the nvmem cell. + * @cell_id: Name of nvmem cell to read. + * @val: pointer to output value. + * + * Return: 0 on success or negative errno. + */ +int nvmem_cell_read_variable_le_u32(struct device *dev, const char *cell_id, + u32 *val) +{ + size_t len; + const u8 *buf; + int i; + + buf = nvmem_cell_read_variable_common(dev, cell_id, sizeof(*val), &len); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* Copy w/ implicit endian conversion */ + *val = 0; + for (i = 0; i < len; i++) + *val |= buf[i] << (8 * i); + + kfree(buf); + + return 0; +} +EXPORT_SYMBOL_GPL(nvmem_cell_read_variable_le_u32); + +/** + * nvmem_cell_read_variable_le_u64() - Read up to 64-bits of data as a little endian number. + * + * @dev: Device that requests the nvmem cell. + * @cell_id: Name of nvmem cell to read. + * @val: pointer to output value. + * + * Return: 0 on success or negative errno. + */ +int nvmem_cell_read_variable_le_u64(struct device *dev, const char *cell_id, + u64 *val) +{ + size_t len; + const u8 *buf; + int i; + + buf = nvmem_cell_read_variable_common(dev, cell_id, sizeof(*val), &len); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* Copy w/ implicit endian conversion */ + *val = 0; + for (i = 0; i < len; i++) + *val |= (uint64_t)buf[i] << (8 * i); + + kfree(buf); + + return 0; +} +EXPORT_SYMBOL_GPL(nvmem_cell_read_variable_le_u64); + +/** + * nvmem_device_cell_read() - Read a given nvmem device and cell + * + * @nvmem: nvmem device to read from. + * @info: nvmem cell info to be read. + * @buf: buffer pointer which will be populated on successful read. + * + * Return: length of successful bytes read on success and negative + * error code on error. + */ +ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf) +{ + struct nvmem_cell_entry cell; + int rc; + ssize_t len; + + if (!nvmem) + return -EINVAL; + + rc = nvmem_cell_info_to_nvmem_cell_entry_nodup(nvmem, info, &cell); + if (rc) + return rc; + + rc = __nvmem_cell_read(nvmem, &cell, buf, &len, NULL, 0); + if (rc) + return rc; + + return len; +} +EXPORT_SYMBOL_GPL(nvmem_device_cell_read); + +/** + * nvmem_device_cell_write() - Write cell to a given nvmem device + * + * @nvmem: nvmem device to be written to. + * @info: nvmem cell info to be written. + * @buf: buffer to be written to cell. + * + * Return: length of bytes written or negative error code on failure. + */ +int nvmem_device_cell_write(struct nvmem_device *nvmem, + struct nvmem_cell_info *info, void *buf) +{ + struct nvmem_cell_entry cell; + int rc; + + if (!nvmem) + return -EINVAL; + + rc = nvmem_cell_info_to_nvmem_cell_entry_nodup(nvmem, info, &cell); + if (rc) + return rc; + + return __nvmem_cell_entry_write(&cell, buf, cell.bytes); +} +EXPORT_SYMBOL_GPL(nvmem_device_cell_write); + +/** + * nvmem_device_read() - Read from a given nvmem device + * + * @nvmem: nvmem device to read from. + * @offset: offset in nvmem device. + * @bytes: number of bytes to read. + * @buf: buffer pointer which will be populated on successful read. + * + * Return: length of successful bytes read on success and negative + * error code on error. + */ +int nvmem_device_read(struct nvmem_device *nvmem, + unsigned int offset, + size_t bytes, void *buf) +{ + int rc; + + if (!nvmem) + return -EINVAL; + + rc = nvmem_reg_read(nvmem, offset, buf, bytes); + + if (rc) + return rc; + + return bytes; +} +EXPORT_SYMBOL_GPL(nvmem_device_read); + +/** + * nvmem_device_write() - Write cell to a given nvmem device + * + * @nvmem: nvmem device to be written to. + * @offset: offset in nvmem device. + * @bytes: number of bytes to write. + * @buf: buffer to be written. + * + * Return: length of bytes written or negative error code on failure. + */ +int nvmem_device_write(struct nvmem_device *nvmem, + unsigned int offset, + size_t bytes, void *buf) +{ + int rc; + + if (!nvmem) + return -EINVAL; + + rc = nvmem_reg_write(nvmem, offset, buf, bytes); + + if (rc) + return rc; + + + return bytes; +} +EXPORT_SYMBOL_GPL(nvmem_device_write); + +/** + * nvmem_add_cell_lookups() - register a list of cell lookup entries + * + * @entries: array of cell lookup entries + * @nentries: number of cell lookup entries in the array + */ +void nvmem_add_cell_lookups(struct nvmem_cell_lookup *entries, size_t nentries) +{ + int i; + + guard(mutex)(&nvmem_lookup_mutex); + + for (i = 0; i < nentries; i++) + list_add_tail(&entries[i].node, &nvmem_lookup_list); +} +EXPORT_SYMBOL_GPL(nvmem_add_cell_lookups); + +/** + * nvmem_del_cell_lookups() - remove a list of previously added cell lookup + * entries + * + * @entries: array of cell lookup entries + * @nentries: number of cell lookup entries in the array + */ +void nvmem_del_cell_lookups(struct nvmem_cell_lookup *entries, size_t nentries) +{ + int i; + + guard(mutex)(&nvmem_lookup_mutex); + + for (i = 0; i < nentries; i++) + list_del(&entries[i].node); +} +EXPORT_SYMBOL_GPL(nvmem_del_cell_lookups); + +/** + * nvmem_dev_name() - Get the name of a given nvmem device. + * + * @nvmem: nvmem device. + * + * Return: name of the nvmem device. + */ +const char *nvmem_dev_name(struct nvmem_device *nvmem) +{ + return dev_name(&nvmem->dev); +} +EXPORT_SYMBOL_GPL(nvmem_dev_name); + +/** + * nvmem_dev_size() - Get the size of a given nvmem device. + * + * @nvmem: nvmem device. + * + * Return: size of the nvmem device. + */ +size_t nvmem_dev_size(struct nvmem_device *nvmem) +{ + return nvmem->size; +} +EXPORT_SYMBOL_GPL(nvmem_dev_size); + +static int __init nvmem_init(void) +{ + int ret; + + ret = bus_register(&nvmem_bus_type); + if (ret) + return ret; + + ret = nvmem_layout_bus_register(); + if (ret) + bus_unregister(&nvmem_bus_type); + + return ret; +} + +static void __exit nvmem_exit(void) +{ + nvmem_layout_bus_unregister(); + bus_unregister(&nvmem_bus_type); +} + +subsys_initcall(nvmem_init); +module_exit(nvmem_exit); + +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org>"); +MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>"); +MODULE_DESCRIPTION("nvmem Driver Core"); diff --git a/drivers/nvmem/ee1004.c b/drivers/nvmem/ee1004.c new file mode 100644 index 000000000..923f404a4 --- /dev/null +++ b/drivers/nvmem/ee1004.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ee1004 - driver for DDR4 SPD EEPROMs + * + * Copyright (C) 2017-2019 Jean Delvare + * + * Based on the at24 driver: + * Copyright (C) 2005-2007 David Brownell + * Copyright (C) 2008 Wolfram Sang, Pengutronix + */ + +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/nvmem-provider.h> + +/* + * DDR4 memory modules use special EEPROMs following the Jedec EE1004 + * specification. These are 512-byte EEPROMs using a single I2C address + * in the 0x50-0x57 range for data. One of two 256-byte page is selected + * by writing a command to I2C address 0x36 or 0x37 on the same I2C bus. + * + * Therefore we need to request these 2 additional addresses, and serialize + * access to all such EEPROMs with a single mutex. + * + * We assume it is safe to read up to 32 bytes at once from these EEPROMs. + * We use SMBus access even if I2C is available, these EEPROMs are small + * enough, and reading from them infrequent enough, that we favor simplicity + * over performance. + */ + +#define EE1004_MAX_BUSSES 8 +#define EE1004_ADDR_SET_PAGE 0x36 +#define EE1004_NUM_PAGES 2 +#define EE1004_PAGE_SIZE 256 +#define EE1004_PAGE_SHIFT 8 +#define EE1004_EEPROM_SIZE (EE1004_PAGE_SIZE * EE1004_NUM_PAGES) + +/* + * Mutex protects ee1004_set_page and ee1004_dev_count, and must be held + * from page selection to end of read. + */ +static DEFINE_MUTEX(ee1004_bus_lock); + +static struct ee1004_bus_data { + struct i2c_adapter *adap; + struct i2c_client *set_page[EE1004_NUM_PAGES]; + unsigned int dev_count; + int current_page; +} ee1004_bus_data[EE1004_MAX_BUSSES]; + +static const struct i2c_device_id ee1004_ids[] = { + { "ee1004" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ee1004_ids); + +/*-------------------------------------------------------------------------*/ + +static struct ee1004_bus_data *ee1004_get_bus_data(struct i2c_adapter *adap) +{ + int i; + + for (i = 0; i < EE1004_MAX_BUSSES; i++) + if (ee1004_bus_data[i].adap == adap) + return ee1004_bus_data + i; + + /* If not existent yet, create new entry */ + for (i = 0; i < EE1004_MAX_BUSSES; i++) + if (!ee1004_bus_data[i].adap) { + ee1004_bus_data[i].adap = adap; + return ee1004_bus_data + i; + } + + return NULL; +} + +static int ee1004_get_current_page(struct ee1004_bus_data *bd) +{ + int err; + + err = i2c_smbus_read_byte(bd->set_page[0]); + if (err == -ENXIO) { + /* Nack means page 1 is selected */ + return 1; + } + if (err < 0) { + /* Anything else is a real error, bail out */ + return err; + } + + /* Ack means page 0 is selected, returned value meaningless */ + return 0; +} + +static int ee1004_set_current_page(struct i2c_client *client, int page) +{ + struct ee1004_bus_data *bd = i2c_get_clientdata(client); + int ret; + + if (page == bd->current_page) + return 0; + + /* Data is ignored */ + ret = i2c_smbus_write_byte(bd->set_page[page], 0x00); + /* + * Don't give up just yet. Some memory modules will select the page + * but not ack the command. Check which page is selected now. + */ + if (ret == -ENXIO && ee1004_get_current_page(bd) == page) + ret = 0; + if (ret < 0) { + dev_err(&client->dev, "Failed to select page %d (%d)\n", page, ret); + return ret; + } + + dev_dbg(&client->dev, "Selected page %d\n", page); + bd->current_page = page; + + return 0; +} + +static ssize_t ee1004_eeprom_read(struct i2c_client *client, char *buf, + unsigned int offset, size_t count) +{ + int status, page; + + page = offset >> EE1004_PAGE_SHIFT; + offset &= (1 << EE1004_PAGE_SHIFT) - 1; + + status = ee1004_set_current_page(client, page); + if (status) + return status; + + /* Can't cross page boundaries */ + if (offset + count > EE1004_PAGE_SIZE) + count = EE1004_PAGE_SIZE - offset; + + if (count > I2C_SMBUS_BLOCK_MAX) + count = I2C_SMBUS_BLOCK_MAX; + + return i2c_smbus_read_i2c_block_data_or_emulated(client, offset, count, buf); +} + +static int ee1004_read(void *priv, unsigned int off, void *val, size_t count) +{ + struct i2c_client *client = priv; + char *buf = val; + int ret; + + if (unlikely(!count)) + return count; + + if (off + count > EE1004_EEPROM_SIZE) + return -EINVAL; + + /* + * Read data from chip, protecting against concurrent access to + * other EE1004 SPD EEPROMs on the same adapter. + */ + mutex_lock(&ee1004_bus_lock); + + while (count) { + ret = ee1004_eeprom_read(client, buf, off, count); + if (ret < 0) { + mutex_unlock(&ee1004_bus_lock); + return ret; + } + + buf += ret; + off += ret; + count -= ret; + } + + mutex_unlock(&ee1004_bus_lock); + + return 0; +} + +static void ee1004_probe_temp_sensor(struct i2c_client *client) +{ + struct i2c_board_info info = { .type = "jc42" }; + unsigned short addr = 0x18 | (client->addr & 7); + unsigned short addr_list[] = { addr, I2C_CLIENT_END }; + u8 data[2]; + int ret; + + /* byte 14, bit 7 is set if temp sensor is present */ + ret = ee1004_eeprom_read(client, data, 14, 1); + if (ret != 1) + return; + + if (!(data[0] & BIT(7))) { + /* + * If the SPD data suggests that there is no temperature + * sensor, it may still be there for SPD revision 1.0. + * See SPD Annex L, Revision 1 and 2, for details. + * Check DIMM type and SPD revision; if it is a DDR4 + * with SPD revision 1.0, check the thermal sensor address + * and instantiate the jc42 driver if a chip is found at + * that address. + * It is not necessary to check if there is a chip at the + * temperature sensor address since i2c_new_scanned_device() + * will do that and return silently if no chip is found. + */ + ret = ee1004_eeprom_read(client, data, 1, 2); + if (ret != 2 || data[0] != 0x10 || data[1] != 0x0c) + return; + } + i2c_new_scanned_device(client->adapter, &info, addr_list, NULL); +} + +static void ee1004_cleanup(int idx, struct ee1004_bus_data *bd) +{ + if (--bd->dev_count == 0) { + while (--idx >= 0) + i2c_unregister_device(bd->set_page[idx]); + memset(bd, 0, sizeof(struct ee1004_bus_data)); + } +} + +static void ee1004_cleanup_bus_data(void *data) +{ + struct ee1004_bus_data *bd = data; + + /* Remove page select clients if this is the last device */ + mutex_lock(&ee1004_bus_lock); + ee1004_cleanup(EE1004_NUM_PAGES, bd); + mutex_unlock(&ee1004_bus_lock); +} + +static int ee1004_init_bus_data(struct i2c_client *client) +{ + struct ee1004_bus_data *bd; + int err, cnr = 0; + + bd = ee1004_get_bus_data(client->adapter); + if (!bd) + return dev_err_probe(&client->dev, -ENOSPC, "Only %d busses supported", + EE1004_MAX_BUSSES); + + i2c_set_clientdata(client, bd); + + if (++bd->dev_count == 1) { + /* Use 2 dummy devices for page select command */ + for (cnr = 0; cnr < EE1004_NUM_PAGES; cnr++) { + struct i2c_client *cl; + + cl = i2c_new_dummy_device(client->adapter, EE1004_ADDR_SET_PAGE + cnr); + if (IS_ERR(cl)) { + err = PTR_ERR(cl); + goto err_out; + } + + bd->set_page[cnr] = cl; + } + + /* Remember current page to avoid unneeded page select */ + err = ee1004_get_current_page(bd); + if (err < 0) + goto err_out; + + dev_dbg(&client->dev, "Currently selected page: %d\n", err); + bd->current_page = err; + } + + return 0; + +err_out: + ee1004_cleanup(cnr, bd); + + return err; +} + +static int ee1004_probe(struct i2c_client *client) +{ + struct nvmem_config config = { + .dev = &client->dev, + .name = dev_name(&client->dev), + .id = NVMEM_DEVID_NONE, + .owner = THIS_MODULE, + .type = NVMEM_TYPE_EEPROM, + .read_only = true, + .root_only = false, + .reg_read = ee1004_read, + .size = EE1004_EEPROM_SIZE, + .word_size = 1, + .stride = 1, + .priv = client, + .compat = true, + .base_dev = &client->dev, + }; + struct nvmem_device *ndev; + int err; + + /* Make sure we can operate on this adapter */ + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_READ_I2C_BLOCK) && + !i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_READ_BYTE_DATA)) + return -EPFNOSUPPORT; + + err = i2c_smbus_read_byte(client); + if (err < 0) + return -ENODEV; + + mutex_lock(&ee1004_bus_lock); + + err = ee1004_init_bus_data(client); + if (err < 0) { + mutex_unlock(&ee1004_bus_lock); + return err; + } + + ee1004_probe_temp_sensor(client); + + mutex_unlock(&ee1004_bus_lock); + + err = devm_add_action_or_reset(&client->dev, ee1004_cleanup_bus_data, + i2c_get_clientdata(client)); + if (err < 0) + return err; + + ndev = devm_nvmem_register(&client->dev, &config); + if (IS_ERR(ndev)) + return PTR_ERR(ndev); + + dev_info(&client->dev, + "%u byte EE1004-compliant SPD EEPROM, read-only\n", + EE1004_EEPROM_SIZE); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static struct i2c_driver ee1004_driver = { + .driver = { + .name = "ee1004", + }, + .probe = ee1004_probe, + .id_table = ee1004_ids, +}; +module_i2c_driver(ee1004_driver); + +MODULE_DESCRIPTION("Driver for EE1004-compliant DDR4 SPD EEPROMs"); +MODULE_AUTHOR("Jean Delvare"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/eeprom_93xx46.c b/drivers/nvmem/eeprom_93xx46.c new file mode 100644 index 000000000..f9c3ab52c --- /dev/null +++ b/drivers/nvmem/eeprom_93xx46.c @@ -0,0 +1,560 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for 93xx46 EEPROMs + * + * (C) 2011 DENX Software Engineering, Anatolij Gustschin <agust@denx.de> + */ + +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/kstrtox.h> +#include <linux/log2.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/spi/spi.h> +#include <linux/string_choices.h> + +#include <linux/nvmem-provider.h> + +struct eeprom_93xx46_platform_data { + unsigned char flags; +#define EE_ADDR8 0x01 /* 8 bit addr. cfg */ +#define EE_ADDR16 0x02 /* 16 bit addr. cfg */ +#define EE_READONLY 0x08 /* forbid writing */ +#define EE_SIZE1K 0x10 /* 1 kb of data, that is a 93xx46 */ +#define EE_SIZE2K 0x20 /* 2 kb of data, that is a 93xx56 */ +#define EE_SIZE4K 0x40 /* 4 kb of data, that is a 93xx66 */ + + unsigned int quirks; +/* Single word read transfers only; no sequential read. */ +#define EEPROM_93XX46_QUIRK_SINGLE_WORD_READ (1 << 0) +/* Instructions such as EWEN are (addrlen + 2) in length. */ +#define EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH (1 << 1) +/* Add extra cycle after address during a read */ +#define EEPROM_93XX46_QUIRK_EXTRA_READ_CYCLE BIT(2) + + struct gpio_desc *select; +}; + +#define OP_START 0x4 +#define OP_WRITE (OP_START | 0x1) +#define OP_READ (OP_START | 0x2) +/* The following addresses are offset for the 1K EEPROM variant in 16-bit mode */ +#define ADDR_EWDS 0x00 +#define ADDR_ERAL 0x20 +#define ADDR_EWEN 0x30 + +struct eeprom_93xx46_devtype_data { + unsigned int quirks; + unsigned char flags; +}; + +static const struct eeprom_93xx46_devtype_data at93c46_data = { + .flags = EE_SIZE1K, +}; + +static const struct eeprom_93xx46_devtype_data at93c56_data = { + .flags = EE_SIZE2K, +}; + +static const struct eeprom_93xx46_devtype_data at93c66_data = { + .flags = EE_SIZE4K, +}; + +static const struct eeprom_93xx46_devtype_data atmel_at93c46d_data = { + .flags = EE_SIZE1K, + .quirks = EEPROM_93XX46_QUIRK_SINGLE_WORD_READ | + EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH, +}; + +static const struct eeprom_93xx46_devtype_data microchip_93lc46b_data = { + .flags = EE_SIZE1K, + .quirks = EEPROM_93XX46_QUIRK_EXTRA_READ_CYCLE, +}; + +struct eeprom_93xx46_dev { + struct spi_device *spi; + struct eeprom_93xx46_platform_data *pdata; + struct mutex lock; + struct nvmem_config nvmem_config; + struct nvmem_device *nvmem; + int addrlen; + int size; +}; + +static inline bool has_quirk_single_word_read(struct eeprom_93xx46_dev *edev) +{ + return edev->pdata->quirks & EEPROM_93XX46_QUIRK_SINGLE_WORD_READ; +} + +static inline bool has_quirk_instruction_length(struct eeprom_93xx46_dev *edev) +{ + return edev->pdata->quirks & EEPROM_93XX46_QUIRK_INSTRUCTION_LENGTH; +} + +static inline bool has_quirk_extra_read_cycle(struct eeprom_93xx46_dev *edev) +{ + return edev->pdata->quirks & EEPROM_93XX46_QUIRK_EXTRA_READ_CYCLE; +} + +static int eeprom_93xx46_read(void *priv, unsigned int off, + void *val, size_t count) +{ + struct eeprom_93xx46_dev *edev = priv; + char *buf = val; + int err = 0; + int bits; + + if (unlikely(off >= edev->size)) + return 0; + if ((off + count) > edev->size) + count = edev->size - off; + if (unlikely(!count)) + return count; + + mutex_lock(&edev->lock); + + gpiod_set_value_cansleep(edev->pdata->select, 1); + + /* The opcode in front of the address is three bits. */ + bits = edev->addrlen + 3; + + while (count) { + struct spi_message m; + struct spi_transfer t[2] = {}; + u16 cmd_addr = OP_READ << edev->addrlen; + size_t nbytes = count; + + if (edev->pdata->flags & EE_ADDR8) { + cmd_addr |= off; + if (has_quirk_single_word_read(edev)) + nbytes = 1; + } else { + cmd_addr |= (off >> 1); + if (has_quirk_single_word_read(edev)) + nbytes = 2; + } + + dev_dbg(&edev->spi->dev, "read cmd 0x%x, %d Hz\n", + cmd_addr, edev->spi->max_speed_hz); + + if (has_quirk_extra_read_cycle(edev)) { + cmd_addr <<= 1; + bits += 1; + } + + t[0].tx_buf = (char *)&cmd_addr; + t[0].len = 2; + t[0].bits_per_word = bits; + + t[1].rx_buf = buf; + t[1].len = count; + t[1].bits_per_word = 8; + + spi_message_init_with_transfers(&m, t, ARRAY_SIZE(t)); + + err = spi_sync(edev->spi, &m); + /* have to wait at least Tcsl ns */ + ndelay(250); + + if (err) { + dev_err(&edev->spi->dev, "read %zu bytes at %u: err. %d\n", + nbytes, off, err); + break; + } + + buf += nbytes; + off += nbytes; + count -= nbytes; + } + + gpiod_set_value_cansleep(edev->pdata->select, 0); + + mutex_unlock(&edev->lock); + + return err; +} + +static int eeprom_93xx46_ew(struct eeprom_93xx46_dev *edev, int is_on) +{ + struct spi_message m; + struct spi_transfer t = {}; + int bits, ret; + u16 cmd_addr; + + /* The opcode in front of the address is three bits. */ + bits = edev->addrlen + 3; + + cmd_addr = OP_START << edev->addrlen; + cmd_addr |= (is_on ? ADDR_EWEN : ADDR_EWDS) << (edev->addrlen - 6); + + if (has_quirk_instruction_length(edev)) { + cmd_addr <<= 2; + bits += 2; + } + + dev_dbg(&edev->spi->dev, "ew %s cmd 0x%04x, %d bits\n", + str_enable_disable(is_on), cmd_addr, bits); + + t.tx_buf = &cmd_addr; + t.len = 2; + t.bits_per_word = bits; + + spi_message_init_with_transfers(&m, &t, 1); + + mutex_lock(&edev->lock); + + gpiod_set_value_cansleep(edev->pdata->select, 1); + + ret = spi_sync(edev->spi, &m); + /* have to wait at least Tcsl ns */ + ndelay(250); + if (ret) + dev_err(&edev->spi->dev, "erase/write %s error %d\n", + str_enable_disable(is_on), ret); + + gpiod_set_value_cansleep(edev->pdata->select, 0); + + mutex_unlock(&edev->lock); + return ret; +} + +static ssize_t +eeprom_93xx46_write_word(struct eeprom_93xx46_dev *edev, + const char *buf, unsigned int off) +{ + struct spi_message m; + struct spi_transfer t[2] = {}; + int bits, data_len, ret; + u16 cmd_addr; + + if (unlikely(off >= edev->size)) + return -EINVAL; + + /* The opcode in front of the address is three bits. */ + bits = edev->addrlen + 3; + + cmd_addr = OP_WRITE << edev->addrlen; + + if (edev->pdata->flags & EE_ADDR8) { + cmd_addr |= off; + data_len = 1; + } else { + cmd_addr |= (off >> 1); + data_len = 2; + } + + dev_dbg(&edev->spi->dev, "write cmd 0x%x\n", cmd_addr); + + t[0].tx_buf = (char *)&cmd_addr; + t[0].len = 2; + t[0].bits_per_word = bits; + + t[1].tx_buf = buf; + t[1].len = data_len; + t[1].bits_per_word = 8; + + spi_message_init_with_transfers(&m, t, ARRAY_SIZE(t)); + + ret = spi_sync(edev->spi, &m); + /* have to wait program cycle time Twc ms */ + mdelay(6); + return ret; +} + +static int eeprom_93xx46_write(void *priv, unsigned int off, + void *val, size_t count) +{ + struct eeprom_93xx46_dev *edev = priv; + char *buf = val; + int ret, step = 1; + unsigned int i; + + if (unlikely(off >= edev->size)) + return -EFBIG; + if ((off + count) > edev->size) + count = edev->size - off; + if (unlikely(!count)) + return count; + + /* only write even number of bytes on 16-bit devices */ + if (edev->pdata->flags & EE_ADDR16) { + step = 2; + count &= ~1; + } + + /* erase/write enable */ + ret = eeprom_93xx46_ew(edev, 1); + if (ret) + return ret; + + mutex_lock(&edev->lock); + + gpiod_set_value_cansleep(edev->pdata->select, 1); + + for (i = 0; i < count; i += step) { + ret = eeprom_93xx46_write_word(edev, &buf[i], off + i); + if (ret) { + dev_err(&edev->spi->dev, "write failed at %u: %d\n", off + i, ret); + break; + } + } + + gpiod_set_value_cansleep(edev->pdata->select, 0); + + mutex_unlock(&edev->lock); + + /* erase/write disable */ + eeprom_93xx46_ew(edev, 0); + return ret; +} + +static int eeprom_93xx46_eral(struct eeprom_93xx46_dev *edev) +{ + struct spi_message m; + struct spi_transfer t = {}; + int bits, ret; + u16 cmd_addr; + + /* The opcode in front of the address is three bits. */ + bits = edev->addrlen + 3; + + cmd_addr = OP_START << edev->addrlen; + cmd_addr |= ADDR_ERAL << (edev->addrlen - 6); + + if (has_quirk_instruction_length(edev)) { + cmd_addr <<= 2; + bits += 2; + } + + dev_dbg(&edev->spi->dev, "eral cmd 0x%04x, %d bits\n", cmd_addr, bits); + + t.tx_buf = &cmd_addr; + t.len = 2; + t.bits_per_word = bits; + + spi_message_init_with_transfers(&m, &t, 1); + + mutex_lock(&edev->lock); + + gpiod_set_value_cansleep(edev->pdata->select, 1); + + ret = spi_sync(edev->spi, &m); + if (ret) + dev_err(&edev->spi->dev, "erase error %d\n", ret); + /* have to wait erase cycle time Tec ms */ + mdelay(6); + + gpiod_set_value_cansleep(edev->pdata->select, 0); + + mutex_unlock(&edev->lock); + return ret; +} + +static ssize_t erase_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct eeprom_93xx46_dev *edev = dev_get_drvdata(dev); + bool erase; + int ret; + + ret = kstrtobool(buf, &erase); + if (ret) + return ret; + + if (erase) { + ret = eeprom_93xx46_ew(edev, 1); + if (ret) + return ret; + ret = eeprom_93xx46_eral(edev); + if (ret) + return ret; + ret = eeprom_93xx46_ew(edev, 0); + if (ret) + return ret; + } + return count; +} +static DEVICE_ATTR_WO(erase); + +static const struct of_device_id eeprom_93xx46_of_table[] = { + { .compatible = "eeprom-93xx46", .data = &at93c46_data, }, + { .compatible = "atmel,at93c46", .data = &at93c46_data, }, + { .compatible = "atmel,at93c46d", .data = &atmel_at93c46d_data, }, + { .compatible = "atmel,at93c56", .data = &at93c56_data, }, + { .compatible = "atmel,at93c66", .data = &at93c66_data, }, + { .compatible = "microchip,93lc46b", .data = µchip_93lc46b_data, }, + {} +}; +MODULE_DEVICE_TABLE(of, eeprom_93xx46_of_table); + +static const struct spi_device_id eeprom_93xx46_spi_ids[] = { + { .name = "eeprom-93xx46", + .driver_data = (kernel_ulong_t)&at93c46_data, }, + { .name = "at93c46", + .driver_data = (kernel_ulong_t)&at93c46_data, }, + { .name = "at93c46d", + .driver_data = (kernel_ulong_t)&atmel_at93c46d_data, }, + { .name = "at93c56", + .driver_data = (kernel_ulong_t)&at93c56_data, }, + { .name = "at93c66", + .driver_data = (kernel_ulong_t)&at93c66_data, }, + { .name = "93lc46b", + .driver_data = (kernel_ulong_t)µchip_93lc46b_data, }, + {} +}; +MODULE_DEVICE_TABLE(spi, eeprom_93xx46_spi_ids); + +static int eeprom_93xx46_probe_fw(struct device *dev) +{ + const struct eeprom_93xx46_devtype_data *data; + struct eeprom_93xx46_platform_data *pd; + u32 tmp; + int ret; + + pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + + ret = device_property_read_u32(dev, "data-size", &tmp); + if (ret < 0) { + dev_err(dev, "data-size property not found\n"); + return ret; + } + + if (tmp == 8) { + pd->flags |= EE_ADDR8; + } else if (tmp == 16) { + pd->flags |= EE_ADDR16; + } else { + dev_err(dev, "invalid data-size (%d)\n", tmp); + return -EINVAL; + } + + if (device_property_read_bool(dev, "read-only")) + pd->flags |= EE_READONLY; + + pd->select = devm_gpiod_get_optional(dev, "select", GPIOD_OUT_LOW); + if (IS_ERR(pd->select)) + return PTR_ERR(pd->select); + gpiod_set_consumer_name(pd->select, "93xx46 EEPROMs OE"); + + data = spi_get_device_match_data(to_spi_device(dev)); + if (data) { + pd->quirks = data->quirks; + pd->flags |= data->flags; + } + + dev->platform_data = pd; + + return 0; +} + +static int eeprom_93xx46_probe(struct spi_device *spi) +{ + struct eeprom_93xx46_platform_data *pd; + struct eeprom_93xx46_dev *edev; + struct device *dev = &spi->dev; + int err; + + err = eeprom_93xx46_probe_fw(dev); + if (err < 0) + return err; + + pd = spi->dev.platform_data; + if (!pd) { + dev_err(&spi->dev, "missing platform data\n"); + return -ENODEV; + } + + edev = devm_kzalloc(&spi->dev, sizeof(*edev), GFP_KERNEL); + if (!edev) + return -ENOMEM; + + if (pd->flags & EE_SIZE1K) + edev->size = 128; + else if (pd->flags & EE_SIZE2K) + edev->size = 256; + else if (pd->flags & EE_SIZE4K) + edev->size = 512; + else { + dev_err(&spi->dev, "unspecified size\n"); + return -EINVAL; + } + + if (pd->flags & EE_ADDR8) + edev->addrlen = ilog2(edev->size); + else if (pd->flags & EE_ADDR16) + edev->addrlen = ilog2(edev->size) - 1; + else { + dev_err(&spi->dev, "unspecified address type\n"); + return -EINVAL; + } + + mutex_init(&edev->lock); + + edev->spi = spi; + edev->pdata = pd; + + edev->nvmem_config.type = NVMEM_TYPE_EEPROM; + edev->nvmem_config.name = dev_name(&spi->dev); + edev->nvmem_config.dev = &spi->dev; + edev->nvmem_config.read_only = pd->flags & EE_READONLY; + edev->nvmem_config.root_only = true; + edev->nvmem_config.owner = THIS_MODULE; + edev->nvmem_config.compat = true; + edev->nvmem_config.base_dev = &spi->dev; + edev->nvmem_config.reg_read = eeprom_93xx46_read; + edev->nvmem_config.reg_write = eeprom_93xx46_write; + edev->nvmem_config.priv = edev; + edev->nvmem_config.stride = 4; + edev->nvmem_config.word_size = 1; + edev->nvmem_config.size = edev->size; + + edev->nvmem = devm_nvmem_register(&spi->dev, &edev->nvmem_config); + if (IS_ERR(edev->nvmem)) + return PTR_ERR(edev->nvmem); + + dev_info(&spi->dev, "%d-bit eeprom containing %d bytes %s\n", + (pd->flags & EE_ADDR8) ? 8 : 16, + edev->size, + (pd->flags & EE_READONLY) ? "(readonly)" : ""); + + if (!(pd->flags & EE_READONLY)) { + if (device_create_file(&spi->dev, &dev_attr_erase)) + dev_err(&spi->dev, "can't create erase interface\n"); + } + + spi_set_drvdata(spi, edev); + return 0; +} + +static void eeprom_93xx46_remove(struct spi_device *spi) +{ + struct eeprom_93xx46_dev *edev = spi_get_drvdata(spi); + + if (!(edev->pdata->flags & EE_READONLY)) + device_remove_file(&spi->dev, &dev_attr_erase); +} + +static struct spi_driver eeprom_93xx46_driver = { + .driver = { + .name = "93xx46", + .of_match_table = eeprom_93xx46_of_table, + }, + .probe = eeprom_93xx46_probe, + .remove = eeprom_93xx46_remove, + .id_table = eeprom_93xx46_spi_ids, +}; + +module_spi_driver(eeprom_93xx46_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Driver for 93xx46 EEPROMs"); +MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>"); +MODULE_ALIAS("spi:93xx46"); diff --git a/drivers/nvmem/imx-iim.c b/drivers/nvmem/imx-iim.c new file mode 100644 index 000000000..8cfbe55a5 --- /dev/null +++ b/drivers/nvmem/imx-iim.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * i.MX IIM driver + * + * Copyright (c) 2017 Pengutronix, Michael Grzeschik <m.grzeschik@pengutronix.de> + * + * Based on the barebox iim driver, + * Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il>, + * Orex Computed Radiography + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clk.h> + +#define IIM_BANK_BASE(n) (0x800 + 0x400 * (n)) + +struct imx_iim_drvdata { + unsigned int nregs; +}; + +struct iim_priv { + void __iomem *base; + struct clk *clk; +}; + +static int imx_iim_read(void *context, unsigned int offset, + void *buf, size_t bytes) +{ + struct iim_priv *iim = context; + int i, ret; + u8 *buf8 = buf; + + ret = clk_prepare_enable(iim->clk); + if (ret) + return ret; + + for (i = offset; i < offset + bytes; i++) { + int bank = i >> 5; + int reg = i & 0x1f; + + *buf8++ = readl(iim->base + IIM_BANK_BASE(bank) + reg * 4); + } + + clk_disable_unprepare(iim->clk); + + return 0; +} + +static struct imx_iim_drvdata imx27_drvdata = { + .nregs = 2 * 32, +}; + +static struct imx_iim_drvdata imx25_imx31_imx35_drvdata = { + .nregs = 3 * 32, +}; + +static struct imx_iim_drvdata imx51_drvdata = { + .nregs = 4 * 32, +}; + +static struct imx_iim_drvdata imx53_drvdata = { + .nregs = 4 * 32 + 16, +}; + +static const struct of_device_id imx_iim_dt_ids[] = { + { + .compatible = "fsl,imx25-iim", + .data = &imx25_imx31_imx35_drvdata, + }, { + .compatible = "fsl,imx27-iim", + .data = &imx27_drvdata, + }, { + .compatible = "fsl,imx31-iim", + .data = &imx25_imx31_imx35_drvdata, + }, { + .compatible = "fsl,imx35-iim", + .data = &imx25_imx31_imx35_drvdata, + }, { + .compatible = "fsl,imx51-iim", + .data = &imx51_drvdata, + }, { + .compatible = "fsl,imx53-iim", + .data = &imx53_drvdata, + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, imx_iim_dt_ids); + +static int imx_iim_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iim_priv *iim; + struct nvmem_device *nvmem; + struct nvmem_config cfg = {}; + const struct imx_iim_drvdata *drvdata = NULL; + + iim = devm_kzalloc(dev, sizeof(*iim), GFP_KERNEL); + if (!iim) + return -ENOMEM; + + iim->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(iim->base)) + return PTR_ERR(iim->base); + + drvdata = of_device_get_match_data(&pdev->dev); + + iim->clk = devm_clk_get(dev, NULL); + if (IS_ERR(iim->clk)) + return PTR_ERR(iim->clk); + + cfg.name = "imx-iim"; + cfg.read_only = true; + cfg.word_size = 1; + cfg.stride = 1; + cfg.reg_read = imx_iim_read; + cfg.dev = dev; + cfg.size = drvdata->nregs; + cfg.priv = iim; + + nvmem = devm_nvmem_register(dev, &cfg); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static struct platform_driver imx_iim_driver = { + .probe = imx_iim_probe, + .driver = { + .name = "imx-iim", + .of_match_table = imx_iim_dt_ids, + }, +}; +module_platform_driver(imx_iim_driver); + +MODULE_AUTHOR("Michael Grzeschik <m.grzeschik@pengutronix.de>"); +MODULE_DESCRIPTION("i.MX IIM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/imx-ocotp-ele.c b/drivers/nvmem/imx-ocotp-ele.c new file mode 100644 index 000000000..a0d2985c6 --- /dev/null +++ b/drivers/nvmem/imx-ocotp-ele.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * i.MX9 OCOTP fusebox driver + * + * Copyright 2023 NXP + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/if_ether.h> /* ETH_ALEN */ + +enum fuse_type { + FUSE_FSB = BIT(0), + FUSE_ELE = BIT(1), + FUSE_ECC = BIT(2), + FUSE_INVALID = -1 +}; + +struct ocotp_map_entry { + u32 start; /* start word */ + u32 num; /* num words */ + enum fuse_type type; +}; + +struct ocotp_devtype_data { + u32 reg_off; + char *name; + u32 size; + u32 num_entry; + u32 flag; + nvmem_reg_read_t reg_read; + struct ocotp_map_entry entry[]; +}; + +struct imx_ocotp_priv { + struct device *dev; + void __iomem *base; + struct nvmem_config config; + struct mutex lock; + const struct ocotp_devtype_data *data; +}; + +static enum fuse_type imx_ocotp_fuse_type(void *context, u32 index) +{ + struct imx_ocotp_priv *priv = context; + const struct ocotp_devtype_data *data = priv->data; + u32 start, end; + int i; + + for (i = 0; i < data->num_entry; i++) { + start = data->entry[i].start; + end = data->entry[i].start + data->entry[i].num; + + if (index >= start && index < end) + return data->entry[i].type; + } + + return FUSE_INVALID; +} + +static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, size_t bytes) +{ + struct imx_ocotp_priv *priv = context; + void __iomem *reg = priv->base + priv->data->reg_off; + u32 count, index, num_bytes; + enum fuse_type type; + u32 *buf; + void *p; + int i; + u8 skipbytes; + + if (offset + bytes > priv->data->size) + bytes = priv->data->size - offset; + + index = offset >> 2; + skipbytes = offset - (index << 2); + num_bytes = round_up(bytes + skipbytes, 4); + count = num_bytes >> 2; + + p = kzalloc(num_bytes, GFP_KERNEL); + if (!p) + return -ENOMEM; + + mutex_lock(&priv->lock); + + buf = p; + + for (i = index; i < (index + count); i++) { + type = imx_ocotp_fuse_type(context, i); + if (type == FUSE_INVALID || type == FUSE_ELE) { + *buf++ = 0; + continue; + } + + if (type & FUSE_ECC) + *buf++ = readl_relaxed(reg + (i << 2)) & GENMASK(15, 0); + else + *buf++ = readl_relaxed(reg + (i << 2)); + } + + memcpy(val, ((u8 *)p) + skipbytes, bytes); + + mutex_unlock(&priv->lock); + + kfree(p); + + return 0; +}; + +static int imx_ocotp_cell_pp(void *context, const char *id, int index, + unsigned int offset, void *data, size_t bytes) +{ + u8 *buf = data; + int i; + + /* Deal with some post processing of nvmem cell data */ + if (id && !strcmp(id, "mac-address")) { + bytes = min(bytes, ETH_ALEN); + for (i = 0; i < bytes / 2; i++) + swap(buf[i], buf[bytes - i - 1]); + } + + return 0; +} + +static void imx_ocotp_fixup_dt_cell_info(struct nvmem_device *nvmem, + struct nvmem_cell_info *cell) +{ + cell->raw_len = round_up(cell->bytes, 4); + cell->read_post_process = imx_ocotp_cell_pp; +} + +static int imx_ele_ocotp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imx_ocotp_priv *priv; + struct nvmem_device *nvmem; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->data = of_device_get_match_data(dev); + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->config.dev = dev; + priv->config.name = "ELE-OCOTP"; + priv->config.id = NVMEM_DEVID_AUTO; + priv->config.owner = THIS_MODULE; + priv->config.size = priv->data->size; + priv->config.reg_read = priv->data->reg_read; + priv->config.word_size = 1; + priv->config.stride = 1; + priv->config.priv = priv; + priv->config.read_only = true; + priv->config.add_legacy_fixed_of_cells = true; + priv->config.fixup_dt_cell_info = imx_ocotp_fixup_dt_cell_info; + mutex_init(&priv->lock); + + nvmem = devm_nvmem_register(dev, &priv->config); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + return 0; +} + +static const struct ocotp_devtype_data imx93_ocotp_data = { + .reg_off = 0x8000, + .reg_read = imx_ocotp_reg_read, + .size = 2048, + .num_entry = 6, + .entry = { + { 0, 52, FUSE_FSB }, + { 63, 1, FUSE_ELE}, + { 128, 16, FUSE_ELE }, + { 182, 1, FUSE_ELE }, + { 188, 1, FUSE_ELE }, + { 312, 200, FUSE_FSB } + }, +}; + +static const struct ocotp_devtype_data imx94_ocotp_data = { + .reg_off = 0x8000, + .reg_read = imx_ocotp_reg_read, + .size = 3296, /* 103 Banks */ + .num_entry = 10, + .entry = { + { 0, 1, FUSE_FSB | FUSE_ECC }, + { 7, 1, FUSE_FSB | FUSE_ECC }, + { 9, 3, FUSE_FSB | FUSE_ECC }, + { 12, 24, FUSE_FSB }, + { 36, 2, FUSE_FSB | FUSE_ECC }, + { 38, 14, FUSE_FSB }, + { 59, 1, FUSE_ELE }, + { 525, 2, FUSE_FSB | FUSE_ECC }, + { 528, 7, FUSE_FSB }, + { 536, 280, FUSE_FSB }, + }, +}; + +static const struct ocotp_devtype_data imx95_ocotp_data = { + .reg_off = 0x8000, + .reg_read = imx_ocotp_reg_read, + .size = 2048, + .num_entry = 12, + .entry = { + { 0, 1, FUSE_FSB | FUSE_ECC }, + { 7, 1, FUSE_FSB | FUSE_ECC }, + { 9, 3, FUSE_FSB | FUSE_ECC }, + { 12, 24, FUSE_FSB }, + { 36, 2, FUSE_FSB | FUSE_ECC }, + { 38, 14, FUSE_FSB }, + { 63, 1, FUSE_ELE }, + { 128, 16, FUSE_ELE }, + { 188, 1, FUSE_ELE }, + { 317, 2, FUSE_FSB | FUSE_ECC }, + { 320, 7, FUSE_FSB }, + { 328, 184, FUSE_FSB } + }, +}; + +static const struct of_device_id imx_ele_ocotp_dt_ids[] = { + { .compatible = "fsl,imx93-ocotp", .data = &imx93_ocotp_data, }, + { .compatible = "fsl,imx94-ocotp", .data = &imx94_ocotp_data, }, + { .compatible = "fsl,imx95-ocotp", .data = &imx95_ocotp_data, }, + {}, +}; +MODULE_DEVICE_TABLE(of, imx_ele_ocotp_dt_ids); + +static struct platform_driver imx_ele_ocotp_driver = { + .driver = { + .name = "imx_ele_ocotp", + .of_match_table = imx_ele_ocotp_dt_ids, + }, + .probe = imx_ele_ocotp_probe, +}; +module_platform_driver(imx_ele_ocotp_driver); + +MODULE_DESCRIPTION("i.MX OCOTP/ELE driver"); +MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/imx-ocotp-scu.c b/drivers/nvmem/imx-ocotp-scu.c new file mode 100644 index 000000000..517d83e11 --- /dev/null +++ b/drivers/nvmem/imx-ocotp-scu.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * i.MX8 OCOTP fusebox driver + * + * Copyright 2019 NXP + * + * Peng Fan <peng.fan@nxp.com> + */ + +#include <linux/arm-smccc.h> +#include <linux/firmware/imx/sci.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define IMX_SIP_OTP_WRITE 0xc200000B + +enum ocotp_devtype { + IMX8QXP, + IMX8QM, +}; + +#define ECC_REGION BIT(0) +#define HOLE_REGION BIT(1) + +struct ocotp_region { + u32 start; + u32 end; + u32 flag; +}; + +struct ocotp_devtype_data { + int devtype; + int nregs; + u32 num_region; + struct ocotp_region region[]; +}; + +struct ocotp_priv { + struct device *dev; + const struct ocotp_devtype_data *data; + struct imx_sc_ipc *nvmem_ipc; +}; + +struct imx_sc_msg_misc_fuse_read { + struct imx_sc_rpc_msg hdr; + u32 word; +} __packed; + +static DEFINE_MUTEX(scu_ocotp_mutex); + +static struct ocotp_devtype_data imx8qxp_data = { + .devtype = IMX8QXP, + .nregs = 800, + .num_region = 3, + .region = { + {0x10, 0x10f, ECC_REGION}, + {0x110, 0x21F, HOLE_REGION}, + {0x220, 0x31F, ECC_REGION}, + }, +}; + +static struct ocotp_devtype_data imx8qm_data = { + .devtype = IMX8QM, + .nregs = 800, + .num_region = 2, + .region = { + {0x10, 0x10f, ECC_REGION}, + {0x1a0, 0x1ff, ECC_REGION}, + }, +}; + +static bool in_hole(void *context, u32 index) +{ + struct ocotp_priv *priv = context; + const struct ocotp_devtype_data *data = priv->data; + int i; + + for (i = 0; i < data->num_region; i++) { + if (data->region[i].flag & HOLE_REGION) { + if ((index >= data->region[i].start) && + (index <= data->region[i].end)) + return true; + } + } + + return false; +} + +static bool in_ecc(void *context, u32 index) +{ + struct ocotp_priv *priv = context; + const struct ocotp_devtype_data *data = priv->data; + int i; + + for (i = 0; i < data->num_region; i++) { + if (data->region[i].flag & ECC_REGION) { + if ((index >= data->region[i].start) && + (index <= data->region[i].end)) + return true; + } + } + + return false; +} + +static int imx_sc_misc_otp_fuse_read(struct imx_sc_ipc *ipc, u32 word, + u32 *val) +{ + struct imx_sc_msg_misc_fuse_read msg; + struct imx_sc_rpc_msg *hdr = &msg.hdr; + int ret; + + hdr->ver = IMX_SC_RPC_VERSION; + hdr->svc = IMX_SC_RPC_SVC_MISC; + hdr->func = IMX_SC_MISC_FUNC_OTP_FUSE_READ; + hdr->size = 2; + + msg.word = word; + + ret = imx_scu_call_rpc(ipc, &msg, true); + if (ret) + return ret; + + *val = msg.word; + + return 0; +} + +static int imx_scu_ocotp_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct ocotp_priv *priv = context; + u32 count, index, num_bytes; + u32 *buf; + void *p; + int i, ret; + + index = offset; + num_bytes = round_up(bytes, 4); + count = num_bytes >> 2; + + if (count > (priv->data->nregs - index)) + count = priv->data->nregs - index; + + p = kzalloc(num_bytes, GFP_KERNEL); + if (!p) + return -ENOMEM; + + mutex_lock(&scu_ocotp_mutex); + + buf = p; + + for (i = index; i < (index + count); i++) { + if (in_hole(context, i)) { + *buf++ = 0; + continue; + } + + ret = imx_sc_misc_otp_fuse_read(priv->nvmem_ipc, i, buf); + if (ret) { + mutex_unlock(&scu_ocotp_mutex); + kfree(p); + return ret; + } + buf++; + } + + memcpy(val, (u8 *)p, bytes); + + mutex_unlock(&scu_ocotp_mutex); + + kfree(p); + + return 0; +} + +static int imx_scu_ocotp_write(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct ocotp_priv *priv = context; + struct arm_smccc_res res; + u32 *buf = val; + u32 tmp; + u32 index; + int ret; + + /* allow only writing one complete OTP word at a time */ + if (bytes != 4) + return -EINVAL; + + index = offset; + + if (in_hole(context, index)) + return -EINVAL; + + if (in_ecc(context, index)) { + pr_warn("ECC region, only program once\n"); + mutex_lock(&scu_ocotp_mutex); + ret = imx_sc_misc_otp_fuse_read(priv->nvmem_ipc, index, &tmp); + mutex_unlock(&scu_ocotp_mutex); + if (ret) + return ret; + if (tmp) { + pr_warn("ECC region, already has value: %x\n", tmp); + return -EIO; + } + } + + mutex_lock(&scu_ocotp_mutex); + + arm_smccc_smc(IMX_SIP_OTP_WRITE, index, *buf, 0, 0, 0, 0, 0, &res); + + mutex_unlock(&scu_ocotp_mutex); + + return res.a0; +} + +static struct nvmem_config imx_scu_ocotp_nvmem_config = { + .name = "imx-scu-ocotp", + .add_legacy_fixed_of_cells = true, + .read_only = false, + .word_size = 4, + .stride = 1, + .owner = THIS_MODULE, + .reg_read = imx_scu_ocotp_read, + .reg_write = imx_scu_ocotp_write, +}; + +static const struct of_device_id imx_scu_ocotp_dt_ids[] = { + { .compatible = "fsl,imx8qxp-scu-ocotp", (void *)&imx8qxp_data }, + { .compatible = "fsl,imx8qm-scu-ocotp", (void *)&imx8qm_data }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_scu_ocotp_dt_ids); + +static int imx_scu_ocotp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ocotp_priv *priv; + struct nvmem_device *nvmem; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + ret = imx_scu_get_handle(&priv->nvmem_ipc); + if (ret) + return ret; + + priv->data = of_device_get_match_data(dev); + priv->dev = dev; + imx_scu_ocotp_nvmem_config.size = 4 * priv->data->nregs; + imx_scu_ocotp_nvmem_config.dev = dev; + imx_scu_ocotp_nvmem_config.priv = priv; + nvmem = devm_nvmem_register(dev, &imx_scu_ocotp_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static struct platform_driver imx_scu_ocotp_driver = { + .probe = imx_scu_ocotp_probe, + .driver = { + .name = "imx_scu_ocotp", + .of_match_table = imx_scu_ocotp_dt_ids, + }, +}; +module_platform_driver(imx_scu_ocotp_driver); + +MODULE_AUTHOR("Peng Fan <peng.fan@nxp.com>"); +MODULE_DESCRIPTION("i.MX8 SCU OCOTP fuse box driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c new file mode 100644 index 000000000..108d78d7f --- /dev/null +++ b/drivers/nvmem/imx-ocotp.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * i.MX6 OCOTP fusebox driver + * + * Copyright (c) 2015 Pengutronix, Philipp Zabel <p.zabel@pengutronix.de> + * + * Copyright 2019 NXP + * + * Based on the barebox ocotp driver, + * Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il>, + * Orex Computed Radiography + * + * Write support based on the fsl_otp driver, + * Copyright (C) 2010-2013 Freescale Semiconductor, Inc + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/if_ether.h> /* ETH_ALEN */ + +#define IMX_OCOTP_OFFSET_B0W0 0x400 /* Offset from base address of the + * OTP Bank0 Word0 + */ +#define IMX_OCOTP_OFFSET_PER_WORD 0x10 /* Offset between the start addr + * of two consecutive OTP words. + */ + +#define IMX_OCOTP_ADDR_CTRL 0x0000 +#define IMX_OCOTP_ADDR_CTRL_SET 0x0004 +#define IMX_OCOTP_ADDR_CTRL_CLR 0x0008 +#define IMX_OCOTP_ADDR_TIMING 0x0010 +#define IMX_OCOTP_ADDR_DATA0 0x0020 +#define IMX_OCOTP_ADDR_DATA1 0x0030 +#define IMX_OCOTP_ADDR_DATA2 0x0040 +#define IMX_OCOTP_ADDR_DATA3 0x0050 + +#define IMX_OCOTP_BM_CTRL_ADDR 0x000000FF +#define IMX_OCOTP_BM_CTRL_BUSY 0x00000100 +#define IMX_OCOTP_BM_CTRL_ERROR 0x00000200 +#define IMX_OCOTP_BM_CTRL_REL_SHADOWS 0x00000400 + +#define IMX_OCOTP_BM_CTRL_ADDR_8MP 0x000001FF +#define IMX_OCOTP_BM_CTRL_BUSY_8MP 0x00000200 +#define IMX_OCOTP_BM_CTRL_ERROR_8MP 0x00000400 +#define IMX_OCOTP_BM_CTRL_REL_SHADOWS_8MP 0x00000800 + +#define IMX_OCOTP_BM_CTRL_DEFAULT \ + { \ + .bm_addr = IMX_OCOTP_BM_CTRL_ADDR, \ + .bm_busy = IMX_OCOTP_BM_CTRL_BUSY, \ + .bm_error = IMX_OCOTP_BM_CTRL_ERROR, \ + .bm_rel_shadows = IMX_OCOTP_BM_CTRL_REL_SHADOWS,\ + } + +#define IMX_OCOTP_BM_CTRL_8MP \ + { \ + .bm_addr = IMX_OCOTP_BM_CTRL_ADDR_8MP, \ + .bm_busy = IMX_OCOTP_BM_CTRL_BUSY_8MP, \ + .bm_error = IMX_OCOTP_BM_CTRL_ERROR_8MP, \ + .bm_rel_shadows = IMX_OCOTP_BM_CTRL_REL_SHADOWS_8MP,\ + } + +#define TIMING_STROBE_PROG_US 10 /* Min time to blow a fuse */ +#define TIMING_STROBE_READ_NS 37 /* Min time before read */ +#define TIMING_RELAX_NS 17 +#define DEF_FSOURCE 1001 /* > 1000 ns */ +#define DEF_STROBE_PROG 10000 /* IPG clocks */ +#define IMX_OCOTP_WR_UNLOCK 0x3E770000 +#define IMX_OCOTP_READ_LOCKED_VAL 0xBADABADA + +static DEFINE_MUTEX(ocotp_mutex); + +struct ocotp_priv { + struct device *dev; + struct clk *clk; + void __iomem *base; + const struct ocotp_params *params; + struct nvmem_config *config; +}; + +struct ocotp_ctrl_reg { + u32 bm_addr; + u32 bm_busy; + u32 bm_error; + u32 bm_rel_shadows; +}; + +struct ocotp_params { + unsigned int nregs; + unsigned int bank_address_words; + void (*set_timing)(struct ocotp_priv *priv); + struct ocotp_ctrl_reg ctrl; +}; + +static int imx_ocotp_wait_for_busy(struct ocotp_priv *priv, u32 flags) +{ + int count; + u32 c, mask; + u32 bm_ctrl_busy, bm_ctrl_error; + void __iomem *base = priv->base; + + bm_ctrl_busy = priv->params->ctrl.bm_busy; + bm_ctrl_error = priv->params->ctrl.bm_error; + + mask = bm_ctrl_busy | bm_ctrl_error | flags; + + for (count = 10000; count >= 0; count--) { + c = readl(base + IMX_OCOTP_ADDR_CTRL); + if (!(c & mask)) + break; + cpu_relax(); + } + + if (count < 0) { + /* HW_OCOTP_CTRL[ERROR] will be set under the following + * conditions: + * - A write is performed to a shadow register during a shadow + * reload (essentially, while HW_OCOTP_CTRL[RELOAD_SHADOWS] is + * set. In addition, the contents of the shadow register shall + * not be updated. + * - A write is performed to a shadow register which has been + * locked. + * - A read is performed to from a shadow register which has + * been read locked. + * - A program is performed to a fuse word which has been locked + * - A read is performed to from a fuse word which has been read + * locked. + */ + if (c & bm_ctrl_error) + return -EPERM; + return -ETIMEDOUT; + } + + return 0; +} + +static void imx_ocotp_clr_err_if_set(struct ocotp_priv *priv) +{ + u32 c, bm_ctrl_error; + void __iomem *base = priv->base; + + bm_ctrl_error = priv->params->ctrl.bm_error; + + c = readl(base + IMX_OCOTP_ADDR_CTRL); + if (!(c & bm_ctrl_error)) + return; + + writel(bm_ctrl_error, base + IMX_OCOTP_ADDR_CTRL_CLR); +} + +static int imx_ocotp_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct ocotp_priv *priv = context; + unsigned int count; + u8 *buf, *p; + int i, ret; + u32 index, num_bytes; + + index = offset >> 2; + num_bytes = round_up((offset % 4) + bytes, 4); + count = num_bytes >> 2; + + if (count > (priv->params->nregs - index)) + count = priv->params->nregs - index; + + p = kzalloc(num_bytes, GFP_KERNEL); + if (!p) + return -ENOMEM; + + mutex_lock(&ocotp_mutex); + + buf = p; + + ret = clk_prepare_enable(priv->clk); + if (ret < 0) { + mutex_unlock(&ocotp_mutex); + dev_err(priv->dev, "failed to prepare/enable ocotp clk\n"); + kfree(p); + return ret; + } + + ret = imx_ocotp_wait_for_busy(priv, 0); + if (ret < 0) { + dev_err(priv->dev, "timeout during read setup\n"); + goto read_end; + } + + for (i = index; i < (index + count); i++) { + *(u32 *)buf = readl(priv->base + IMX_OCOTP_OFFSET_B0W0 + + i * IMX_OCOTP_OFFSET_PER_WORD); + + /* 47.3.1.2 + * For "read locked" registers 0xBADABADA will be returned and + * HW_OCOTP_CTRL[ERROR] will be set. It must be cleared by + * software before any new write, read or reload access can be + * issued + */ + if (*((u32 *)buf) == IMX_OCOTP_READ_LOCKED_VAL) + imx_ocotp_clr_err_if_set(priv); + + buf += 4; + } + + index = offset % 4; + memcpy(val, &p[index], bytes); + +read_end: + clk_disable_unprepare(priv->clk); + mutex_unlock(&ocotp_mutex); + + kfree(p); + + return ret; +} + +static int imx_ocotp_cell_pp(void *context, const char *id, int index, + unsigned int offset, void *data, size_t bytes) +{ + u8 *buf = data; + int i; + + /* Deal with some post processing of nvmem cell data */ + if (id && !strcmp(id, "mac-address")) { + bytes = min(bytes, ETH_ALEN); + for (i = 0; i < bytes / 2; i++) + swap(buf[i], buf[bytes - i - 1]); + } + + return 0; +} + +static void imx_ocotp_set_imx6_timing(struct ocotp_priv *priv) +{ + unsigned long clk_rate; + unsigned long strobe_read, relax, strobe_prog; + u32 timing; + + /* 47.3.1.3.1 + * Program HW_OCOTP_TIMING[STROBE_PROG] and HW_OCOTP_TIMING[RELAX] + * fields with timing values to match the current frequency of the + * ipg_clk. OTP writes will work at maximum bus frequencies as long + * as the HW_OCOTP_TIMING parameters are set correctly. + * + * Note: there are minimum timings required to ensure an OTP fuse burns + * correctly that are independent of the ipg_clk. Those values are not + * formally documented anywhere however, working from the minimum + * timings given in u-boot we can say: + * + * - Minimum STROBE_PROG time is 10 microseconds. Intuitively 10 + * microseconds feels about right as representative of a minimum time + * to physically burn out a fuse. + * + * - Minimum STROBE_READ i.e. the time to wait post OTP fuse burn before + * performing another read is 37 nanoseconds + * + * - Minimum RELAX timing is 17 nanoseconds. This final RELAX minimum + * timing is not entirely clear the documentation says "This + * count value specifies the time to add to all default timing + * parameters other than the Tpgm and Trd. It is given in number + * of ipg_clk periods." where Tpgm and Trd refer to STROBE_PROG + * and STROBE_READ respectively. What the other timing parameters + * are though, is not specified. Experience shows a zero RELAX + * value will mess up a re-load of the shadow registers post OTP + * burn. + */ + clk_rate = clk_get_rate(priv->clk); + + relax = DIV_ROUND_UP(clk_rate * TIMING_RELAX_NS, 1000000000) - 1; + strobe_read = DIV_ROUND_UP(clk_rate * TIMING_STROBE_READ_NS, + 1000000000); + strobe_read += 2 * (relax + 1) - 1; + strobe_prog = DIV_ROUND_CLOSEST(clk_rate * TIMING_STROBE_PROG_US, + 1000000); + strobe_prog += 2 * (relax + 1) - 1; + + timing = readl(priv->base + IMX_OCOTP_ADDR_TIMING) & 0x0FC00000; + timing |= strobe_prog & 0x00000FFF; + timing |= (relax << 12) & 0x0000F000; + timing |= (strobe_read << 16) & 0x003F0000; + + writel(timing, priv->base + IMX_OCOTP_ADDR_TIMING); +} + +static void imx_ocotp_set_imx7_timing(struct ocotp_priv *priv) +{ + unsigned long clk_rate; + u64 fsource, strobe_prog; + u32 timing; + + /* i.MX 7Solo Applications Processor Reference Manual, Rev. 0.1 + * 6.4.3.3 + */ + clk_rate = clk_get_rate(priv->clk); + fsource = DIV_ROUND_UP_ULL((u64)clk_rate * DEF_FSOURCE, + NSEC_PER_SEC) + 1; + strobe_prog = DIV_ROUND_CLOSEST_ULL((u64)clk_rate * DEF_STROBE_PROG, + NSEC_PER_SEC) + 1; + + timing = strobe_prog & 0x00000FFF; + timing |= (fsource << 12) & 0x000FF000; + + writel(timing, priv->base + IMX_OCOTP_ADDR_TIMING); +} + +static int imx_ocotp_write(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct ocotp_priv *priv = context; + u32 *buf = val; + int ret; + + u32 ctrl; + u8 waddr; + u8 word = 0; + + /* allow only writing one complete OTP word at a time */ + if ((bytes != priv->config->word_size) || + (offset % priv->config->word_size)) + return -EINVAL; + + mutex_lock(&ocotp_mutex); + + ret = clk_prepare_enable(priv->clk); + if (ret < 0) { + mutex_unlock(&ocotp_mutex); + dev_err(priv->dev, "failed to prepare/enable ocotp clk\n"); + return ret; + } + + /* Setup the write timing values */ + priv->params->set_timing(priv); + + /* 47.3.1.3.2 + * Check that HW_OCOTP_CTRL[BUSY] and HW_OCOTP_CTRL[ERROR] are clear. + * Overlapped accesses are not supported by the controller. Any pending + * write or reload must be completed before a write access can be + * requested. + */ + ret = imx_ocotp_wait_for_busy(priv, 0); + if (ret < 0) { + dev_err(priv->dev, "timeout during timing setup\n"); + goto write_end; + } + + /* 47.3.1.3.3 + * Write the requested address to HW_OCOTP_CTRL[ADDR] and program the + * unlock code into HW_OCOTP_CTRL[WR_UNLOCK]. This must be programmed + * for each write access. The lock code is documented in the register + * description. Both the unlock code and address can be written in the + * same operation. + */ + if (priv->params->bank_address_words != 0) { + /* + * In banked/i.MX7 mode the OTP register bank goes into waddr + * see i.MX 7Solo Applications Processor Reference Manual, Rev. + * 0.1 section 6.4.3.1 + */ + offset = offset / priv->config->word_size; + waddr = offset / priv->params->bank_address_words; + word = offset & (priv->params->bank_address_words - 1); + } else { + /* + * Non-banked i.MX6 mode. + * OTP write/read address specifies one of 128 word address + * locations + */ + waddr = offset / 4; + } + + ctrl = readl(priv->base + IMX_OCOTP_ADDR_CTRL); + ctrl &= ~priv->params->ctrl.bm_addr; + ctrl |= waddr & priv->params->ctrl.bm_addr; + ctrl |= IMX_OCOTP_WR_UNLOCK; + + writel(ctrl, priv->base + IMX_OCOTP_ADDR_CTRL); + + /* 47.3.1.3.4 + * Write the data to the HW_OCOTP_DATA register. This will automatically + * set HW_OCOTP_CTRL[BUSY] and clear HW_OCOTP_CTRL[WR_UNLOCK]. To + * protect programming same OTP bit twice, before program OCOTP will + * automatically read fuse value in OTP and use read value to mask + * program data. The controller will use masked program data to program + * a 32-bit word in the OTP per the address in HW_OCOTP_CTRL[ADDR]. Bit + * fields with 1's will result in that OTP bit being programmed. Bit + * fields with 0's will be ignored. At the same time that the write is + * accepted, the controller makes an internal copy of + * HW_OCOTP_CTRL[ADDR] which cannot be updated until the next write + * sequence is initiated. This copy guarantees that erroneous writes to + * HW_OCOTP_CTRL[ADDR] will not affect an active write operation. It + * should also be noted that during the programming HW_OCOTP_DATA will + * shift right (with zero fill). This shifting is required to program + * the OTP serially. During the write operation, HW_OCOTP_DATA cannot be + * modified. + * Note: on i.MX7 there are four data fields to write for banked write + * with the fuse blowing operation only taking place after data0 + * has been written. This is why data0 must always be the last + * register written. + */ + if (priv->params->bank_address_words != 0) { + /* Banked/i.MX7 mode */ + switch (word) { + case 0: + writel(0, priv->base + IMX_OCOTP_ADDR_DATA1); + writel(0, priv->base + IMX_OCOTP_ADDR_DATA2); + writel(0, priv->base + IMX_OCOTP_ADDR_DATA3); + writel(*buf, priv->base + IMX_OCOTP_ADDR_DATA0); + break; + case 1: + writel(*buf, priv->base + IMX_OCOTP_ADDR_DATA1); + writel(0, priv->base + IMX_OCOTP_ADDR_DATA2); + writel(0, priv->base + IMX_OCOTP_ADDR_DATA3); + writel(0, priv->base + IMX_OCOTP_ADDR_DATA0); + break; + case 2: + writel(0, priv->base + IMX_OCOTP_ADDR_DATA1); + writel(*buf, priv->base + IMX_OCOTP_ADDR_DATA2); + writel(0, priv->base + IMX_OCOTP_ADDR_DATA3); + writel(0, priv->base + IMX_OCOTP_ADDR_DATA0); + break; + case 3: + writel(0, priv->base + IMX_OCOTP_ADDR_DATA1); + writel(0, priv->base + IMX_OCOTP_ADDR_DATA2); + writel(*buf, priv->base + IMX_OCOTP_ADDR_DATA3); + writel(0, priv->base + IMX_OCOTP_ADDR_DATA0); + break; + } + } else { + /* Non-banked i.MX6 mode */ + writel(*buf, priv->base + IMX_OCOTP_ADDR_DATA0); + } + + /* 47.4.1.4.5 + * Once complete, the controller will clear BUSY. A write request to a + * protected or locked region will result in no OTP access and no + * setting of HW_OCOTP_CTRL[BUSY]. In addition HW_OCOTP_CTRL[ERROR] will + * be set. It must be cleared by software before any new write access + * can be issued. + */ + ret = imx_ocotp_wait_for_busy(priv, 0); + if (ret < 0) { + if (ret == -EPERM) { + dev_err(priv->dev, "failed write to locked region"); + imx_ocotp_clr_err_if_set(priv); + } else { + dev_err(priv->dev, "timeout during data write\n"); + } + goto write_end; + } + + /* 47.3.1.4 + * Write Postamble: Due to internal electrical characteristics of the + * OTP during writes, all OTP operations following a write must be + * separated by 2 us after the clearing of HW_OCOTP_CTRL_BUSY following + * the write. + */ + udelay(2); + + /* reload all shadow registers */ + writel(priv->params->ctrl.bm_rel_shadows, + priv->base + IMX_OCOTP_ADDR_CTRL_SET); + ret = imx_ocotp_wait_for_busy(priv, + priv->params->ctrl.bm_rel_shadows); + if (ret < 0) + dev_err(priv->dev, "timeout during shadow register reload\n"); + +write_end: + clk_disable_unprepare(priv->clk); + mutex_unlock(&ocotp_mutex); + return ret < 0 ? ret : bytes; +} + +static struct nvmem_config imx_ocotp_nvmem_config = { + .name = "imx-ocotp", + .read_only = false, + .word_size = 4, + .stride = 1, + .reg_read = imx_ocotp_read, + .reg_write = imx_ocotp_write, +}; + +static const struct ocotp_params imx6q_params = { + .nregs = 128, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, +}; + +static const struct ocotp_params imx6sl_params = { + .nregs = 64, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, +}; + +static const struct ocotp_params imx6sll_params = { + .nregs = 80, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, +}; + +static const struct ocotp_params imx6sx_params = { + .nregs = 128, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, +}; + +static const struct ocotp_params imx6ul_params = { + .nregs = 144, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, +}; + +static const struct ocotp_params imx6ull_params = { + .nregs = 80, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, +}; + +static const struct ocotp_params imx7d_params = { + .nregs = 64, + .bank_address_words = 4, + .set_timing = imx_ocotp_set_imx7_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, +}; + +static const struct ocotp_params imx7ulp_params = { + .nregs = 256, + .bank_address_words = 0, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, +}; + +static const struct ocotp_params imx8mq_params = { + .nregs = 256, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, +}; + +static const struct ocotp_params imx8mm_params = { + .nregs = 256, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, +}; + +static const struct ocotp_params imx8mn_params = { + .nregs = 256, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_DEFAULT, +}; + +static const struct ocotp_params imx8mp_params = { + .nregs = 384, + .bank_address_words = 0, + .set_timing = imx_ocotp_set_imx6_timing, + .ctrl = IMX_OCOTP_BM_CTRL_8MP, +}; + +static const struct of_device_id imx_ocotp_dt_ids[] = { + { .compatible = "fsl,imx6q-ocotp", .data = &imx6q_params }, + { .compatible = "fsl,imx6sl-ocotp", .data = &imx6sl_params }, + { .compatible = "fsl,imx6sx-ocotp", .data = &imx6sx_params }, + { .compatible = "fsl,imx6ul-ocotp", .data = &imx6ul_params }, + { .compatible = "fsl,imx6ull-ocotp", .data = &imx6ull_params }, + { .compatible = "fsl,imx7d-ocotp", .data = &imx7d_params }, + { .compatible = "fsl,imx6sll-ocotp", .data = &imx6sll_params }, + { .compatible = "fsl,imx7ulp-ocotp", .data = &imx7ulp_params }, + { .compatible = "fsl,imx8mq-ocotp", .data = &imx8mq_params }, + { .compatible = "fsl,imx8mm-ocotp", .data = &imx8mm_params }, + { .compatible = "fsl,imx8mn-ocotp", .data = &imx8mn_params }, + { .compatible = "fsl,imx8mp-ocotp", .data = &imx8mp_params }, + { }, +}; +MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids); + +static void imx_ocotp_fixup_dt_cell_info(struct nvmem_device *nvmem, + struct nvmem_cell_info *cell) +{ + cell->raw_len = round_up(cell->bytes, 4); + cell->read_post_process = imx_ocotp_cell_pp; +} + +static int imx_ocotp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ocotp_priv *priv; + struct nvmem_device *nvmem; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->clk = devm_clk_get(dev, NULL); + if (IS_ERR(priv->clk)) + return PTR_ERR(priv->clk); + + priv->params = of_device_get_match_data(&pdev->dev); + imx_ocotp_nvmem_config.add_legacy_fixed_of_cells = true; + imx_ocotp_nvmem_config.size = 4 * priv->params->nregs; + imx_ocotp_nvmem_config.dev = dev; + imx_ocotp_nvmem_config.priv = priv; + imx_ocotp_nvmem_config.fixup_dt_cell_info = &imx_ocotp_fixup_dt_cell_info; + + priv->config = &imx_ocotp_nvmem_config; + + clk_prepare_enable(priv->clk); + imx_ocotp_clr_err_if_set(priv); + clk_disable_unprepare(priv->clk); + + nvmem = devm_nvmem_register(dev, &imx_ocotp_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static struct platform_driver imx_ocotp_driver = { + .probe = imx_ocotp_probe, + .driver = { + .name = "imx_ocotp", + .of_match_table = imx_ocotp_dt_ids, + }, +}; +module_platform_driver(imx_ocotp_driver); + +MODULE_AUTHOR("Philipp Zabel <p.zabel@pengutronix.de>"); +MODULE_DESCRIPTION("i.MX6/i.MX7 OCOTP fuse box driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h new file mode 100644 index 000000000..4e610deea --- /dev/null +++ b/drivers/nvmem/internals.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _LINUX_NVMEM_INTERNALS_H +#define _LINUX_NVMEM_INTERNALS_H + +#include <linux/device.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> + +/* Hold pointers to callbacks owned by the nvmem provider module. */ +struct nvmem_operations { + nvmem_reg_read_t reg_read; + nvmem_reg_write_t reg_write; +}; + +struct nvmem_device { + struct module *owner; + struct device dev; + int stride; + int word_size; + int id; + struct kref refcnt; + size_t size; + bool read_only; + bool root_only; + int flags; + enum nvmem_type type; + struct bin_attribute eeprom; + struct device *base_dev; + struct list_head cells; + void (*fixup_dt_cell_info)(struct nvmem_device *nvmem, + struct nvmem_cell_info *cell); + const struct nvmem_keepout *keepout; + unsigned int nkeepout; + struct gpio_desc *wp_gpio; + struct nvmem_layout *layout; + struct nvmem_operations *ops; + void *priv; + bool sysfs_cells_populated; +}; + +int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np); + +#if IS_ENABLED(CONFIG_OF) +int nvmem_layout_bus_register(void); +void nvmem_layout_bus_unregister(void); +int nvmem_populate_layout(struct nvmem_device *nvmem); +void nvmem_destroy_layout(struct nvmem_device *nvmem); +#else /* CONFIG_OF */ +static inline int nvmem_layout_bus_register(void) +{ + return 0; +} + +static inline void nvmem_layout_bus_unregister(void) {} + +static inline int nvmem_populate_layout(struct nvmem_device *nvmem) +{ + return 0; +} + +static inline void nvmem_destroy_layout(struct nvmem_device *nvmem) { } +#endif /* CONFIG_OF */ + +#endif /* ifndef _LINUX_NVMEM_INTERNALS_H */ diff --git a/drivers/nvmem/jz4780-efuse.c b/drivers/nvmem/jz4780-efuse.c new file mode 100644 index 000000000..0b01b840e --- /dev/null +++ b/drivers/nvmem/jz4780-efuse.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * JZ4780 EFUSE Memory Support driver + * + * Copyright (c) 2017 PrasannaKumar Muralidharan <prasannatsmkumar@gmail.com> + * Copyright (c) 2020 H. Nikolaus Schaller <hns@goldelico.com> + */ + +/* + * Currently supports JZ4780 efuse which has 8K programmable bit. + * Efuse is separated into seven segments as below: + * + * ----------------------------------------------------------------------- + * | 64 bit | 128 bit | 128 bit | 3520 bit | 8 bit | 2296 bit | 2048 bit | + * ----------------------------------------------------------------------- + * + * The rom itself is accessed using a 9 bit address line and an 8 word wide bus + * which reads/writes based on strobes. The strobe is configured in the config + * register and is based on number of cycles of the bus clock. + * + * Driver supports read only as the writes are done in the Factory. + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/timer.h> + +#define JZ_EFUCTRL (0x0) /* Control Register */ +#define JZ_EFUCFG (0x4) /* Configure Register*/ +#define JZ_EFUSTATE (0x8) /* Status Register */ +#define JZ_EFUDATA(n) (0xC + (n) * 4) + +/* We read 32 byte chunks to avoid complexity in the driver. */ +#define JZ_EFU_READ_SIZE 32 + +#define EFUCTRL_ADDR_MASK 0x3FF +#define EFUCTRL_ADDR_SHIFT 21 +#define EFUCTRL_LEN_MASK 0x1F +#define EFUCTRL_LEN_SHIFT 16 +#define EFUCTRL_PG_EN BIT(15) +#define EFUCTRL_WR_EN BIT(1) +#define EFUCTRL_RD_EN BIT(0) + +#define EFUCFG_INT_EN BIT(31) +#define EFUCFG_RD_ADJ_MASK 0xF +#define EFUCFG_RD_ADJ_SHIFT 20 +#define EFUCFG_RD_STR_MASK 0xF +#define EFUCFG_RD_STR_SHIFT 16 +#define EFUCFG_WR_ADJ_MASK 0xF +#define EFUCFG_WR_ADJ_SHIFT 12 +#define EFUCFG_WR_STR_MASK 0xFFF +#define EFUCFG_WR_STR_SHIFT 0 + +#define EFUSTATE_WR_DONE BIT(1) +#define EFUSTATE_RD_DONE BIT(0) + +struct jz4780_efuse { + struct device *dev; + struct regmap *map; + struct clk *clk; +}; + +/* main entry point */ +static int jz4780_efuse_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct jz4780_efuse *efuse = context; + + while (bytes > 0) { + size_t start = offset & ~(JZ_EFU_READ_SIZE - 1); + size_t chunk = min(bytes, (start + JZ_EFU_READ_SIZE) + - offset); + char buf[JZ_EFU_READ_SIZE]; + unsigned int tmp; + u32 ctrl; + int ret; + + ctrl = (start << EFUCTRL_ADDR_SHIFT) + | ((JZ_EFU_READ_SIZE - 1) << EFUCTRL_LEN_SHIFT) + | EFUCTRL_RD_EN; + + regmap_update_bits(efuse->map, JZ_EFUCTRL, + (EFUCTRL_ADDR_MASK << EFUCTRL_ADDR_SHIFT) | + (EFUCTRL_LEN_MASK << EFUCTRL_LEN_SHIFT) | + EFUCTRL_PG_EN | EFUCTRL_WR_EN | + EFUCTRL_RD_EN, + ctrl); + + ret = regmap_read_poll_timeout(efuse->map, JZ_EFUSTATE, + tmp, tmp & EFUSTATE_RD_DONE, + 1 * MSEC_PER_SEC, + 50 * MSEC_PER_SEC); + if (ret < 0) { + dev_err(efuse->dev, "Time out while reading efuse data"); + return ret; + } + + ret = regmap_bulk_read(efuse->map, JZ_EFUDATA(0), + buf, JZ_EFU_READ_SIZE / sizeof(u32)); + if (ret < 0) + return ret; + + memcpy(val, &buf[offset - start], chunk); + + val += chunk; + offset += chunk; + bytes -= chunk; + } + + return 0; +} + +static struct nvmem_config jz4780_efuse_nvmem_config = { + .name = "jz4780-efuse", + .size = 1024, + .word_size = 1, + .stride = 1, + .owner = THIS_MODULE, + .reg_read = jz4780_efuse_read, +}; + +static const struct regmap_config jz4780_efuse_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = JZ_EFUDATA(7), +}; + +static void clk_disable_unprepare_helper(void *clock) +{ + clk_disable_unprepare(clock); +} + +static int jz4780_efuse_probe(struct platform_device *pdev) +{ + struct nvmem_device *nvmem; + struct jz4780_efuse *efuse; + struct nvmem_config cfg; + unsigned long clk_rate; + unsigned long rd_adj; + unsigned long rd_strobe; + struct device *dev = &pdev->dev; + void __iomem *regs; + int ret; + + efuse = devm_kzalloc(dev, sizeof(*efuse), GFP_KERNEL); + if (!efuse) + return -ENOMEM; + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + efuse->map = devm_regmap_init_mmio(dev, regs, + &jz4780_efuse_regmap_config); + if (IS_ERR(efuse->map)) + return PTR_ERR(efuse->map); + + efuse->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(efuse->clk)) + return PTR_ERR(efuse->clk); + + ret = clk_prepare_enable(efuse->clk); + if (ret < 0) + return ret; + + ret = devm_add_action_or_reset(&pdev->dev, + clk_disable_unprepare_helper, + efuse->clk); + if (ret < 0) + return ret; + + clk_rate = clk_get_rate(efuse->clk); + + efuse->dev = dev; + + /* + * rd_adj and rd_strobe are 4 bit values + * conditions: + * bus clk_period * (rd_adj + 1) > 6.5ns + * bus clk_period * (rd_adj + 5 + rd_strobe) > 35ns + * i.e. rd_adj >= 6.5ns / clk_period + * i.e. rd_strobe >= 35 ns / clk_period - 5 - rd_adj + 1 + * constants: + * 1 / 6.5ns == 153846154 Hz + * 1 / 35ns == 28571429 Hz + */ + + rd_adj = clk_rate / 153846154; + rd_strobe = clk_rate / 28571429 - 5 - rd_adj + 1; + + if (rd_adj > EFUCFG_RD_ADJ_MASK || + rd_strobe > EFUCFG_RD_STR_MASK) { + dev_err(&pdev->dev, "Cannot set clock configuration\n"); + return -EINVAL; + } + + regmap_update_bits(efuse->map, JZ_EFUCFG, + (EFUCFG_RD_ADJ_MASK << EFUCFG_RD_ADJ_SHIFT) | + (EFUCFG_RD_STR_MASK << EFUCFG_RD_STR_SHIFT), + (rd_adj << EFUCFG_RD_ADJ_SHIFT) | + (rd_strobe << EFUCFG_RD_STR_SHIFT)); + + cfg = jz4780_efuse_nvmem_config; + cfg.dev = &pdev->dev; + cfg.priv = efuse; + + nvmem = devm_nvmem_register(dev, &cfg); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id jz4780_efuse_match[] = { + { .compatible = "ingenic,jz4780-efuse" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, jz4780_efuse_match); + +static struct platform_driver jz4780_efuse_driver = { + .probe = jz4780_efuse_probe, + .driver = { + .name = "jz4780-efuse", + .of_match_table = jz4780_efuse_match, + }, +}; +module_platform_driver(jz4780_efuse_driver); + +MODULE_AUTHOR("PrasannaKumar Muralidharan <prasannatsmkumar@gmail.com>"); +MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>"); +MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); +MODULE_DESCRIPTION("Ingenic JZ4780 efuse driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/lan9662-otpc.c b/drivers/nvmem/lan9662-otpc.c new file mode 100644 index 000000000..62d1d6381 --- /dev/null +++ b/drivers/nvmem/lan9662-otpc.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#define OTP_OTP_PWR_DN(t) (t + 0x00) +#define OTP_OTP_PWR_DN_OTP_PWRDN_N BIT(0) +#define OTP_OTP_ADDR_HI(t) (t + 0x04) +#define OTP_OTP_ADDR_LO(t) (t + 0x08) +#define OTP_OTP_PRGM_DATA(t) (t + 0x10) +#define OTP_OTP_PRGM_MODE(t) (t + 0x14) +#define OTP_OTP_PRGM_MODE_OTP_PGM_MODE_BYTE BIT(0) +#define OTP_OTP_RD_DATA(t) (t + 0x18) +#define OTP_OTP_FUNC_CMD(t) (t + 0x20) +#define OTP_OTP_FUNC_CMD_OTP_PROGRAM BIT(1) +#define OTP_OTP_FUNC_CMD_OTP_READ BIT(0) +#define OTP_OTP_CMD_GO(t) (t + 0x28) +#define OTP_OTP_CMD_GO_OTP_GO BIT(0) +#define OTP_OTP_PASS_FAIL(t) (t + 0x2c) +#define OTP_OTP_PASS_FAIL_OTP_READ_PROHIBITED BIT(3) +#define OTP_OTP_PASS_FAIL_OTP_WRITE_PROHIBITED BIT(2) +#define OTP_OTP_PASS_FAIL_OTP_FAIL BIT(0) +#define OTP_OTP_STATUS(t) (t + 0x30) +#define OTP_OTP_STATUS_OTP_CPUMPEN BIT(1) +#define OTP_OTP_STATUS_OTP_BUSY BIT(0) + +#define OTP_SLEEP_US 10 +#define OTP_TIMEOUT_US 500000 + +struct lan9662_otp { + struct device *dev; + void __iomem *base; +}; + +static int lan9662_otp_wait_flag_clear(void __iomem *reg, u32 flag) +{ + u32 val; + + return readl_poll_timeout(reg, val, !(val & flag), + OTP_SLEEP_US, OTP_TIMEOUT_US); +} + +static int lan9662_otp_power(struct lan9662_otp *otp, bool up) +{ + void __iomem *pwrdn = OTP_OTP_PWR_DN(otp->base); + + if (up) { + writel(readl(pwrdn) & ~OTP_OTP_PWR_DN_OTP_PWRDN_N, pwrdn); + if (lan9662_otp_wait_flag_clear(OTP_OTP_STATUS(otp->base), + OTP_OTP_STATUS_OTP_CPUMPEN)) + return -ETIMEDOUT; + } else { + writel(readl(pwrdn) | OTP_OTP_PWR_DN_OTP_PWRDN_N, pwrdn); + } + + return 0; +} + +static int lan9662_otp_execute(struct lan9662_otp *otp) +{ + if (lan9662_otp_wait_flag_clear(OTP_OTP_CMD_GO(otp->base), + OTP_OTP_CMD_GO_OTP_GO)) + return -ETIMEDOUT; + + if (lan9662_otp_wait_flag_clear(OTP_OTP_STATUS(otp->base), + OTP_OTP_STATUS_OTP_BUSY)) + return -ETIMEDOUT; + + return 0; +} + +static void lan9662_otp_set_address(struct lan9662_otp *otp, u32 offset) +{ + writel(0xff & (offset >> 8), OTP_OTP_ADDR_HI(otp->base)); + writel(0xff & offset, OTP_OTP_ADDR_LO(otp->base)); +} + +static int lan9662_otp_read_byte(struct lan9662_otp *otp, u32 offset, u8 *dst) +{ + u32 pass; + int rc; + + lan9662_otp_set_address(otp, offset); + writel(OTP_OTP_FUNC_CMD_OTP_READ, OTP_OTP_FUNC_CMD(otp->base)); + writel(OTP_OTP_CMD_GO_OTP_GO, OTP_OTP_CMD_GO(otp->base)); + rc = lan9662_otp_execute(otp); + if (!rc) { + pass = readl(OTP_OTP_PASS_FAIL(otp->base)); + if (pass & OTP_OTP_PASS_FAIL_OTP_READ_PROHIBITED) + return -EACCES; + *dst = (u8) readl(OTP_OTP_RD_DATA(otp->base)); + } + return rc; +} + +static int lan9662_otp_write_byte(struct lan9662_otp *otp, u32 offset, u8 data) +{ + u32 pass; + int rc; + + lan9662_otp_set_address(otp, offset); + writel(OTP_OTP_PRGM_MODE_OTP_PGM_MODE_BYTE, OTP_OTP_PRGM_MODE(otp->base)); + writel(data, OTP_OTP_PRGM_DATA(otp->base)); + writel(OTP_OTP_FUNC_CMD_OTP_PROGRAM, OTP_OTP_FUNC_CMD(otp->base)); + writel(OTP_OTP_CMD_GO_OTP_GO, OTP_OTP_CMD_GO(otp->base)); + + rc = lan9662_otp_execute(otp); + if (!rc) { + pass = readl(OTP_OTP_PASS_FAIL(otp->base)); + if (pass & OTP_OTP_PASS_FAIL_OTP_WRITE_PROHIBITED) + return -EACCES; + if (pass & OTP_OTP_PASS_FAIL_OTP_FAIL) + return -EIO; + } + return rc; +} + +static int lan9662_otp_read(void *context, unsigned int offset, + void *_val, size_t bytes) +{ + struct lan9662_otp *otp = context; + u8 *val = _val; + uint8_t data; + int i, rc = 0; + + lan9662_otp_power(otp, true); + for (i = 0; i < bytes; i++) { + rc = lan9662_otp_read_byte(otp, offset + i, &data); + if (rc < 0) + break; + *val++ = data; + } + lan9662_otp_power(otp, false); + + return rc; +} + +static int lan9662_otp_write(void *context, unsigned int offset, + void *_val, size_t bytes) +{ + struct lan9662_otp *otp = context; + u8 *val = _val; + u8 data, newdata; + int i, rc = 0; + + lan9662_otp_power(otp, true); + for (i = 0; i < bytes; i++) { + /* Skip zero bytes */ + if (val[i]) { + rc = lan9662_otp_read_byte(otp, offset + i, &data); + if (rc < 0) + break; + + newdata = data | val[i]; + if (newdata == data) + continue; + + rc = lan9662_otp_write_byte(otp, offset + i, + newdata); + if (rc < 0) + break; + } + } + lan9662_otp_power(otp, false); + + return rc; +} + +static struct nvmem_config otp_config = { + .name = "lan9662-otp", + .stride = 1, + .word_size = 1, + .reg_read = lan9662_otp_read, + .reg_write = lan9662_otp_write, +}; + +static int lan9662_otp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nvmem_device *nvmem; + struct lan9662_otp *otp; + + otp = devm_kzalloc(&pdev->dev, sizeof(*otp), GFP_KERNEL); + if (!otp) + return -ENOMEM; + + otp->dev = dev; + otp->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(otp->base)) + return PTR_ERR(otp->base); + + otp_config.priv = otp; + otp_config.dev = dev; + otp_config.size = (uintptr_t) device_get_match_data(dev); + + nvmem = devm_nvmem_register(dev, &otp_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id lan9662_otp_match[] = { + { + .compatible = "microchip,lan9662-otpc", + .data = (const void *) SZ_8K, + }, + { + .compatible = "microchip,lan9691-otpc", + .data = (const void *) SZ_16K, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, lan9662_otp_match); + +static struct platform_driver lan9662_otp_driver = { + .probe = lan9662_otp_probe, + .driver = { + .name = "lan9662-otp", + .of_match_table = lan9662_otp_match, + }, +}; +module_platform_driver(lan9662_otp_driver); + +MODULE_AUTHOR("Horatiu Vultur <horatiu.vultur@microchip.com>"); +MODULE_DESCRIPTION("lan9662 OTP driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/layerscape-sfp.c b/drivers/nvmem/layerscape-sfp.c new file mode 100644 index 000000000..c1521afd5 --- /dev/null +++ b/drivers/nvmem/layerscape-sfp.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Layerscape SFP driver + * + * Copyright (c) 2022 Michael Walle <michael@walle.cc> + * + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regmap.h> + +#define LAYERSCAPE_SFP_OTP_OFFSET 0x0200 + +struct layerscape_sfp_priv { + struct regmap *regmap; +}; + +struct layerscape_sfp_data { + int size; + enum regmap_endian endian; +}; + +static int layerscape_sfp_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct layerscape_sfp_priv *priv = context; + + return regmap_bulk_read(priv->regmap, + LAYERSCAPE_SFP_OTP_OFFSET + offset, val, + bytes / 4); +} + +static struct nvmem_config layerscape_sfp_nvmem_config = { + .name = "fsl-sfp", + .reg_read = layerscape_sfp_read, + .word_size = 4, + .stride = 4, +}; + +static int layerscape_sfp_probe(struct platform_device *pdev) +{ + const struct layerscape_sfp_data *data; + struct layerscape_sfp_priv *priv; + struct nvmem_device *nvmem; + struct regmap_config config = { 0 }; + 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); + + data = device_get_match_data(&pdev->dev); + config.reg_bits = 32; + config.reg_stride = 4; + config.val_bits = 32; + config.val_format_endian = data->endian; + config.max_register = LAYERSCAPE_SFP_OTP_OFFSET + data->size - 4; + priv->regmap = devm_regmap_init_mmio(&pdev->dev, base, &config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + layerscape_sfp_nvmem_config.size = data->size; + layerscape_sfp_nvmem_config.dev = &pdev->dev; + layerscape_sfp_nvmem_config.priv = priv; + + nvmem = devm_nvmem_register(&pdev->dev, &layerscape_sfp_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct layerscape_sfp_data ls1021a_data = { + .size = 0x88, + .endian = REGMAP_ENDIAN_BIG, +}; + +static const struct layerscape_sfp_data ls1028a_data = { + .size = 0x88, + .endian = REGMAP_ENDIAN_LITTLE, +}; + +static const struct of_device_id layerscape_sfp_dt_ids[] = { + { .compatible = "fsl,ls1021a-sfp", .data = &ls1021a_data }, + { .compatible = "fsl,ls1028a-sfp", .data = &ls1028a_data }, + {}, +}; +MODULE_DEVICE_TABLE(of, layerscape_sfp_dt_ids); + +static struct platform_driver layerscape_sfp_driver = { + .probe = layerscape_sfp_probe, + .driver = { + .name = "layerscape_sfp", + .of_match_table = layerscape_sfp_dt_ids, + }, +}; +module_platform_driver(layerscape_sfp_driver); + +MODULE_AUTHOR("Michael Walle <michael@walle.cc>"); +MODULE_DESCRIPTION("Layerscape Security Fuse Processor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/layouts.c b/drivers/nvmem/layouts.c new file mode 100644 index 000000000..07a34be96 --- /dev/null +++ b/drivers/nvmem/layouts.c @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NVMEM layout bus handling + * + * Copyright (C) 2023 Bootlin + * Author: Miquel Raynal <miquel.raynal@bootlin.com + */ + +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> + +#include "internals.h" + +#define to_nvmem_layout_driver(drv) \ + (container_of_const((drv), struct nvmem_layout_driver, driver)) +#define to_nvmem_layout_device(_dev) \ + container_of((_dev), struct nvmem_layout, dev) + +static int nvmem_layout_bus_match(struct device *dev, const struct device_driver *drv) +{ + return of_driver_match_device(dev, drv); +} + +static int nvmem_layout_bus_probe(struct device *dev) +{ + struct nvmem_layout_driver *drv = to_nvmem_layout_driver(dev->driver); + struct nvmem_layout *layout = to_nvmem_layout_device(dev); + + if (!drv->probe || !drv->remove) + return -EINVAL; + + return drv->probe(layout); +} + +static void nvmem_layout_bus_remove(struct device *dev) +{ + struct nvmem_layout_driver *drv = to_nvmem_layout_driver(dev->driver); + struct nvmem_layout *layout = to_nvmem_layout_device(dev); + + return drv->remove(layout); +} + +static int nvmem_layout_bus_uevent(const struct device *dev, + struct kobj_uevent_env *env) +{ + int ret; + + ret = of_device_uevent_modalias(dev, env); + if (ret != -ENODEV) + return ret; + + return 0; +} + +static const struct bus_type nvmem_layout_bus_type = { + .name = "nvmem-layout", + .match = nvmem_layout_bus_match, + .probe = nvmem_layout_bus_probe, + .remove = nvmem_layout_bus_remove, + .uevent = nvmem_layout_bus_uevent, +}; + +int __nvmem_layout_driver_register(struct nvmem_layout_driver *drv, + struct module *owner) +{ + drv->driver.bus = &nvmem_layout_bus_type; + drv->driver.owner = owner; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__nvmem_layout_driver_register); + +void nvmem_layout_driver_unregister(struct nvmem_layout_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(nvmem_layout_driver_unregister); + +static void nvmem_layout_release_device(struct device *dev) +{ + struct nvmem_layout *layout = to_nvmem_layout_device(dev); + + of_node_put(layout->dev.of_node); + kfree(layout); +} + +static int nvmem_layout_create_device(struct nvmem_device *nvmem, + struct device_node *np) +{ + struct nvmem_layout *layout; + struct device *dev; + int ret; + + layout = kzalloc_obj(*layout); + if (!layout) + return -ENOMEM; + + /* Create a bidirectional link */ + layout->nvmem = nvmem; + nvmem->layout = layout; + + /* Device model registration */ + dev = &layout->dev; + device_initialize(dev); + dev->parent = &nvmem->dev; + dev->bus = &nvmem_layout_bus_type; + dev->release = nvmem_layout_release_device; + dev->coherent_dma_mask = DMA_BIT_MASK(32); + dev->dma_mask = &dev->coherent_dma_mask; + device_set_node(dev, of_fwnode_handle(of_node_get(np))); + of_device_make_bus_id(dev); + of_msi_configure(dev, dev->of_node); + + ret = device_add(dev); + if (ret) { + put_device(dev); + return ret; + } + + return 0; +} + +static int nvmem_layout_bus_populate(struct nvmem_device *nvmem, + struct device_node *layout_dn) +{ + int ret; + + /* Make sure it has a compatible property */ + if (!of_property_present(layout_dn, "compatible")) { + pr_debug("%s() - skipping %pOF, no compatible prop\n", + __func__, layout_dn); + return 0; + } + + if (of_node_check_flag(layout_dn, OF_POPULATED_BUS)) { + pr_debug("%s() - skipping %pOF, already populated\n", + __func__, layout_dn); + + return 0; + } + + /* NVMEM layout buses expect only a single device representing the layout */ + ret = nvmem_layout_create_device(nvmem, layout_dn); + if (ret) + return ret; + + of_node_set_flag(layout_dn, OF_POPULATED_BUS); + + return 0; +} + +struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem) +{ + return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout"); +} +EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container); + +/* + * Returns the number of devices populated, 0 if the operation was not relevant + * for this nvmem device, an error code otherwise. + */ +int nvmem_populate_layout(struct nvmem_device *nvmem) +{ + struct device_node *layout_dn; + int ret; + + layout_dn = of_nvmem_layout_get_container(nvmem); + if (!layout_dn) + return 0; + + /* Populate the layout device */ + device_links_supplier_sync_state_pause(); + ret = nvmem_layout_bus_populate(nvmem, layout_dn); + device_links_supplier_sync_state_resume(); + + of_node_put(layout_dn); + return ret; +} + +void nvmem_destroy_layout(struct nvmem_device *nvmem) +{ + struct device *dev; + + if (!nvmem->layout) + return; + + dev = &nvmem->layout->dev; + of_node_clear_flag(dev->of_node, OF_POPULATED_BUS); + device_unregister(dev); +} + +int nvmem_layout_bus_register(void) +{ + return bus_register(&nvmem_layout_bus_type); +} + +void nvmem_layout_bus_unregister(void) +{ + bus_unregister(&nvmem_layout_bus_type); +} diff --git a/drivers/nvmem/layouts/Kconfig b/drivers/nvmem/layouts/Kconfig new file mode 100644 index 000000000..5e586dfeb --- /dev/null +++ b/drivers/nvmem/layouts/Kconfig @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: GPL-2.0 + +config NVMEM_LAYOUTS + bool + depends on OF + +if NVMEM_LAYOUTS + +menu "Layout Types" + +config NVMEM_LAYOUT_SL28_VPD + tristate "Kontron sl28 VPD layout support" + select CRC8 + help + Say Y here if you want to support the VPD layout of the Kontron + SMARC-sAL28 boards. + + If unsure, say N. + +config NVMEM_LAYOUT_ONIE_TLV + tristate "ONIE tlv support" + select CRC32 + help + Say Y here if you want to support the Open Compute Project ONIE + Type-Length-Value standard table. + + If unsure, say N. + +config NVMEM_LAYOUT_U_BOOT_ENV + tristate "U-Boot environment variables layout" + select CRC32 + select GENERIC_NET_UTILS + help + U-Boot stores its setup as environment variables. This driver adds + support for verifying & exporting such data. It also exposes variables + as NVMEM cells so they can be referenced by other drivers. + + If unsure, say N. + +endmenu + +endif diff --git a/drivers/nvmem/layouts/Makefile b/drivers/nvmem/layouts/Makefile new file mode 100644 index 000000000..dd6c6c70b --- /dev/null +++ b/drivers/nvmem/layouts/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for nvmem layouts. +# + +obj-$(CONFIG_NVMEM_LAYOUTS) += fixed-layout.o +obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o +obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o +obj-$(CONFIG_NVMEM_LAYOUT_U_BOOT_ENV) += u-boot-env.o diff --git a/drivers/nvmem/layouts/fixed-layout.c b/drivers/nvmem/layouts/fixed-layout.c new file mode 100644 index 000000000..635d448b3 --- /dev/null +++ b/drivers/nvmem/layouts/fixed-layout.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2026 Bootlin + * + * Authors: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com> + */ + +#include <linux/nvmem-provider.h> +#include <linux/of.h> + +#include "../internals.h" + +static int fixed_layout_add_cells(struct nvmem_layout *layout) +{ + struct device_node *np; + int ret; + + np = of_nvmem_layout_get_container(layout->nvmem); + if (!np) + return -ENOENT; + + ret = nvmem_add_cells_from_dt(layout->nvmem, np); + of_node_put(np); + + return ret; +} + +static int fixed_layout_probe(struct nvmem_layout *layout) +{ + layout->add_cells = fixed_layout_add_cells; + + return nvmem_layout_register(layout); +} + +static void fixed_layout_remove(struct nvmem_layout *layout) +{ + nvmem_layout_unregister(layout); +} + +static const struct of_device_id fixed_layout_of_match_table[] = { + { .compatible = "fixed-layout", }, + {}, +}; + +static struct nvmem_layout_driver fixed_layout_layout = { + .driver = { + .name = "fixed-layout", + .of_match_table = fixed_layout_of_match_table, + }, + .probe = fixed_layout_probe, + .remove = fixed_layout_remove, +}; +module_nvmem_layout_driver(fixed_layout_layout); + +MODULE_AUTHOR("Mathieu Dubois-Briand"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, fixed_layout_of_match_table); +MODULE_DESCRIPTION("NVMEM fixed-layout driver"); diff --git a/drivers/nvmem/layouts/onie-tlv.c b/drivers/nvmem/layouts/onie-tlv.c new file mode 100644 index 000000000..8b0f3c1b8 --- /dev/null +++ b/drivers/nvmem/layouts/onie-tlv.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ONIE tlv NVMEM cells provider + * + * Copyright (C) 2022 Open Compute Group ONIE + * Author: Miquel Raynal <miquel.raynal@bootlin.com> + * Based on the nvmem driver written by: Vadym Kochan <vadym.kochan@plvision.eu> + * Inspired by the first layout written by: Rafał Miłecki <rafal@milecki.pl> + */ + +#include <linux/crc32.h> +#include <linux/etherdevice.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> + +#define ONIE_TLV_MAX_LEN 2048 +#define ONIE_TLV_CRC_FIELD_SZ 6 +#define ONIE_TLV_CRC_SZ 4 +#define ONIE_TLV_HDR_ID "TlvInfo" + +struct onie_tlv_hdr { + u8 id[8]; + u8 version; + __be16 data_len; +} __packed; + +struct onie_tlv { + u8 type; + u8 len; +} __packed; + +static const char *onie_tlv_cell_name(u8 type) +{ + switch (type) { + case 0x21: + return "product-name"; + case 0x22: + return "part-number"; + case 0x23: + return "serial-number"; + case 0x24: + return "mac-address"; + case 0x25: + return "manufacture-date"; + case 0x26: + return "device-version"; + case 0x27: + return "label-revision"; + case 0x28: + return "platform-name"; + case 0x29: + return "onie-version"; + case 0x2A: + return "num-macs"; + case 0x2B: + return "manufacturer"; + case 0x2C: + return "country-code"; + case 0x2D: + return "vendor"; + case 0x2E: + return "diag-version"; + case 0x2F: + return "service-tag"; + case 0xFD: + return "vendor-extension"; + case 0xFE: + return "crc32"; + default: + break; + } + + return NULL; +} + +static int onie_tlv_mac_read_cb(void *priv, const char *id, int index, + unsigned int offset, void *buf, + size_t bytes) +{ + eth_addr_add(buf, index); + + return 0; +} + +static nvmem_cell_post_process_t onie_tlv_read_cb(u8 type, u8 *buf) +{ + switch (type) { + case 0x24: + return &onie_tlv_mac_read_cb; + default: + break; + } + + return NULL; +} + +static int onie_tlv_add_cells(struct device *dev, struct nvmem_device *nvmem, + size_t data_len, u8 *data) +{ + struct nvmem_cell_info cell = {}; + struct device_node *layout; + struct onie_tlv tlv; + unsigned int hdr_len = sizeof(struct onie_tlv_hdr); + unsigned int offset = 0; + int ret; + + layout = of_nvmem_layout_get_container(nvmem); + if (!layout) + return -ENOENT; + + while (offset < data_len) { + memcpy(&tlv, data + offset, sizeof(tlv)); + if (offset + tlv.len >= data_len) { + dev_err(dev, "Out of bounds field (0x%x bytes at 0x%x)\n", + tlv.len, hdr_len + offset); + break; + } + + cell.name = onie_tlv_cell_name(tlv.type); + if (!cell.name) + goto next; + + cell.offset = hdr_len + offset + sizeof(tlv.type) + sizeof(tlv.len); + cell.bytes = tlv.len; + cell.np = of_get_child_by_name(layout, cell.name); + cell.read_post_process = onie_tlv_read_cb(tlv.type, data + offset + sizeof(tlv)); + + ret = nvmem_add_one_cell(nvmem, &cell); + if (ret) { + of_node_put(layout); + return ret; + } + +next: + offset += sizeof(tlv) + tlv.len; + } + + of_node_put(layout); + + return 0; +} + +static bool onie_tlv_hdr_is_valid(struct device *dev, struct onie_tlv_hdr *hdr) +{ + if (memcmp(hdr->id, ONIE_TLV_HDR_ID, sizeof(hdr->id))) { + dev_err(dev, "Invalid header\n"); + return false; + } + + if (hdr->version != 0x1) { + dev_err(dev, "Invalid version number\n"); + return false; + } + + return true; +} + +static bool onie_tlv_crc_is_valid(struct device *dev, size_t table_len, u8 *table) +{ + struct onie_tlv crc_hdr; + u32 read_crc, calc_crc; + __be32 crc_be; + + memcpy(&crc_hdr, table + table_len - ONIE_TLV_CRC_FIELD_SZ, sizeof(crc_hdr)); + if (crc_hdr.type != 0xfe || crc_hdr.len != ONIE_TLV_CRC_SZ) { + dev_err(dev, "Invalid CRC field\n"); + return false; + } + + /* The table contains a JAMCRC, which is XOR'ed compared to the original + * CRC32 implementation as known in the Ethernet world. + */ + memcpy(&crc_be, table + table_len - ONIE_TLV_CRC_SZ, ONIE_TLV_CRC_SZ); + read_crc = be32_to_cpu(crc_be); + calc_crc = crc32(~0, table, table_len - ONIE_TLV_CRC_SZ) ^ 0xFFFFFFFF; + if (read_crc != calc_crc) { + dev_err(dev, "Invalid CRC read: 0x%08x, expected: 0x%08x\n", + read_crc, calc_crc); + return false; + } + + return true; +} + +static int onie_tlv_parse_table(struct nvmem_layout *layout) +{ + struct nvmem_device *nvmem = layout->nvmem; + struct device *dev = &layout->dev; + struct onie_tlv_hdr hdr; + size_t table_len, data_len, hdr_len; + u8 *table, *data; + int ret; + + ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr); + if (ret < 0) + return ret; + + if (!onie_tlv_hdr_is_valid(dev, &hdr)) { + dev_err(dev, "Invalid ONIE TLV header\n"); + return -EINVAL; + } + + hdr_len = sizeof(hdr.id) + sizeof(hdr.version) + sizeof(hdr.data_len); + data_len = be16_to_cpu(hdr.data_len); + table_len = hdr_len + data_len; + if (table_len > ONIE_TLV_MAX_LEN) { + dev_err(dev, "Invalid ONIE TLV data length\n"); + return -EINVAL; + } + + table = devm_kmalloc(dev, table_len, GFP_KERNEL); + if (!table) + return -ENOMEM; + + ret = nvmem_device_read(nvmem, 0, table_len, table); + if (ret != table_len) + return ret; + + if (!onie_tlv_crc_is_valid(dev, table_len, table)) + return -EINVAL; + + data = table + hdr_len; + ret = onie_tlv_add_cells(dev, nvmem, data_len, data); + if (ret) + return ret; + + return 0; +} + +static int onie_tlv_probe(struct nvmem_layout *layout) +{ + layout->add_cells = onie_tlv_parse_table; + + return nvmem_layout_register(layout); +} + +static void onie_tlv_remove(struct nvmem_layout *layout) +{ + nvmem_layout_unregister(layout); +} + +static const struct of_device_id onie_tlv_of_match_table[] = { + { .compatible = "onie,tlv-layout", }, + {}, +}; +MODULE_DEVICE_TABLE(of, onie_tlv_of_match_table); + +static struct nvmem_layout_driver onie_tlv_layout = { + .driver = { + .name = "onie-tlv-layout", + .of_match_table = onie_tlv_of_match_table, + }, + .probe = onie_tlv_probe, + .remove = onie_tlv_remove, +}; +module_nvmem_layout_driver(onie_tlv_layout); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>"); +MODULE_DESCRIPTION("NVMEM layout driver for Onie TLV table parsing"); diff --git a/drivers/nvmem/layouts/sl28vpd.c b/drivers/nvmem/layouts/sl28vpd.c new file mode 100644 index 000000000..e93b020b0 --- /dev/null +++ b/drivers/nvmem/layouts/sl28vpd.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/crc8.h> +#include <linux/etherdevice.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <uapi/linux/if_ether.h> + +#define SL28VPD_MAGIC 'V' + +struct sl28vpd_header { + u8 magic; + u8 version; +} __packed; + +struct sl28vpd_v1 { + struct sl28vpd_header header; + char serial_number[15]; + u8 base_mac_address[ETH_ALEN]; + u8 crc8; +} __packed; + +static int sl28vpd_mac_address_pp(void *priv, const char *id, int index, + unsigned int offset, void *buf, + size_t bytes) +{ + if (bytes != ETH_ALEN) + return -EINVAL; + + if (index < 0) + return -EINVAL; + + if (!is_valid_ether_addr(buf)) + return -EINVAL; + + eth_addr_add(buf, index); + + return 0; +} + +static const struct nvmem_cell_info sl28vpd_v1_entries[] = { + { + .name = "serial-number", + .offset = offsetof(struct sl28vpd_v1, serial_number), + .bytes = sizeof_field(struct sl28vpd_v1, serial_number), + }, + { + .name = "base-mac-address", + .offset = offsetof(struct sl28vpd_v1, base_mac_address), + .bytes = sizeof_field(struct sl28vpd_v1, base_mac_address), + .read_post_process = sl28vpd_mac_address_pp, + }, +}; + +static int sl28vpd_v1_check_crc(struct device *dev, struct nvmem_device *nvmem) +{ + struct sl28vpd_v1 data_v1; + u8 table[CRC8_TABLE_SIZE]; + int ret; + u8 crc; + + crc8_populate_msb(table, 0x07); + + ret = nvmem_device_read(nvmem, 0, sizeof(data_v1), &data_v1); + if (ret < 0) + return ret; + else if (ret != sizeof(data_v1)) + return -EIO; + + crc = crc8(table, (void *)&data_v1, sizeof(data_v1) - 1, 0); + + if (crc != data_v1.crc8) { + dev_err(dev, + "Checksum is invalid (got %02x, expected %02x).\n", + crc, data_v1.crc8); + return -EINVAL; + } + + return 0; +} + +static int sl28vpd_add_cells(struct nvmem_layout *layout) +{ + struct nvmem_device *nvmem = layout->nvmem; + struct device *dev = &layout->dev; + const struct nvmem_cell_info *pinfo; + struct nvmem_cell_info info = {0}; + struct device_node *layout_np; + struct sl28vpd_header hdr; + int ret, i; + + /* check header */ + ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr); + if (ret < 0) + return ret; + else if (ret != sizeof(hdr)) + return -EIO; + + if (hdr.magic != SL28VPD_MAGIC) { + dev_err(dev, "Invalid magic value (%02x)\n", hdr.magic); + return -EINVAL; + } + + if (hdr.version != 1) { + dev_err(dev, "Version %d is unsupported.\n", hdr.version); + return -EINVAL; + } + + ret = sl28vpd_v1_check_crc(dev, nvmem); + if (ret) + return ret; + + layout_np = of_nvmem_layout_get_container(nvmem); + if (!layout_np) + return -ENOENT; + + for (i = 0; i < ARRAY_SIZE(sl28vpd_v1_entries); i++) { + pinfo = &sl28vpd_v1_entries[i]; + + info.name = pinfo->name; + info.offset = pinfo->offset; + info.bytes = pinfo->bytes; + info.read_post_process = pinfo->read_post_process; + info.np = of_get_child_by_name(layout_np, pinfo->name); + + ret = nvmem_add_one_cell(nvmem, &info); + if (ret) { + of_node_put(layout_np); + return ret; + } + } + + of_node_put(layout_np); + + return 0; +} + +static int sl28vpd_probe(struct nvmem_layout *layout) +{ + layout->add_cells = sl28vpd_add_cells; + + return nvmem_layout_register(layout); +} + +static void sl28vpd_remove(struct nvmem_layout *layout) +{ + nvmem_layout_unregister(layout); +} + +static const struct of_device_id sl28vpd_of_match_table[] = { + { .compatible = "kontron,sl28-vpd" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sl28vpd_of_match_table); + +static struct nvmem_layout_driver sl28vpd_layout = { + .driver = { + .name = "kontron-sl28vpd-layout", + .of_match_table = sl28vpd_of_match_table, + }, + .probe = sl28vpd_probe, + .remove = sl28vpd_remove, +}; +module_nvmem_layout_driver(sl28vpd_layout); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Walle <michael@walle.cc>"); +MODULE_DESCRIPTION("NVMEM layout driver for the VPD of Kontron sl28 boards"); diff --git a/drivers/nvmem/layouts/u-boot-env.c b/drivers/nvmem/layouts/u-boot-env.c new file mode 100644 index 000000000..33ec23503 --- /dev/null +++ b/drivers/nvmem/layouts/u-boot-env.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 - 2023 Rafał Miłecki <rafal@milecki.pl> + */ + +#include <linux/crc32.h> +#include <linux/etherdevice.h> +#include <linux/export.h> +#include <linux/hex.h> +#include <linux/if_ether.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/slab.h> + +#include "u-boot-env.h" + +struct u_boot_env_image_single { + __le32 crc32; + uint8_t data[]; +} __packed; + +struct u_boot_env_image_redundant { + __le32 crc32; + u8 mark; + uint8_t data[]; +} __packed; + +struct u_boot_env_image_broadcom { + __le32 magic; + __le32 len; + __le32 crc32; + DECLARE_FLEX_ARRAY(uint8_t, data); +} __packed; + +static int u_boot_env_read_post_process_ethaddr(void *context, const char *id, int index, + unsigned int offset, void *buf, size_t bytes) +{ + u8 mac[ETH_ALEN]; + + if (!mac_pton(buf, mac)) + return -EINVAL; + + if (index) + eth_addr_add(mac, index); + + ether_addr_copy(buf, mac); + + return 0; +} + +static int u_boot_env_parse_cells(struct device *dev, struct nvmem_device *nvmem, uint8_t *buf, + size_t data_offset, size_t data_len) +{ + char *data = buf + data_offset; + char *var, *value, *eq; + + for (var = data; + var < data + data_len && *var; + var = value + strlen(value) + 1) { + struct nvmem_cell_info info = {}; + + eq = strchr(var, '='); + if (!eq) + break; + *eq = '\0'; + value = eq + 1; + + info.name = devm_kstrdup(dev, var, GFP_KERNEL); + if (!info.name) + return -ENOMEM; + info.offset = data_offset + value - data; + info.bytes = strlen(value); + info.np = of_get_child_by_name(dev->of_node, info.name); + if (!strcmp(var, "ethaddr") && info.bytes == MAC_ADDR_STR_LEN) { + info.raw_len = strlen(value); + info.bytes = ETH_ALEN; + info.read_post_process = u_boot_env_read_post_process_ethaddr; + } + + nvmem_add_one_cell(nvmem, &info); + } + + return 0; +} + +int u_boot_env_parse(struct device *dev, struct nvmem_device *nvmem, + enum u_boot_env_format format) +{ + size_t crc32_data_offset; + size_t crc32_data_len; + size_t crc32_offset; + uint32_t *crc32_addr; + size_t data_offset; + size_t data_len; + size_t dev_size; + uint32_t crc32; + uint32_t calc; + uint8_t *buf; + u32 env_size; + int bytes; + int err; + + dev_size = device_property_read_u32(dev, "env-size", &env_size) ? + nvmem_dev_size(nvmem) : (size_t)env_size; + + buf = kzalloc(dev_size, GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto err_out; + } + + bytes = nvmem_device_read(nvmem, 0, dev_size, buf); + if (bytes < 0) { + err = bytes; + goto err_kfree; + } else if (bytes != dev_size) { + err = -EIO; + goto err_kfree; + } + + switch (format) { + case U_BOOT_FORMAT_SINGLE: + crc32_offset = offsetof(struct u_boot_env_image_single, crc32); + crc32_data_offset = offsetof(struct u_boot_env_image_single, data); + data_offset = offsetof(struct u_boot_env_image_single, data); + break; + case U_BOOT_FORMAT_REDUNDANT: + crc32_offset = offsetof(struct u_boot_env_image_redundant, crc32); + crc32_data_offset = offsetof(struct u_boot_env_image_redundant, data); + data_offset = offsetof(struct u_boot_env_image_redundant, data); + break; + case U_BOOT_FORMAT_BROADCOM: + crc32_offset = offsetof(struct u_boot_env_image_broadcom, crc32); + crc32_data_offset = offsetof(struct u_boot_env_image_broadcom, data); + data_offset = offsetof(struct u_boot_env_image_broadcom, data); + break; + } + + if (dev_size < data_offset) { + dev_err(dev, "Device too small for u-boot-env\n"); + err = -EIO; + goto err_kfree; + } + + crc32_addr = (uint32_t *)(buf + crc32_offset); + crc32 = *crc32_addr; + crc32_data_len = dev_size - crc32_data_offset; + data_len = dev_size - data_offset; + + calc = crc32_le(~0, buf + crc32_data_offset, crc32_data_len) ^ ~0L; + if (calc != crc32) { + dev_err(dev, "Invalid calculated CRC32: 0x%08x (expected: 0x%08x)\n", calc, crc32); + err = -EINVAL; + goto err_kfree; + } + + buf[dev_size - 1] = '\0'; + err = u_boot_env_parse_cells(dev, nvmem, buf, data_offset, data_len); + +err_kfree: + kfree(buf); +err_out: + return err; +} +EXPORT_SYMBOL_GPL(u_boot_env_parse); + +static int u_boot_env_add_cells(struct nvmem_layout *layout) +{ + struct device *dev = &layout->dev; + enum u_boot_env_format format; + + format = (uintptr_t)device_get_match_data(dev); + + return u_boot_env_parse(dev, layout->nvmem, format); +} + +static int u_boot_env_probe(struct nvmem_layout *layout) +{ + layout->add_cells = u_boot_env_add_cells; + + return nvmem_layout_register(layout); +} + +static void u_boot_env_remove(struct nvmem_layout *layout) +{ + nvmem_layout_unregister(layout); +} + +static const struct of_device_id u_boot_env_of_match_table[] = { + { .compatible = "u-boot,env", .data = (void *)U_BOOT_FORMAT_SINGLE, }, + { .compatible = "u-boot,env-redundant-bool", .data = (void *)U_BOOT_FORMAT_REDUNDANT, }, + { .compatible = "u-boot,env-redundant-count", .data = (void *)U_BOOT_FORMAT_REDUNDANT, }, + { .compatible = "brcm,env", .data = (void *)U_BOOT_FORMAT_BROADCOM, }, + {}, +}; + +static struct nvmem_layout_driver u_boot_env_layout = { + .driver = { + .name = "u-boot-env-layout", + .of_match_table = u_boot_env_of_match_table, + }, + .probe = u_boot_env_probe, + .remove = u_boot_env_remove, +}; +module_nvmem_layout_driver(u_boot_env_layout); + +MODULE_AUTHOR("Rafał Miłecki"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, u_boot_env_of_match_table); +MODULE_DESCRIPTION("NVMEM layout driver for U-Boot environment variables"); diff --git a/drivers/nvmem/layouts/u-boot-env.h b/drivers/nvmem/layouts/u-boot-env.h new file mode 100644 index 000000000..dd5f280ac --- /dev/null +++ b/drivers/nvmem/layouts/u-boot-env.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _LINUX_NVMEM_LAYOUTS_U_BOOT_ENV_H +#define _LINUX_NVMEM_LAYOUTS_U_BOOT_ENV_H + +enum u_boot_env_format { + U_BOOT_FORMAT_SINGLE, + U_BOOT_FORMAT_REDUNDANT, + U_BOOT_FORMAT_BROADCOM, +}; + +int u_boot_env_parse(struct device *dev, struct nvmem_device *nvmem, + enum u_boot_env_format format); + +#endif /* ifndef _LINUX_NVMEM_LAYOUTS_U_BOOT_ENV_H */ diff --git a/drivers/nvmem/lpc18xx_eeprom.c b/drivers/nvmem/lpc18xx_eeprom.c new file mode 100644 index 000000000..504155e30 --- /dev/null +++ b/drivers/nvmem/lpc18xx_eeprom.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP LPC18xx/LPC43xx EEPROM memory NVMEM driver + * + * Copyright (c) 2015 Ariel D'Alessandro <ariel@vanguardiasur.com> + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/reset.h> + +/* Registers */ +#define LPC18XX_EEPROM_AUTOPROG 0x00c +#define LPC18XX_EEPROM_AUTOPROG_WORD 0x1 + +#define LPC18XX_EEPROM_CLKDIV 0x014 + +#define LPC18XX_EEPROM_PWRDWN 0x018 +#define LPC18XX_EEPROM_PWRDWN_NO 0x0 +#define LPC18XX_EEPROM_PWRDWN_YES 0x1 + +#define LPC18XX_EEPROM_INTSTAT 0xfe0 +#define LPC18XX_EEPROM_INTSTAT_END_OF_PROG BIT(2) + +#define LPC18XX_EEPROM_INTSTATCLR 0xfe8 +#define LPC18XX_EEPROM_INTSTATCLR_PROG_CLR_ST BIT(2) + +/* Fixed page size (bytes) */ +#define LPC18XX_EEPROM_PAGE_SIZE 0x80 + +/* EEPROM device requires a ~1500 kHz clock (min 800 kHz, max 1600 kHz) */ +#define LPC18XX_EEPROM_CLOCK_HZ 1500000 + +/* EEPROM requires 3 ms of erase/program time between each writing */ +#define LPC18XX_EEPROM_PROGRAM_TIME 3 + +struct lpc18xx_eeprom_dev { + struct clk *clk; + void __iomem *reg_base; + void __iomem *mem_base; + struct nvmem_device *nvmem; + unsigned reg_bytes; + unsigned val_bytes; + int size; +}; + +static inline void lpc18xx_eeprom_writel(struct lpc18xx_eeprom_dev *eeprom, + u32 reg, u32 val) +{ + writel(val, eeprom->reg_base + reg); +} + +static inline u32 lpc18xx_eeprom_readl(struct lpc18xx_eeprom_dev *eeprom, + u32 reg) +{ + return readl(eeprom->reg_base + reg); +} + +static int lpc18xx_eeprom_busywait_until_prog(struct lpc18xx_eeprom_dev *eeprom) +{ + unsigned long end; + u32 val; + + /* Wait until EEPROM program operation has finished */ + end = jiffies + msecs_to_jiffies(LPC18XX_EEPROM_PROGRAM_TIME * 10); + + while (time_is_after_jiffies(end)) { + val = lpc18xx_eeprom_readl(eeprom, LPC18XX_EEPROM_INTSTAT); + + if (val & LPC18XX_EEPROM_INTSTAT_END_OF_PROG) { + lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_INTSTATCLR, + LPC18XX_EEPROM_INTSTATCLR_PROG_CLR_ST); + return 0; + } + + usleep_range(LPC18XX_EEPROM_PROGRAM_TIME * USEC_PER_MSEC, + (LPC18XX_EEPROM_PROGRAM_TIME + 1) * USEC_PER_MSEC); + } + + return -ETIMEDOUT; +} + +static int lpc18xx_eeprom_gather_write(void *context, unsigned int reg, + void *val, size_t bytes) +{ + struct lpc18xx_eeprom_dev *eeprom = context; + unsigned int offset = reg; + int ret; + + /* + * The last page contains the EEPROM initialization data and is not + * writable. + */ + if ((reg > eeprom->size - LPC18XX_EEPROM_PAGE_SIZE) || + (reg + bytes > eeprom->size - LPC18XX_EEPROM_PAGE_SIZE)) + return -EINVAL; + + + lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN, + LPC18XX_EEPROM_PWRDWN_NO); + + /* Wait 100 us while the EEPROM wakes up */ + usleep_range(100, 200); + + while (bytes) { + writel(*(u32 *)val, eeprom->mem_base + offset); + ret = lpc18xx_eeprom_busywait_until_prog(eeprom); + if (ret < 0) + return ret; + + bytes -= eeprom->val_bytes; + val += eeprom->val_bytes; + offset += eeprom->val_bytes; + } + + lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN, + LPC18XX_EEPROM_PWRDWN_YES); + + return 0; +} + +static int lpc18xx_eeprom_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct lpc18xx_eeprom_dev *eeprom = context; + + lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN, + LPC18XX_EEPROM_PWRDWN_NO); + + /* Wait 100 us while the EEPROM wakes up */ + usleep_range(100, 200); + + while (bytes) { + *(u32 *)val = readl(eeprom->mem_base + offset); + bytes -= eeprom->val_bytes; + val += eeprom->val_bytes; + offset += eeprom->val_bytes; + } + + lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN, + LPC18XX_EEPROM_PWRDWN_YES); + + return 0; +} + + +static struct nvmem_config lpc18xx_nvmem_config = { + .name = "lpc18xx-eeprom", + .stride = 4, + .word_size = 4, + .reg_read = lpc18xx_eeprom_read, + .reg_write = lpc18xx_eeprom_gather_write, +}; + +static int lpc18xx_eeprom_probe(struct platform_device *pdev) +{ + struct lpc18xx_eeprom_dev *eeprom; + struct device *dev = &pdev->dev; + struct reset_control *rst; + unsigned long clk_rate; + struct resource *res; + int ret; + + eeprom = devm_kzalloc(dev, sizeof(*eeprom), GFP_KERNEL); + if (!eeprom) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "reg"); + eeprom->reg_base = devm_ioremap_resource(dev, res); + if (IS_ERR(eeprom->reg_base)) + return PTR_ERR(eeprom->reg_base); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mem"); + eeprom->mem_base = devm_ioremap_resource(dev, res); + if (IS_ERR(eeprom->mem_base)) + return PTR_ERR(eeprom->mem_base); + + eeprom->clk = devm_clk_get(&pdev->dev, "eeprom"); + if (IS_ERR(eeprom->clk)) { + dev_err(&pdev->dev, "failed to get eeprom clock\n"); + return PTR_ERR(eeprom->clk); + } + + ret = clk_prepare_enable(eeprom->clk); + if (ret < 0) { + dev_err(dev, "failed to prepare/enable eeprom clk: %d\n", ret); + return ret; + } + + rst = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(rst)) { + dev_err(dev, "failed to get reset: %ld\n", PTR_ERR(rst)); + ret = PTR_ERR(rst); + goto err_clk; + } + + ret = reset_control_assert(rst); + if (ret < 0) { + dev_err(dev, "failed to assert reset: %d\n", ret); + goto err_clk; + } + + eeprom->val_bytes = 4; + eeprom->reg_bytes = 4; + + /* + * Clock rate is generated by dividing the system bus clock by the + * division factor, contained in the divider register (minus 1 encoded). + */ + clk_rate = clk_get_rate(eeprom->clk); + clk_rate = DIV_ROUND_UP(clk_rate, LPC18XX_EEPROM_CLOCK_HZ) - 1; + lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_CLKDIV, clk_rate); + + /* + * Writing a single word to the page will start the erase/program cycle + * automatically + */ + lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_AUTOPROG, + LPC18XX_EEPROM_AUTOPROG_WORD); + + lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN, + LPC18XX_EEPROM_PWRDWN_YES); + + eeprom->size = resource_size(res); + lpc18xx_nvmem_config.size = resource_size(res); + lpc18xx_nvmem_config.dev = dev; + lpc18xx_nvmem_config.priv = eeprom; + + eeprom->nvmem = devm_nvmem_register(dev, &lpc18xx_nvmem_config); + if (IS_ERR(eeprom->nvmem)) { + ret = PTR_ERR(eeprom->nvmem); + goto err_clk; + } + + platform_set_drvdata(pdev, eeprom); + + return 0; + +err_clk: + clk_disable_unprepare(eeprom->clk); + + return ret; +} + +static void lpc18xx_eeprom_remove(struct platform_device *pdev) +{ + struct lpc18xx_eeprom_dev *eeprom = platform_get_drvdata(pdev); + + clk_disable_unprepare(eeprom->clk); +} + +static const struct of_device_id lpc18xx_eeprom_of_match[] = { + { .compatible = "nxp,lpc1857-eeprom" }, + { }, +}; +MODULE_DEVICE_TABLE(of, lpc18xx_eeprom_of_match); + +static struct platform_driver lpc18xx_eeprom_driver = { + .probe = lpc18xx_eeprom_probe, + .remove = lpc18xx_eeprom_remove, + .driver = { + .name = "lpc18xx-eeprom", + .of_match_table = lpc18xx_eeprom_of_match, + }, +}; + +module_platform_driver(lpc18xx_eeprom_driver); + +MODULE_AUTHOR("Ariel D'Alessandro <ariel@vanguardiasur.com.ar>"); +MODULE_DESCRIPTION("NXP LPC18xx EEPROM memory Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/lpc18xx_otp.c b/drivers/nvmem/lpc18xx_otp.c new file mode 100644 index 000000000..c41a0c58b --- /dev/null +++ b/drivers/nvmem/lpc18xx_otp.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NXP LPC18xx/43xx OTP memory NVMEM driver + * + * Copyright (c) 2016 Joachim Eastwood <manabian@gmail.com> + * + * Based on the imx ocotp driver, + * Copyright (c) 2015 Pengutronix, Philipp Zabel <p.zabel@pengutronix.de> + * + * TODO: add support for writing OTP register via API in boot ROM. + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* + * LPC18xx OTP memory contains 4 banks with 4 32-bit words. Bank 0 starts + * at offset 0 from the base. + * + * Bank 0 contains the part ID for Flashless devices and is reserved for + * devices with Flash. + * Bank 1/2 is generale purpose or AES key storage for secure devices. + * Bank 3 contains control data, USB ID and generale purpose words. + */ +#define LPC18XX_OTP_NUM_BANKS 4 +#define LPC18XX_OTP_WORDS_PER_BANK 4 +#define LPC18XX_OTP_WORD_SIZE sizeof(u32) +#define LPC18XX_OTP_SIZE (LPC18XX_OTP_NUM_BANKS * \ + LPC18XX_OTP_WORDS_PER_BANK * \ + LPC18XX_OTP_WORD_SIZE) + +struct lpc18xx_otp { + void __iomem *base; +}; + +static int lpc18xx_otp_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct lpc18xx_otp *otp = context; + unsigned int count = bytes >> 2; + u32 index = offset >> 2; + u32 *buf = val; + int i; + + if (count > (LPC18XX_OTP_SIZE - index)) + count = LPC18XX_OTP_SIZE - index; + + for (i = index; i < (index + count); i++) + *buf++ = readl(otp->base + i * LPC18XX_OTP_WORD_SIZE); + + return 0; +} + +static struct nvmem_config lpc18xx_otp_nvmem_config = { + .name = "lpc18xx-otp", + .read_only = true, + .word_size = LPC18XX_OTP_WORD_SIZE, + .stride = LPC18XX_OTP_WORD_SIZE, + .reg_read = lpc18xx_otp_read, +}; + +static int lpc18xx_otp_probe(struct platform_device *pdev) +{ + struct nvmem_device *nvmem; + struct lpc18xx_otp *otp; + + otp = devm_kzalloc(&pdev->dev, sizeof(*otp), GFP_KERNEL); + if (!otp) + return -ENOMEM; + + otp->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(otp->base)) + return PTR_ERR(otp->base); + + lpc18xx_otp_nvmem_config.size = LPC18XX_OTP_SIZE; + lpc18xx_otp_nvmem_config.dev = &pdev->dev; + lpc18xx_otp_nvmem_config.priv = otp; + + nvmem = devm_nvmem_register(&pdev->dev, &lpc18xx_otp_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id lpc18xx_otp_dt_ids[] = { + { .compatible = "nxp,lpc1850-otp" }, + { }, +}; +MODULE_DEVICE_TABLE(of, lpc18xx_otp_dt_ids); + +static struct platform_driver lpc18xx_otp_driver = { + .probe = lpc18xx_otp_probe, + .driver = { + .name = "lpc18xx_otp", + .of_match_table = lpc18xx_otp_dt_ids, + }, +}; +module_platform_driver(lpc18xx_otp_driver); + +MODULE_AUTHOR("Joachim Eastwoood <manabian@gmail.com>"); +MODULE_DESCRIPTION("NXP LPC18xx OTP NVMEM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/m24lr.c b/drivers/nvmem/m24lr.c new file mode 100644 index 000000000..7a9fd45a8 --- /dev/null +++ b/drivers/nvmem/m24lr.c @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * m24lr.c - Sysfs control interface for ST M24LR series RFID/NFC chips + * + * Copyright (c) 2025 Abd-Alrhman Masalkhi <abd.masalkhi@gmail.com> + * + * This driver implements both the sysfs-based control interface and EEPROM + * access for STMicroelectronics M24LR series chips (e.g., M24LR04E-R). + * It provides access to control registers for features such as password + * authentication, memory protection, and device configuration. In addition, + * it manages read and write operations to the EEPROM region of the chip. + */ + +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> + +#define M24LR_WRITE_TIMEOUT 25u +#define M24LR_READ_TIMEOUT (M24LR_WRITE_TIMEOUT) + +/** + * struct m24lr_chip - describes chip-specific sysfs layout + * @sss_len: the length of the sss region + * @page_size: chip-specific limit on the maximum number of bytes allowed + * in a single write operation. + * @eeprom_size: size of the EEPROM in byte + * + * Supports multiple M24LR chip variants (e.g., M24LRxx) by allowing each + * to define its own set of sysfs attributes, depending on its available + * registers and features. + */ +struct m24lr_chip { + unsigned int sss_len; + unsigned int page_size; + unsigned int eeprom_size; +}; + +/** + * struct m24lr - core driver data for M24LR chip control + * @uid: 64 bits unique identifier stored in the device + * @sss_len: the length of the sss region + * @page_size: chip-specific limit on the maximum number of bytes allowed + * in a single write operation. + * @eeprom_size: size of the EEPROM in byte + * @ctl_regmap: regmap interface for accessing the system parameter sector + * @eeprom_regmap: regmap interface for accessing the EEPROM + * @lock: mutex to synchronize operations to the device + * + * Central data structure holding the state and resources used by the + * M24LR device driver. + */ +struct m24lr { + u64 uid; + unsigned int sss_len; + unsigned int page_size; + unsigned int eeprom_size; + struct regmap *ctl_regmap; + struct regmap *eeprom_regmap; + struct mutex lock; /* synchronize operations to the device */ +}; + +static const struct regmap_range m24lr_ctl_vo_ranges[] = { + regmap_reg_range(0, 63), +}; + +static const struct regmap_access_table m24lr_ctl_vo_table = { + .yes_ranges = m24lr_ctl_vo_ranges, + .n_yes_ranges = ARRAY_SIZE(m24lr_ctl_vo_ranges), +}; + +static const struct regmap_config m24lr_ctl_regmap_conf = { + .name = "m24lr_ctl", + .reg_stride = 1, + .reg_bits = 16, + .val_bits = 8, + .disable_locking = false, + .cache_type = REGCACHE_RBTREE,/* Flat can't be used, there's huge gap */ + .volatile_table = &m24lr_ctl_vo_table, +}; + +/* Chip descriptor for M24LR04E-R variant */ +static const struct m24lr_chip m24lr04e_r_chip = { + .page_size = 4, + .eeprom_size = 512, + .sss_len = 4, +}; + +/* Chip descriptor for M24LR16E-R variant */ +static const struct m24lr_chip m24lr16e_r_chip = { + .page_size = 4, + .eeprom_size = 2048, + .sss_len = 16, +}; + +/* Chip descriptor for M24LR64E-R variant */ +static const struct m24lr_chip m24lr64e_r_chip = { + .page_size = 4, + .eeprom_size = 8192, + .sss_len = 64, +}; + +static const struct i2c_device_id m24lr_ids[] = { + { "m24lr04e-r", (kernel_ulong_t)&m24lr04e_r_chip}, + { "m24lr16e-r", (kernel_ulong_t)&m24lr16e_r_chip}, + { "m24lr64e-r", (kernel_ulong_t)&m24lr64e_r_chip}, + { } +}; +MODULE_DEVICE_TABLE(i2c, m24lr_ids); + +static const struct of_device_id m24lr_of_match[] = { + { .compatible = "st,m24lr04e-r", .data = &m24lr04e_r_chip}, + { .compatible = "st,m24lr16e-r", .data = &m24lr16e_r_chip}, + { .compatible = "st,m24lr64e-r", .data = &m24lr64e_r_chip}, + { } +}; +MODULE_DEVICE_TABLE(of, m24lr_of_match); + +/** + * m24lr_regmap_read - read data using regmap with retry on failure + * @regmap: regmap instance for the device + * @buf: buffer to store the read data + * @size: number of bytes to read + * @offset: starting register address + * + * Attempts to read a block of data from the device with retries and timeout. + * Some M24LR chips may transiently NACK reads (e.g., during internal write + * cycles), so this function retries with a short sleep until the timeout + * expires. + * + * Returns: + * Number of bytes read on success, + * -ETIMEDOUT if the read fails within the timeout window. + */ +static ssize_t m24lr_regmap_read(struct regmap *regmap, u8 *buf, + size_t size, unsigned int offset) +{ + int err; + unsigned long timeout, read_time; + ssize_t ret = -ETIMEDOUT; + + timeout = jiffies + msecs_to_jiffies(M24LR_READ_TIMEOUT); + do { + read_time = jiffies; + + err = regmap_bulk_read(regmap, offset, buf, size); + if (!err) { + ret = size; + break; + } + + usleep_range(1000, 2000); + } while (time_before(read_time, timeout)); + + return ret; +} + +/** + * m24lr_regmap_write - write data using regmap with retry on failure + * @regmap: regmap instance for the device + * @buf: buffer containing the data to write + * @size: number of bytes to write + * @offset: starting register address + * + * Attempts to write a block of data to the device with retries and a timeout. + * Some M24LR devices may NACK I2C writes while an internal write operation + * is in progress. This function retries the write operation with a short delay + * until it succeeds or the timeout is reached. + * + * Returns: + * Number of bytes written on success, + * -ETIMEDOUT if the write fails within the timeout window. + */ +static ssize_t m24lr_regmap_write(struct regmap *regmap, const u8 *buf, + size_t size, unsigned int offset) +{ + int err; + unsigned long timeout, write_time; + ssize_t ret = -ETIMEDOUT; + + timeout = jiffies + msecs_to_jiffies(M24LR_WRITE_TIMEOUT); + + do { + write_time = jiffies; + + err = regmap_bulk_write(regmap, offset, buf, size); + if (!err) { + ret = size; + break; + } + + usleep_range(1000, 2000); + } while (time_before(write_time, timeout)); + + return ret; +} + +static ssize_t m24lr_read(struct m24lr *m24lr, u8 *buf, size_t size, + unsigned int offset, bool is_eeprom) +{ + struct regmap *regmap; + ssize_t ret; + + if (is_eeprom) + regmap = m24lr->eeprom_regmap; + else + regmap = m24lr->ctl_regmap; + + mutex_lock(&m24lr->lock); + ret = m24lr_regmap_read(regmap, buf, size, offset); + mutex_unlock(&m24lr->lock); + + return ret; +} + +/** + * m24lr_write - write buffer to M24LR device with page alignment handling + * @m24lr: pointer to driver context + * @buf: data buffer to write + * @size: number of bytes to write + * @offset: target register address in the device + * @is_eeprom: true if the write should target the EEPROM, + * false if it should target the system parameters sector. + * + * Writes data to the M24LR device using regmap, split into chunks no larger + * than page_size to respect device-specific write limitations (e.g., page + * size or I2C hold-time concerns). Each chunk is aligned to the page boundary + * defined by page_size. + * + * Returns: + * Total number of bytes written on success, + * A negative error code if any write fails. + */ +static ssize_t m24lr_write(struct m24lr *m24lr, const u8 *buf, size_t size, + unsigned int offset, bool is_eeprom) +{ + unsigned int n, next_sector; + struct regmap *regmap; + ssize_t ret = 0; + ssize_t err; + + if (is_eeprom) + regmap = m24lr->eeprom_regmap; + else + regmap = m24lr->ctl_regmap; + + n = min_t(unsigned int, size, m24lr->page_size); + next_sector = roundup(offset + 1, m24lr->page_size); + if (offset + n > next_sector) + n = next_sector - offset; + + mutex_lock(&m24lr->lock); + while (n) { + err = m24lr_regmap_write(regmap, buf + offset, n, offset); + if (IS_ERR_VALUE(err)) { + if (!ret) + ret = err; + + break; + } + + offset += n; + size -= n; + ret += n; + n = min_t(unsigned int, size, m24lr->page_size); + } + mutex_unlock(&m24lr->lock); + + return ret; +} + +/** + * m24lr_write_pass - Write password to M24LR043-R using secure format + * @m24lr: Pointer to device control structure + * @buf: Input buffer containing hex-encoded password + * @count: Number of bytes in @buf + * @code: Operation code to embed between password copies + * + * This function parses a 4-byte password, encodes it in big-endian format, + * and constructs a 9-byte sequence of the form: + * + * [BE(password), code, BE(password)] + * + * The result is written to register 0x0900 (2304), which is the password + * register in M24LR04E-R chip. + * + * Return: Number of bytes written on success, or negative error code on failure + */ +static ssize_t m24lr_write_pass(struct m24lr *m24lr, const char *buf, + size_t count, u8 code) +{ + __be32 be_pass; + u8 output[9]; + ssize_t ret; + u32 pass; + int err; + + if (!count) + return -EINVAL; + + if (count > 8) + return -EINVAL; + + err = kstrtou32(buf, 16, &pass); + if (err) + return err; + + be_pass = cpu_to_be32(pass); + + memcpy(output, &be_pass, sizeof(be_pass)); + output[4] = code; + memcpy(output + 5, &be_pass, sizeof(be_pass)); + + mutex_lock(&m24lr->lock); + ret = m24lr_regmap_write(m24lr->ctl_regmap, output, 9, 2304); + mutex_unlock(&m24lr->lock); + + return ret; +} + +static ssize_t m24lr_read_reg_le(struct m24lr *m24lr, u64 *val, + unsigned int reg_addr, + unsigned int reg_size) +{ + ssize_t ret; + __le64 input = 0; + + ret = m24lr_read(m24lr, (u8 *)&input, reg_size, reg_addr, false); + if (IS_ERR_VALUE(ret)) + return ret; + + if (ret != reg_size) + return -EINVAL; + + switch (reg_size) { + case 1: + *val = *(u8 *)&input; + break; + case 2: + *val = le16_to_cpu((__le16)input); + break; + case 4: + *val = le32_to_cpu((__le32)input); + break; + case 8: + *val = le64_to_cpu((__le64)input); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int m24lr_nvmem_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + ssize_t err; + struct m24lr *m24lr = priv; + + if (!bytes) + return bytes; + + if (offset + bytes > m24lr->eeprom_size) + return -EINVAL; + + err = m24lr_read(m24lr, val, bytes, offset, true); + if (IS_ERR_VALUE(err)) + return err; + + return 0; +} + +static int m24lr_nvmem_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + ssize_t err; + struct m24lr *m24lr = priv; + + if (!bytes) + return -EINVAL; + + if (offset + bytes > m24lr->eeprom_size) + return -EINVAL; + + err = m24lr_write(m24lr, val, bytes, offset, true); + if (IS_ERR_VALUE(err)) + return err; + + return 0; +} + +static ssize_t m24lr_ctl_sss_read(struct file *filep, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t offset, size_t count) +{ + struct m24lr *m24lr = attr->private; + + if (!count) + return count; + + if (size_add(offset, count) > m24lr->sss_len) + return -EINVAL; + + return m24lr_read(m24lr, buf, count, offset, false); +} + +static ssize_t m24lr_ctl_sss_write(struct file *filep, struct kobject *kobj, + const struct bin_attribute *attr, char *buf, + loff_t offset, size_t count) +{ + struct m24lr *m24lr = attr->private; + + if (!count) + return -EINVAL; + + if (size_add(offset, count) > m24lr->sss_len) + return -EINVAL; + + return m24lr_write(m24lr, buf, count, offset, false); +} +static BIN_ATTR(sss, 0600, m24lr_ctl_sss_read, m24lr_ctl_sss_write, 0); + +static ssize_t new_pass_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return m24lr_write_pass(m24lr, buf, count, 7); +} +static DEVICE_ATTR_WO(new_pass); + +static ssize_t unlock_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return m24lr_write_pass(m24lr, buf, count, 9); +} +static DEVICE_ATTR_WO(unlock); + +static ssize_t uid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return sysfs_emit(buf, "%llx\n", m24lr->uid); +} +static DEVICE_ATTR_RO(uid); + +static ssize_t total_sectors_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct m24lr *m24lr = i2c_get_clientdata(to_i2c_client(dev)); + + return sysfs_emit(buf, "%x\n", m24lr->sss_len); +} +static DEVICE_ATTR_RO(total_sectors); + +static struct attribute *m24lr_ctl_dev_attrs[] = { + &dev_attr_unlock.attr, + &dev_attr_new_pass.attr, + &dev_attr_uid.attr, + &dev_attr_total_sectors.attr, + NULL, +}; + +static const struct m24lr_chip *m24lr_get_chip(struct device *dev) +{ + const struct m24lr_chip *ret; + const struct i2c_device_id *id; + + id = i2c_match_id(m24lr_ids, to_i2c_client(dev)); + + if (dev->of_node && of_match_device(m24lr_of_match, dev)) + ret = of_device_get_match_data(dev); + else if (id) + ret = (void *)id->driver_data; + else + ret = acpi_device_get_match_data(dev); + + return ret; +} + +static int m24lr_probe(struct i2c_client *client) +{ + struct regmap_config eeprom_regmap_conf = {0}; + struct nvmem_config nvmem_conf = {0}; + struct device *dev = &client->dev; + struct i2c_client *eeprom_client; + const struct m24lr_chip *chip; + struct regmap *eeprom_regmap; + struct nvmem_device *nvmem; + struct regmap *ctl_regmap; + struct m24lr *m24lr; + u32 regs[2]; + long err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -EOPNOTSUPP; + + chip = m24lr_get_chip(dev); + if (!chip) + return -ENODEV; + + m24lr = devm_kzalloc(dev, sizeof(struct m24lr), GFP_KERNEL); + if (!m24lr) + return -ENOMEM; + + err = device_property_read_u32_array(dev, "reg", regs, ARRAY_SIZE(regs)); + if (err) + return dev_err_probe(dev, err, "Failed to read 'reg' property\n"); + + /* Create a second I2C client for the eeprom interface */ + eeprom_client = devm_i2c_new_dummy_device(dev, client->adapter, regs[1]); + if (IS_ERR(eeprom_client)) + return dev_err_probe(dev, PTR_ERR(eeprom_client), + "Failed to create dummy I2C client for the EEPROM\n"); + + ctl_regmap = devm_regmap_init_i2c(client, &m24lr_ctl_regmap_conf); + if (IS_ERR(ctl_regmap)) + return dev_err_probe(dev, PTR_ERR(ctl_regmap), + "Failed to init regmap\n"); + + eeprom_regmap_conf.name = "m24lr_eeprom"; + eeprom_regmap_conf.reg_bits = 16; + eeprom_regmap_conf.val_bits = 8; + eeprom_regmap_conf.disable_locking = true; + eeprom_regmap_conf.max_register = chip->eeprom_size - 1; + + eeprom_regmap = devm_regmap_init_i2c(eeprom_client, + &eeprom_regmap_conf); + if (IS_ERR(eeprom_regmap)) + return dev_err_probe(dev, PTR_ERR(eeprom_regmap), + "Failed to init regmap\n"); + + mutex_init(&m24lr->lock); + m24lr->sss_len = chip->sss_len; + m24lr->page_size = chip->page_size; + m24lr->eeprom_size = chip->eeprom_size; + m24lr->eeprom_regmap = eeprom_regmap; + m24lr->ctl_regmap = ctl_regmap; + + nvmem_conf.dev = &eeprom_client->dev; + nvmem_conf.owner = THIS_MODULE; + nvmem_conf.type = NVMEM_TYPE_EEPROM; + nvmem_conf.reg_read = m24lr_nvmem_read; + nvmem_conf.reg_write = m24lr_nvmem_write; + nvmem_conf.size = chip->eeprom_size; + nvmem_conf.word_size = 1; + nvmem_conf.stride = 1; + nvmem_conf.priv = m24lr; + + nvmem = devm_nvmem_register(dev, &nvmem_conf); + if (IS_ERR(nvmem)) + return dev_err_probe(dev, PTR_ERR(nvmem), + "Failed to register nvmem\n"); + + i2c_set_clientdata(client, m24lr); + i2c_set_clientdata(eeprom_client, m24lr); + + bin_attr_sss.size = chip->sss_len; + bin_attr_sss.private = m24lr; + err = sysfs_create_bin_file(&dev->kobj, &bin_attr_sss); + if (err) + return dev_err_probe(dev, err, + "Failed to create sss bin file\n"); + + /* test by reading the uid, if success store it */ + err = m24lr_read_reg_le(m24lr, &m24lr->uid, 2324, sizeof(m24lr->uid)); + if (IS_ERR_VALUE(err)) + goto remove_bin_file; + + return 0; + +remove_bin_file: + sysfs_remove_bin_file(&dev->kobj, &bin_attr_sss); + + return err; +} + +static void m24lr_remove(struct i2c_client *client) +{ + sysfs_remove_bin_file(&client->dev.kobj, &bin_attr_sss); +} + +ATTRIBUTE_GROUPS(m24lr_ctl_dev); + +static struct i2c_driver m24lr_driver = { + .driver = { + .name = "m24lr", + .of_match_table = m24lr_of_match, + .dev_groups = m24lr_ctl_dev_groups, + }, + .probe = m24lr_probe, + .remove = m24lr_remove, + .id_table = m24lr_ids, +}; +module_i2c_driver(m24lr_driver); + +MODULE_AUTHOR("Abd-Alrhman Masalkhi"); +MODULE_DESCRIPTION("st m24lr control driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/max77759-nvmem.c b/drivers/nvmem/max77759-nvmem.c new file mode 100644 index 000000000..283000ec3 --- /dev/null +++ b/drivers/nvmem/max77759-nvmem.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright 2020 Google Inc +// Copyright 2025 Linaro Ltd. +// +// NVMEM driver for Maxim MAX77759 + +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/device/driver.h> +#include <linux/err.h> +#include <linux/mfd/max77759.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/string.h> + +#define MAX77759_NVMEM_OPCODE_HEADER_LEN 3 +/* + * NVMEM commands have a three byte header (which becomes part of the command), + * so we need to subtract that. + */ +#define MAX77759_NVMEM_SIZE (MAX77759_MAXQ_OPCODE_MAXLENGTH \ + - MAX77759_NVMEM_OPCODE_HEADER_LEN) + +struct max77759_nvmem { + struct device *dev; + struct max77759 *max77759; +}; + +static int max77759_nvmem_reg_read(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct max77759_nvmem *nvmem = priv; + DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, + MAX77759_NVMEM_OPCODE_HEADER_LEN); + DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, + MAX77759_MAXQ_OPCODE_MAXLENGTH); + int ret; + + cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_READ; + cmd->cmd[1] = offset; + cmd->cmd[2] = bytes; + rsp->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN; + + ret = max77759_maxq_command(nvmem->max77759, cmd, rsp); + if (ret < 0) + return ret; + + if (memcmp(cmd->cmd, rsp->rsp, MAX77759_NVMEM_OPCODE_HEADER_LEN)) { + dev_warn(nvmem->dev, "protocol error (read)\n"); + return -EIO; + } + + memcpy(val, &rsp->rsp[MAX77759_NVMEM_OPCODE_HEADER_LEN], bytes); + + return 0; +} + +static int max77759_nvmem_reg_write(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct max77759_nvmem *nvmem = priv; + DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, + MAX77759_MAXQ_OPCODE_MAXLENGTH); + DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, + MAX77759_MAXQ_OPCODE_MAXLENGTH); + int ret; + + cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE; + cmd->cmd[1] = offset; + cmd->cmd[2] = bytes; + memcpy(&cmd->cmd[MAX77759_NVMEM_OPCODE_HEADER_LEN], val, bytes); + cmd->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN; + rsp->length = cmd->length; + + ret = max77759_maxq_command(nvmem->max77759, cmd, rsp); + if (ret < 0) + return ret; + + if (memcmp(cmd->cmd, rsp->rsp, cmd->length)) { + dev_warn(nvmem->dev, "protocol error (write)\n"); + return -EIO; + } + + return 0; +} + +static int max77759_nvmem_probe(struct platform_device *pdev) +{ + struct nvmem_config config = { + .dev = &pdev->dev, + .name = dev_name(&pdev->dev), + .id = NVMEM_DEVID_NONE, + .type = NVMEM_TYPE_EEPROM, + .ignore_wp = true, + .size = MAX77759_NVMEM_SIZE, + .word_size = sizeof(u8), + .stride = sizeof(u8), + .reg_read = max77759_nvmem_reg_read, + .reg_write = max77759_nvmem_reg_write, + }; + struct max77759_nvmem *nvmem; + + nvmem = devm_kzalloc(&pdev->dev, sizeof(*nvmem), GFP_KERNEL); + if (!nvmem) + return -ENOMEM; + + nvmem->dev = &pdev->dev; + nvmem->max77759 = dev_get_drvdata(pdev->dev.parent); + + config.priv = nvmem; + + return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config)); +} + +static const struct of_device_id max77759_nvmem_of_id[] = { + { .compatible = "maxim,max77759-nvmem", }, + { } +}; +MODULE_DEVICE_TABLE(of, max77759_nvmem_of_id); + +static const struct platform_device_id max77759_nvmem_platform_id[] = { + { "max77759-nvmem", }, + { } +}; +MODULE_DEVICE_TABLE(platform, max77759_nvmem_platform_id); + +static struct platform_driver max77759_nvmem_driver = { + .driver = { + .name = "max77759-nvmem", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = max77759_nvmem_of_id, + }, + .probe = max77759_nvmem_probe, + .id_table = max77759_nvmem_platform_id, +}; + +module_platform_driver(max77759_nvmem_driver); + +MODULE_AUTHOR("André Draszik <andre.draszik@linaro.org>"); +MODULE_DESCRIPTION("NVMEM driver for Maxim MAX77759"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/meson-efuse.c b/drivers/nvmem/meson-efuse.c new file mode 100644 index 000000000..d7f9ac99a --- /dev/null +++ b/drivers/nvmem/meson-efuse.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Amlogic Meson GX eFuse Driver + * + * Copyright (c) 2016 Endless Computers, Inc. + * Author: Carlo Caione <carlo@endlessm.com> + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include <linux/firmware/meson/meson_sm.h> + +static int meson_efuse_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct meson_sm_firmware *fw = context; + int ret; + + ret = meson_sm_call_read(fw, (u8 *)val, bytes, SM_EFUSE_READ, offset, + bytes, 0, 0, 0); + + return ret < 0 ? ret : 0; +} + +static int meson_efuse_write(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct meson_sm_firmware *fw = context; + int ret; + + ret = meson_sm_call_write(fw, (u8 *)val, bytes, SM_EFUSE_WRITE, offset, + bytes, 0, 0, 0); + + return ret < 0 ? ret : 0; +} + +static const struct of_device_id meson_efuse_match[] = { + { .compatible = "amlogic,meson-gxbb-efuse", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, meson_efuse_match); + +static int meson_efuse_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct meson_sm_firmware *fw; + struct nvmem_device *nvmem; + struct nvmem_config *econfig; + struct clk *clk; + unsigned int size; + struct device_node *sm_np __free(device_node) = + of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0); + + if (!sm_np) { + dev_err(&pdev->dev, "no secure-monitor node\n"); + return -ENODEV; + } + + fw = meson_sm_get(sm_np); + if (!fw) + return -EPROBE_DEFER; + + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to get efuse gate"); + + if (meson_sm_call(fw, SM_EFUSE_USER_MAX, &size, 0, 0, 0, 0, 0) < 0) { + dev_err(dev, "failed to get max user"); + return -EINVAL; + } + + econfig = devm_kzalloc(dev, sizeof(*econfig), GFP_KERNEL); + if (!econfig) + return -ENOMEM; + + econfig->dev = dev; + econfig->name = dev_name(dev); + econfig->add_legacy_fixed_of_cells = true; + econfig->stride = 1; + econfig->word_size = 1; + econfig->reg_read = meson_efuse_read; + econfig->reg_write = meson_efuse_write; + econfig->size = size; + econfig->priv = fw; + + nvmem = devm_nvmem_register(&pdev->dev, econfig); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static struct platform_driver meson_efuse_driver = { + .probe = meson_efuse_probe, + .driver = { + .name = "meson-efuse", + .of_match_table = meson_efuse_match, + }, +}; + +module_platform_driver(meson_efuse_driver); + +MODULE_AUTHOR("Carlo Caione <carlo@endlessm.com>"); +MODULE_DESCRIPTION("Amlogic Meson GX NVMEM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/meson-mx-efuse.c b/drivers/nvmem/meson-mx-efuse.c new file mode 100644 index 000000000..8a16f5f02 --- /dev/null +++ b/drivers/nvmem/meson-mx-efuse.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Amlogic Meson6, Meson8 and Meson8b eFuse Driver + * + * Copyright (c) 2017 Martin Blumenstingl <martin.blumenstingl@googlemail.com> + */ + +#include <linux/bitfield.h> +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/sizes.h> +#include <linux/slab.h> + +#define MESON_MX_EFUSE_CNTL1 0x04 +#define MESON_MX_EFUSE_CNTL1_PD_ENABLE BIT(27) +#define MESON_MX_EFUSE_CNTL1_AUTO_RD_BUSY BIT(26) +#define MESON_MX_EFUSE_CNTL1_AUTO_RD_START BIT(25) +#define MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE BIT(24) +#define MESON_MX_EFUSE_CNTL1_BYTE_WR_DATA GENMASK(23, 16) +#define MESON_MX_EFUSE_CNTL1_AUTO_WR_BUSY BIT(14) +#define MESON_MX_EFUSE_CNTL1_AUTO_WR_START BIT(13) +#define MESON_MX_EFUSE_CNTL1_AUTO_WR_ENABLE BIT(12) +#define MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET BIT(11) +#define MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK GENMASK(10, 0) + +#define MESON_MX_EFUSE_CNTL2 0x08 + +#define MESON_MX_EFUSE_CNTL4 0x10 +#define MESON_MX_EFUSE_CNTL4_ENCRYPT_ENABLE BIT(10) + +struct meson_mx_efuse_platform_data { + const char *name; + unsigned int word_size; +}; + +struct meson_mx_efuse { + void __iomem *base; + struct clk *core_clk; + struct nvmem_config config; +}; + +static void meson_mx_efuse_mask_bits(struct meson_mx_efuse *efuse, u32 reg, + u32 mask, u32 set) +{ + u32 data; + + data = readl(efuse->base + reg); + data &= ~mask; + data |= (set & mask); + + writel(data, efuse->base + reg); +} + +static int meson_mx_efuse_hw_enable(struct meson_mx_efuse *efuse) +{ + int err; + + err = clk_prepare_enable(efuse->core_clk); + if (err) + return err; + + /* power up the efuse */ + meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, + MESON_MX_EFUSE_CNTL1_PD_ENABLE, 0); + + meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL4, + MESON_MX_EFUSE_CNTL4_ENCRYPT_ENABLE, 0); + + return 0; +} + +static void meson_mx_efuse_hw_disable(struct meson_mx_efuse *efuse) +{ + meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, + MESON_MX_EFUSE_CNTL1_PD_ENABLE, + MESON_MX_EFUSE_CNTL1_PD_ENABLE); + + clk_disable_unprepare(efuse->core_clk); +} + +static int meson_mx_efuse_read_addr(struct meson_mx_efuse *efuse, + unsigned int addr, u32 *value) +{ + int err; + u32 regval; + + /* write the address to read */ + regval = FIELD_PREP(MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK, addr); + meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, + MESON_MX_EFUSE_CNTL1_BYTE_ADDR_MASK, regval); + + /* inform the hardware that we changed the address */ + meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, + MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET, + MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET); + meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, + MESON_MX_EFUSE_CNTL1_BYTE_ADDR_SET, 0); + + /* start the read process */ + meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, + MESON_MX_EFUSE_CNTL1_AUTO_RD_START, + MESON_MX_EFUSE_CNTL1_AUTO_RD_START); + meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, + MESON_MX_EFUSE_CNTL1_AUTO_RD_START, 0); + + /* + * perform a dummy read to ensure that the HW has the RD_BUSY bit set + * when polling for the status below. + */ + readl(efuse->base + MESON_MX_EFUSE_CNTL1); + + err = readl_poll_timeout_atomic(efuse->base + MESON_MX_EFUSE_CNTL1, + regval, + (!(regval & MESON_MX_EFUSE_CNTL1_AUTO_RD_BUSY)), + 1, 1000); + if (err) { + dev_err(efuse->config.dev, + "Timeout while reading efuse address %u\n", addr); + return err; + } + + *value = readl(efuse->base + MESON_MX_EFUSE_CNTL2); + + return 0; +} + +static int meson_mx_efuse_read(void *context, unsigned int offset, + void *buf, size_t bytes) +{ + struct meson_mx_efuse *efuse = context; + u32 tmp; + int err, i, addr; + + err = meson_mx_efuse_hw_enable(efuse); + if (err) + return err; + + meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, + MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE, + MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE); + + for (i = 0; i < bytes; i += efuse->config.word_size) { + addr = (offset + i) / efuse->config.word_size; + + err = meson_mx_efuse_read_addr(efuse, addr, &tmp); + if (err) + break; + + memcpy(buf + i, &tmp, + min_t(size_t, bytes - i, efuse->config.word_size)); + } + + meson_mx_efuse_mask_bits(efuse, MESON_MX_EFUSE_CNTL1, + MESON_MX_EFUSE_CNTL1_AUTO_RD_ENABLE, 0); + + meson_mx_efuse_hw_disable(efuse); + + return err; +} + +static const struct meson_mx_efuse_platform_data meson6_efuse_data = { + .name = "meson6-efuse", + .word_size = 1, +}; + +static const struct meson_mx_efuse_platform_data meson8_efuse_data = { + .name = "meson8-efuse", + .word_size = 4, +}; + +static const struct meson_mx_efuse_platform_data meson8b_efuse_data = { + .name = "meson8b-efuse", + .word_size = 4, +}; + +static const struct of_device_id meson_mx_efuse_match[] = { + { .compatible = "amlogic,meson6-efuse", .data = &meson6_efuse_data }, + { .compatible = "amlogic,meson8-efuse", .data = &meson8_efuse_data }, + { .compatible = "amlogic,meson8b-efuse", .data = &meson8b_efuse_data }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, meson_mx_efuse_match); + +static int meson_mx_efuse_probe(struct platform_device *pdev) +{ + const struct meson_mx_efuse_platform_data *drvdata; + struct meson_mx_efuse *efuse; + struct nvmem_device *nvmem; + + drvdata = of_device_get_match_data(&pdev->dev); + if (!drvdata) + return -EINVAL; + + efuse = devm_kzalloc(&pdev->dev, sizeof(*efuse), GFP_KERNEL); + if (!efuse) + return -ENOMEM; + + efuse->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(efuse->base)) + return PTR_ERR(efuse->base); + + efuse->config.name = drvdata->name; + efuse->config.owner = THIS_MODULE; + efuse->config.dev = &pdev->dev; + efuse->config.priv = efuse; + efuse->config.add_legacy_fixed_of_cells = true; + efuse->config.stride = drvdata->word_size; + efuse->config.word_size = drvdata->word_size; + efuse->config.size = SZ_512; + efuse->config.read_only = true; + efuse->config.reg_read = meson_mx_efuse_read; + + efuse->core_clk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(efuse->core_clk)) { + dev_err(&pdev->dev, "Failed to get core clock\n"); + return PTR_ERR(efuse->core_clk); + } + + nvmem = devm_nvmem_register(&pdev->dev, &efuse->config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static struct platform_driver meson_mx_efuse_driver = { + .probe = meson_mx_efuse_probe, + .driver = { + .name = "meson-mx-efuse", + .of_match_table = meson_mx_efuse_match, + }, +}; + +module_platform_driver(meson_mx_efuse_driver); + +MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); +MODULE_DESCRIPTION("Amlogic Meson MX eFuse NVMEM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/microchip-otpc.c b/drivers/nvmem/microchip-otpc.c new file mode 100644 index 000000000..df979e854 --- /dev/null +++ b/drivers/nvmem/microchip-otpc.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * OTP Memory controller + * + * Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries + * + * Author: Claudiu Beznea <claudiu.beznea@microchip.com> + */ + +#include <linux/bitfield.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#define MCHP_OTPC_CR (0x0) +#define MCHP_OTPC_CR_READ BIT(6) +#define MCHP_OTPC_MR (0x4) +#define MCHP_OTPC_MR_ADDR GENMASK(31, 16) +#define MCHP_OTPC_AR (0x8) +#define MCHP_OTPC_SR (0xc) +#define MCHP_OTPC_SR_READ BIT(6) +#define MCHP_OTPC_HR (0x20) +#define MCHP_OTPC_HR_SIZE GENMASK(15, 8) +#define MCHP_OTPC_DR (0x24) + +#define MCHP_OTPC_NAME "mchp-otpc" +#define MCHP_OTPC_SIZE (11 * 1024) + +/** + * struct mchp_otpc - OTPC private data structure + * @base: base address + * @dev: struct device pointer + * @packets: list of packets in OTP memory + * @npackets: number of packets in OTP memory + */ +struct mchp_otpc { + void __iomem *base; + struct device *dev; + struct list_head packets; + u32 npackets; +}; + +/** + * struct mchp_otpc_packet - OTPC packet data structure + * @list: list head + * @id: packet ID + * @offset: packet offset (in words) in OTP memory + */ +struct mchp_otpc_packet { + struct list_head list; + u32 id; + u32 offset; +}; + +static struct mchp_otpc_packet *mchp_otpc_id_to_packet(struct mchp_otpc *otpc, + u32 id) +{ + struct mchp_otpc_packet *packet; + + if (id >= otpc->npackets) + return NULL; + + list_for_each_entry(packet, &otpc->packets, list) { + if (packet->id == id) + return packet; + } + + return NULL; +} + +static int mchp_otpc_prepare_read(struct mchp_otpc *otpc, + unsigned int offset) +{ + u32 tmp; + + /* Set address. */ + tmp = readl_relaxed(otpc->base + MCHP_OTPC_MR); + tmp &= ~MCHP_OTPC_MR_ADDR; + tmp |= FIELD_PREP(MCHP_OTPC_MR_ADDR, offset); + writel_relaxed(tmp, otpc->base + MCHP_OTPC_MR); + + /* Set read. */ + tmp = readl_relaxed(otpc->base + MCHP_OTPC_CR); + tmp |= MCHP_OTPC_CR_READ; + writel_relaxed(tmp, otpc->base + MCHP_OTPC_CR); + + /* Wait for packet to be transferred into temporary buffers. */ + return read_poll_timeout(readl_relaxed, tmp, !(tmp & MCHP_OTPC_SR_READ), + 10000, 2000, false, otpc->base + MCHP_OTPC_SR); +} + +/* + * OTPC memory is organized into packets. Each packets contains a header and + * a payload. Header is 4 bytes long and contains the size of the payload. + * Payload size varies. The memory footprint is something as follows: + * + * Memory offset Memory footprint Packet ID + * ------------- ---------------- --------- + * + * 0x0 +------------+ <-- packet 0 + * | header 0 | + * 0x4 +------------+ + * | payload 0 | + * . . + * . ... . + * . . + * offset1 +------------+ <-- packet 1 + * | header 1 | + * offset1 + 0x4 +------------+ + * | payload 1 | + * . . + * . ... . + * . . + * offset2 +------------+ <-- packet 2 + * . . + * . ... . + * . . + * offsetN +------------+ <-- packet N + * | header N | + * offsetN + 0x4 +------------+ + * | payload N | + * . . + * . ... . + * . . + * +------------+ + * + * where offset1, offset2, offsetN depends on the size of payload 0, payload 1, + * payload N-1. + * + * The access to memory is done on a per packet basis: the control registers + * need to be updated with an offset address (within a packet range) and the + * data registers will be update by controller with information contained by + * that packet. E.g. if control registers are updated with any address within + * the range [offset1, offset2) the data registers are updated by controller + * with packet 1. Header data is accessible though MCHP_OTPC_HR register. + * Payload data is accessible though MCHP_OTPC_DR and MCHP_OTPC_AR registers. + * There is no direct mapping b/w the offset requested by software and the + * offset returned by hardware. + * + * For this, the read function will return the first requested bytes in the + * packet. The user will have to be aware of the memory footprint before doing + * the read request. + */ +static int mchp_otpc_read(void *priv, unsigned int off, void *val, + size_t bytes) +{ + struct mchp_otpc *otpc = priv; + struct mchp_otpc_packet *packet; + u32 *buf = val; + u32 offset; + size_t len = 0; + int ret, payload_size; + + /* + * We reach this point with off being multiple of stride = 4 to + * be able to cross the subsystem. Inside the driver we use continuous + * unsigned integer numbers for packet id, thus divide off by 4 + * before passing it to mchp_otpc_id_to_packet(). + */ + packet = mchp_otpc_id_to_packet(otpc, off / 4); + if (!packet) + return -EINVAL; + offset = packet->offset; + + while (len < bytes) { + ret = mchp_otpc_prepare_read(otpc, offset); + if (ret) + return ret; + + /* Read and save header content. */ + *buf++ = readl_relaxed(otpc->base + MCHP_OTPC_HR); + len += sizeof(*buf); + offset++; + if (len >= bytes) + break; + + /* Read and save payload content. */ + payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, *(buf - 1)); + writel_relaxed(0UL, otpc->base + MCHP_OTPC_AR); + do { + *buf++ = readl_relaxed(otpc->base + MCHP_OTPC_DR); + len += sizeof(*buf); + offset++; + payload_size--; + } while (payload_size >= 0 && len < bytes); + } + + return 0; +} + +static int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size) +{ + struct mchp_otpc_packet *packet; + u32 word, word_pos = 0, id = 0, npackets = 0, payload_size; + int ret; + + INIT_LIST_HEAD(&otpc->packets); + *size = 0; + + while (*size < MCHP_OTPC_SIZE) { + ret = mchp_otpc_prepare_read(otpc, word_pos); + if (ret) + return ret; + + word = readl_relaxed(otpc->base + MCHP_OTPC_HR); + payload_size = FIELD_GET(MCHP_OTPC_HR_SIZE, word); + if (!payload_size) + break; + + packet = devm_kzalloc(otpc->dev, sizeof(*packet), GFP_KERNEL); + if (!packet) + return -ENOMEM; + + packet->id = id++; + packet->offset = word_pos; + INIT_LIST_HEAD(&packet->list); + list_add_tail(&packet->list, &otpc->packets); + + /* Count size by adding header and paload sizes. */ + *size += 4 * (payload_size + 1); + /* Next word: this packet (header, payload) position + 1. */ + word_pos += payload_size + 2; + + npackets++; + } + + otpc->npackets = npackets; + + return 0; +} + +static struct nvmem_config mchp_nvmem_config = { + .name = MCHP_OTPC_NAME, + .type = NVMEM_TYPE_OTP, + .read_only = true, + .word_size = 4, + .stride = 4, + .reg_read = mchp_otpc_read, +}; + +static int mchp_otpc_probe(struct platform_device *pdev) +{ + struct nvmem_device *nvmem; + struct mchp_otpc *otpc; + u32 size; + int ret; + + otpc = devm_kzalloc(&pdev->dev, sizeof(*otpc), GFP_KERNEL); + if (!otpc) + return -ENOMEM; + + otpc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(otpc->base)) + return PTR_ERR(otpc->base); + + otpc->dev = &pdev->dev; + ret = mchp_otpc_init_packets_list(otpc, &size); + if (ret) + return ret; + + mchp_nvmem_config.dev = otpc->dev; + mchp_nvmem_config.add_legacy_fixed_of_cells = true; + mchp_nvmem_config.size = size; + mchp_nvmem_config.priv = otpc; + nvmem = devm_nvmem_register(&pdev->dev, &mchp_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id __maybe_unused mchp_otpc_ids[] = { + { .compatible = "microchip,sama7g5-otpc", }, + { }, +}; +MODULE_DEVICE_TABLE(of, mchp_otpc_ids); + +static struct platform_driver mchp_otpc_driver = { + .probe = mchp_otpc_probe, + .driver = { + .name = MCHP_OTPC_NAME, + .of_match_table = of_match_ptr(mchp_otpc_ids), + }, +}; +module_platform_driver(mchp_otpc_driver); + +MODULE_AUTHOR("Claudiu Beznea <claudiu.beznea@microchip.com>"); +MODULE_DESCRIPTION("Microchip SAMA7G5 OTPC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/mtk-efuse.c b/drivers/nvmem/mtk-efuse.c new file mode 100644 index 000000000..00a84ea96 --- /dev/null +++ b/drivers/nvmem/mtk-efuse.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 MediaTek Inc. + * Author: Andrew-CT Chen <andrew-ct.chen@mediatek.com> + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/property.h> + +struct mtk_efuse_pdata { + bool uses_post_processing; +}; + +struct mtk_efuse_priv { + void __iomem *base; +}; + +static int mtk_reg_read(void *context, + unsigned int reg, void *_val, size_t bytes) +{ + struct mtk_efuse_priv *priv = context; + void __iomem *addr = priv->base + reg; + u8 *val = _val; + int i; + + for (i = 0; i < bytes; i++, val++) + *val = readb(addr + i); + + return 0; +} + +static int mtk_efuse_gpu_speedbin_pp(void *context, const char *id, int index, + unsigned int offset, void *data, size_t bytes) +{ + u8 *val = data; + + if (val[0] < 8) + val[0] = BIT(val[0]); + + return 0; +} + +static void mtk_efuse_fixup_dt_cell_info(struct nvmem_device *nvmem, + struct nvmem_cell_info *cell) +{ + size_t sz = strlen(cell->name); + + /* + * On some SoCs, the GPU speedbin is not read as bitmask but as + * a number with range [0-7] (max 3 bits): post process to use + * it in OPP tables to describe supported-hw. + */ + if (cell->nbits <= 3 && + strncmp(cell->name, "gpu-speedbin", min(sz, strlen("gpu-speedbin"))) == 0) + cell->read_post_process = mtk_efuse_gpu_speedbin_pp; +} + +static int mtk_efuse_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct nvmem_device *nvmem; + struct nvmem_config econfig = {}; + struct mtk_efuse_priv *priv; + const struct mtk_efuse_pdata *pdata; + struct platform_device *socinfo; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + pdata = device_get_match_data(dev); + econfig.add_legacy_fixed_of_cells = true; + econfig.stride = 1; + econfig.word_size = 1; + econfig.reg_read = mtk_reg_read; + econfig.size = resource_size(res); + econfig.priv = priv; + econfig.dev = dev; + if (pdata->uses_post_processing) + econfig.fixup_dt_cell_info = &mtk_efuse_fixup_dt_cell_info; + nvmem = devm_nvmem_register(dev, &econfig); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + socinfo = platform_device_register_data(&pdev->dev, "mtk-socinfo", + PLATFORM_DEVID_AUTO, NULL, 0); + if (IS_ERR(socinfo)) + dev_info(dev, "MediaTek SoC Information will be unavailable\n"); + + platform_set_drvdata(pdev, socinfo); + return 0; +} + +static const struct mtk_efuse_pdata mtk_mt8186_efuse_pdata = { + .uses_post_processing = true, +}; + +static const struct mtk_efuse_pdata mtk_efuse_pdata = { + .uses_post_processing = false, +}; + +static const struct of_device_id mtk_efuse_of_match[] = { + { .compatible = "mediatek,mt8173-efuse", .data = &mtk_efuse_pdata }, + { .compatible = "mediatek,mt8186-efuse", .data = &mtk_mt8186_efuse_pdata }, + { .compatible = "mediatek,efuse", .data = &mtk_efuse_pdata }, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, mtk_efuse_of_match); + +static void mtk_efuse_remove(struct platform_device *pdev) +{ + struct platform_device *socinfo = platform_get_drvdata(pdev); + + if (!IS_ERR_OR_NULL(socinfo)) + platform_device_unregister(socinfo); +} + +static struct platform_driver mtk_efuse_driver = { + .probe = mtk_efuse_probe, + .remove = mtk_efuse_remove, + .driver = { + .name = "mediatek,efuse", + .of_match_table = mtk_efuse_of_match, + }, +}; + +static int __init mtk_efuse_init(void) +{ + int ret; + + ret = platform_driver_register(&mtk_efuse_driver); + if (ret) { + pr_err("Failed to register efuse driver\n"); + return ret; + } + + return 0; +} + +static void __exit mtk_efuse_exit(void) +{ + return platform_driver_unregister(&mtk_efuse_driver); +} + +subsys_initcall(mtk_efuse_init); +module_exit(mtk_efuse_exit); + +MODULE_AUTHOR("Andrew-CT Chen <andrew-ct.chen@mediatek.com>"); +MODULE_DESCRIPTION("Mediatek EFUSE driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/mxs-ocotp.c b/drivers/nvmem/mxs-ocotp.c new file mode 100644 index 000000000..7b78f18f9 --- /dev/null +++ b/drivers/nvmem/mxs-ocotp.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Freescale MXS On-Chip OTP driver + * + * Copyright (C) 2015 Stefan Wahren <stefan.wahren@i2se.com> + * + * Based on the driver from Huang Shijie and Christoph G. Baumann + */ +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/stmp_device.h> + +/* OCOTP registers and bits */ + +#define BM_OCOTP_CTRL_RD_BANK_OPEN BIT(12) +#define BM_OCOTP_CTRL_ERROR BIT(9) +#define BM_OCOTP_CTRL_BUSY BIT(8) + +#define OCOTP_TIMEOUT 10000 +#define OCOTP_DATA_OFFSET 0x20 + +struct mxs_ocotp { + struct clk *clk; + void __iomem *base; + struct nvmem_device *nvmem; +}; + +static int mxs_ocotp_wait(struct mxs_ocotp *otp) +{ + int timeout = OCOTP_TIMEOUT; + unsigned int status = 0; + + while (timeout--) { + status = readl(otp->base); + + if (!(status & (BM_OCOTP_CTRL_BUSY | BM_OCOTP_CTRL_ERROR))) + break; + + cpu_relax(); + } + + if (status & BM_OCOTP_CTRL_BUSY) + return -EBUSY; + else if (status & BM_OCOTP_CTRL_ERROR) + return -EIO; + + return 0; +} + +static int mxs_ocotp_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct mxs_ocotp *otp = context; + u32 *buf = val; + int ret; + + ret = clk_enable(otp->clk); + if (ret) + return ret; + + writel(BM_OCOTP_CTRL_ERROR, otp->base + STMP_OFFSET_REG_CLR); + + ret = mxs_ocotp_wait(otp); + if (ret) + goto disable_clk; + + /* open OCOTP banks for read */ + writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_SET); + + /* approximately wait 33 hclk cycles */ + udelay(1); + + ret = mxs_ocotp_wait(otp); + if (ret) + goto close_banks; + + while (bytes) { + if ((offset < OCOTP_DATA_OFFSET) || (offset % 16)) { + /* fill up non-data register */ + *buf++ = 0; + } else { + *buf++ = readl(otp->base + offset); + } + + bytes -= 4; + offset += 4; + } + +close_banks: + /* close banks for power saving */ + writel(BM_OCOTP_CTRL_RD_BANK_OPEN, otp->base + STMP_OFFSET_REG_CLR); + +disable_clk: + clk_disable(otp->clk); + + return ret; +} + +static struct nvmem_config ocotp_config = { + .name = "mxs-ocotp", + .stride = 16, + .word_size = 4, + .reg_read = mxs_ocotp_read, +}; + +struct mxs_data { + int size; +}; + +static const struct mxs_data imx23_data = { + .size = 0x220, +}; + +static const struct mxs_data imx28_data = { + .size = 0x2a0, +}; + +static const struct of_device_id mxs_ocotp_match[] = { + { .compatible = "fsl,imx23-ocotp", .data = &imx23_data }, + { .compatible = "fsl,imx28-ocotp", .data = &imx28_data }, + { /* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, mxs_ocotp_match); + +static void mxs_ocotp_action(void *data) +{ + clk_unprepare(data); +} + +static int mxs_ocotp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct mxs_data *data; + struct mxs_ocotp *otp; + int ret; + + data = device_get_match_data(dev); + if (!data) + return -EINVAL; + + otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL); + if (!otp) + return -ENOMEM; + + otp->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(otp->base)) + return PTR_ERR(otp->base); + + otp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(otp->clk)) + return PTR_ERR(otp->clk); + + ret = clk_prepare(otp->clk); + if (ret < 0) { + dev_err(dev, "failed to prepare clk: %d\n", ret); + return ret; + } + + ret = devm_add_action_or_reset(&pdev->dev, mxs_ocotp_action, otp->clk); + if (ret) + return ret; + + ocotp_config.size = data->size; + ocotp_config.priv = otp; + ocotp_config.dev = dev; + otp->nvmem = devm_nvmem_register(dev, &ocotp_config); + if (IS_ERR(otp->nvmem)) + return PTR_ERR(otp->nvmem); + + platform_set_drvdata(pdev, otp); + + return 0; +} + +static struct platform_driver mxs_ocotp_driver = { + .probe = mxs_ocotp_probe, + .driver = { + .name = "mxs-ocotp", + .of_match_table = mxs_ocotp_match, + }, +}; + +module_platform_driver(mxs_ocotp_driver); +MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net"); +MODULE_DESCRIPTION("driver for OCOTP in i.MX23/i.MX28"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/nintendo-otp.c b/drivers/nvmem/nintendo-otp.c new file mode 100644 index 000000000..a4d986e45 --- /dev/null +++ b/drivers/nvmem/nintendo-otp.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Nintendo Wii and Wii U OTP driver + * + * This is a driver exposing the OTP of a Nintendo Wii or Wii U console. + * + * This memory contains common and per-console keys, signatures and + * related data required to access peripherals. + * + * Based on reversed documentation from https://wiiubrew.org/wiki/Hardware/OTP + * + * Copyright (C) 2021 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#define HW_OTPCMD 0 +#define HW_OTPDATA 4 +#define OTP_READ 0x80000000 +#define BANK_SIZE 128 +#define WORD_SIZE 4 + +struct nintendo_otp_priv { + void __iomem *regs; +}; + +struct nintendo_otp_devtype_data { + const char *name; + unsigned int num_banks; +}; + +static const struct nintendo_otp_devtype_data hollywood_otp_data = { + .name = "wii-otp", + .num_banks = 1, +}; + +static const struct nintendo_otp_devtype_data latte_otp_data = { + .name = "wiiu-otp", + .num_banks = 8, +}; + +static int nintendo_otp_reg_read(void *context, + unsigned int reg, void *_val, size_t bytes) +{ + struct nintendo_otp_priv *priv = context; + u32 *val = _val; + int words = bytes / WORD_SIZE; + u32 bank, addr; + + while (words--) { + bank = (reg / BANK_SIZE) << 8; + addr = (reg / WORD_SIZE) % (BANK_SIZE / WORD_SIZE); + iowrite32be(OTP_READ | bank | addr, priv->regs + HW_OTPCMD); + *val++ = ioread32be(priv->regs + HW_OTPDATA); + reg += WORD_SIZE; + } + + return 0; +} + +static const struct of_device_id nintendo_otp_of_table[] = { + { .compatible = "nintendo,hollywood-otp", .data = &hollywood_otp_data }, + { .compatible = "nintendo,latte-otp", .data = &latte_otp_data }, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, nintendo_otp_of_table); + +static int nintendo_otp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct nintendo_otp_devtype_data *data; + struct nvmem_device *nvmem; + struct nintendo_otp_priv *priv; + + struct nvmem_config config = { + .stride = WORD_SIZE, + .word_size = WORD_SIZE, + .reg_read = nintendo_otp_reg_read, + .read_only = true, + .root_only = true, + }; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->regs)) + return PTR_ERR(priv->regs); + + data = of_device_get_match_data(dev); + if (data) { + config.name = data->name; + config.size = data->num_banks * BANK_SIZE; + } + + config.dev = dev; + config.priv = priv; + + nvmem = devm_nvmem_register(dev, &config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static struct platform_driver nintendo_otp_driver = { + .probe = nintendo_otp_probe, + .driver = { + .name = "nintendo-otp", + .of_match_table = nintendo_otp_of_table, + }, +}; +module_platform_driver(nintendo_otp_driver); +MODULE_AUTHOR("Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>"); +MODULE_DESCRIPTION("Nintendo Wii and Wii U OTP driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/qcom-spmi-sdam.c b/drivers/nvmem/qcom-spmi-sdam.c new file mode 100644 index 000000000..4f1cca6ea --- /dev/null +++ b/drivers/nvmem/qcom-spmi-sdam.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017, 2020-2021, The Linux Foundation. All rights reserved. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define SDAM_MEM_START 0x40 +#define REGISTER_MAP_ID 0x40 +#define REGISTER_MAP_VERSION 0x41 +#define SDAM_SIZE 0x44 +#define SDAM_PBS_TRIG_SET 0xE5 +#define SDAM_PBS_TRIG_CLR 0xE6 + +struct sdam_chip { + struct regmap *regmap; + struct nvmem_config sdam_config; + unsigned int base; + unsigned int size; +}; + +/* read only register offsets */ +static const u8 sdam_ro_map[] = { + REGISTER_MAP_ID, + REGISTER_MAP_VERSION, + SDAM_SIZE +}; + +static bool sdam_is_valid(struct sdam_chip *sdam, unsigned int offset, + size_t len) +{ + unsigned int sdam_mem_end = SDAM_MEM_START + sdam->size - 1; + + if (!len) + return false; + + if (offset >= SDAM_MEM_START && offset <= sdam_mem_end + && (offset + len - 1) <= sdam_mem_end) + return true; + else if ((offset == SDAM_PBS_TRIG_SET || offset == SDAM_PBS_TRIG_CLR) + && (len == 1)) + return true; + + return false; +} + +static bool sdam_is_ro(unsigned int offset, size_t len) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sdam_ro_map); i++) + if (offset <= sdam_ro_map[i] && (offset + len) > sdam_ro_map[i]) + return true; + + return false; +} + +static int sdam_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct sdam_chip *sdam = priv; + struct device *dev = sdam->sdam_config.dev; + int rc; + + if (!sdam_is_valid(sdam, offset, bytes)) { + dev_err(dev, "Invalid SDAM offset %#x len=%zd\n", + offset, bytes); + return -EINVAL; + } + + rc = regmap_bulk_read(sdam->regmap, sdam->base + offset, val, bytes); + if (rc < 0) + dev_err(dev, "Failed to read SDAM offset %#x len=%zd, rc=%d\n", + offset, bytes, rc); + + return rc; +} + +static int sdam_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct sdam_chip *sdam = priv; + struct device *dev = sdam->sdam_config.dev; + int rc; + + if (!sdam_is_valid(sdam, offset, bytes)) { + dev_err(dev, "Invalid SDAM offset %#x len=%zd\n", + offset, bytes); + return -EINVAL; + } + + if (sdam_is_ro(offset, bytes)) { + dev_err(dev, "Invalid write offset %#x len=%zd\n", + offset, bytes); + return -EINVAL; + } + + rc = regmap_bulk_write(sdam->regmap, sdam->base + offset, val, bytes); + if (rc < 0) + dev_err(dev, "Failed to write SDAM offset %#x len=%zd, rc=%d\n", + offset, bytes, rc); + + return rc; +} + +static int sdam_probe(struct platform_device *pdev) +{ + struct sdam_chip *sdam; + struct nvmem_device *nvmem; + unsigned int val; + int rc; + + sdam = devm_kzalloc(&pdev->dev, sizeof(*sdam), GFP_KERNEL); + if (!sdam) + return -ENOMEM; + + sdam->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!sdam->regmap) { + dev_err(&pdev->dev, "Failed to get regmap handle\n"); + return -ENXIO; + } + + rc = of_property_read_u32(pdev->dev.of_node, "reg", &sdam->base); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to get SDAM base, rc=%d\n", rc); + return -EINVAL; + } + + rc = regmap_read(sdam->regmap, sdam->base + SDAM_SIZE, &val); + if (rc < 0) { + dev_err(&pdev->dev, "Failed to read SDAM_SIZE rc=%d\n", rc); + return -EINVAL; + } + sdam->size = val * 32; + + sdam->sdam_config.dev = &pdev->dev; + sdam->sdam_config.name = "spmi_sdam"; + sdam->sdam_config.id = NVMEM_DEVID_AUTO; + sdam->sdam_config.owner = THIS_MODULE; + sdam->sdam_config.add_legacy_fixed_of_cells = true; + sdam->sdam_config.stride = 1; + sdam->sdam_config.size = sdam->size; + sdam->sdam_config.word_size = 1; + sdam->sdam_config.reg_read = sdam_read; + sdam->sdam_config.reg_write = sdam_write; + sdam->sdam_config.priv = sdam; + + nvmem = devm_nvmem_register(&pdev->dev, &sdam->sdam_config); + if (IS_ERR(nvmem)) { + dev_err(&pdev->dev, + "Failed to register SDAM nvmem device rc=%ld\n", + PTR_ERR(nvmem)); + return -ENXIO; + } + dev_dbg(&pdev->dev, + "SDAM base=%#x size=%u registered successfully\n", + sdam->base, sdam->size); + + return 0; +} + +static const struct of_device_id sdam_match_table[] = { + { .compatible = "qcom,spmi-sdam" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sdam_match_table); + +static struct platform_driver sdam_driver = { + .driver = { + .name = "qcom,spmi-sdam", + .of_match_table = sdam_match_table, + }, + .probe = sdam_probe, +}; +module_platform_driver(sdam_driver); + +MODULE_DESCRIPTION("QCOM SPMI SDAM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/qfprom.c b/drivers/nvmem/qfprom.c new file mode 100644 index 000000000..1de3435df --- /dev/null +++ b/drivers/nvmem/qfprom.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 Srinivas Kandagatla <srinivas.kandagatla@linaro.org> + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/pm_domain.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> + +/* Blow timer clock frequency in Mhz */ +#define QFPROM_BLOW_TIMER_OFFSET 0x03c + +/* Amount of time required to hold charge to blow fuse in micro-seconds */ +#define QFPROM_FUSE_BLOW_POLL_US 100 +#define QFPROM_FUSE_BLOW_TIMEOUT_US 10000 + +#define QFPROM_BLOW_STATUS_OFFSET 0x048 +#define QFPROM_BLOW_STATUS_BUSY 0x1 +#define QFPROM_BLOW_STATUS_READY 0x0 + +#define QFPROM_ACCEL_OFFSET 0x044 + +#define QFPROM_VERSION_OFFSET 0x0 +#define QFPROM_MAJOR_VERSION_SHIFT 28 +#define QFPROM_MAJOR_VERSION_MASK GENMASK(31, QFPROM_MAJOR_VERSION_SHIFT) +#define QFPROM_MINOR_VERSION_SHIFT 16 +#define QFPROM_MINOR_VERSION_MASK GENMASK(27, QFPROM_MINOR_VERSION_SHIFT) + +static bool read_raw_data; +module_param(read_raw_data, bool, 0644); +MODULE_PARM_DESC(read_raw_data, "Read raw instead of corrected data"); + +/** + * struct qfprom_soc_data - config that varies from SoC to SoC. + * + * @accel_value: Should contain qfprom accel value. + * @qfprom_blow_timer_value: The timer value of qfprom when doing efuse blow. + * @qfprom_blow_set_freq: The frequency required to set when we start the + * fuse blowing. + * @qfprom_blow_uV: LDO voltage to be set when doing efuse blow + */ +struct qfprom_soc_data { + u32 accel_value; + u32 qfprom_blow_timer_value; + u32 qfprom_blow_set_freq; + int qfprom_blow_uV; +}; + +/** + * struct qfprom_priv - structure holding qfprom attributes + * + * @qfpraw: iomapped memory space for qfprom-efuse raw address space. + * @qfpconf: iomapped memory space for qfprom-efuse configuration address + * space. + * @qfpcorrected: iomapped memory space for qfprom corrected address space. + * @qfpsecurity: iomapped memory space for qfprom security control space. + * @dev: qfprom device structure. + * @secclk: Clock supply. + * @vcc: Regulator supply. + * @soc_data: Data that for things that varies from SoC to SoC. + */ +struct qfprom_priv { + void __iomem *qfpraw; + void __iomem *qfpconf; + void __iomem *qfpcorrected; + void __iomem *qfpsecurity; + struct device *dev; + struct clk *secclk; + struct regulator *vcc; + const struct qfprom_soc_data *soc_data; +}; + +/** + * struct qfprom_touched_values - saved values to restore after blowing + * + * @clk_rate: The rate the clock was at before blowing. + * @accel_val: The value of the accel reg before blowing. + * @timer_val: The value of the timer before blowing. + */ +struct qfprom_touched_values { + unsigned long clk_rate; + u32 accel_val; + u32 timer_val; +}; + +/** + * struct qfprom_soc_compatible_data - Data matched against the SoC + * compatible string. + * + * @keepout: Array of keepout regions for this SoC. + * @nkeepout: Number of elements in the keepout array. + */ +struct qfprom_soc_compatible_data { + const struct nvmem_keepout *keepout; + unsigned int nkeepout; +}; + +static const struct nvmem_keepout sc7180_qfprom_keepout[] = { + {.start = 0x128, .end = 0x148}, + {.start = 0x220, .end = 0x228} +}; + +static const struct qfprom_soc_compatible_data sc7180_qfprom = { + .keepout = sc7180_qfprom_keepout, + .nkeepout = ARRAY_SIZE(sc7180_qfprom_keepout) +}; + +static const struct nvmem_keepout sc7280_qfprom_keepout[] = { + {.start = 0x128, .end = 0x148}, + {.start = 0x238, .end = 0x248} +}; + +static const struct qfprom_soc_compatible_data sc7280_qfprom = { + .keepout = sc7280_qfprom_keepout, + .nkeepout = ARRAY_SIZE(sc7280_qfprom_keepout) +}; + +/** + * qfprom_disable_fuse_blowing() - Undo enabling of fuse blowing. + * @priv: Our driver data. + * @old: The data that was stashed from before fuse blowing. + * + * Resets the value of the blow timer, accel register and the clock + * and voltage settings. + * + * Prints messages if there are errors but doesn't return an error code + * since there's not much we can do upon failure. + */ +static void qfprom_disable_fuse_blowing(const struct qfprom_priv *priv, + const struct qfprom_touched_values *old) +{ + int ret; + + writel(old->timer_val, priv->qfpconf + QFPROM_BLOW_TIMER_OFFSET); + writel(old->accel_val, priv->qfpconf + QFPROM_ACCEL_OFFSET); + + dev_pm_genpd_set_performance_state(priv->dev, 0); + pm_runtime_put(priv->dev); + + /* + * This may be a shared rail and may be able to run at a lower rate + * when we're not blowing fuses. At the moment, the regulator framework + * applies voltage constraints even on disabled rails, so remove our + * constraints and allow the rail to be adjusted by other users. + */ + ret = regulator_set_voltage(priv->vcc, 0, INT_MAX); + if (ret) + dev_warn(priv->dev, "Failed to set 0 voltage (ignoring)\n"); + + ret = regulator_disable(priv->vcc); + if (ret) + dev_warn(priv->dev, "Failed to disable regulator (ignoring)\n"); + + ret = clk_set_rate(priv->secclk, old->clk_rate); + if (ret) + dev_warn(priv->dev, + "Failed to set clock rate for disable (ignoring)\n"); + + clk_disable_unprepare(priv->secclk); +} + +/** + * qfprom_enable_fuse_blowing() - Enable fuse blowing. + * @priv: Our driver data. + * @old: We'll stash stuff here to use when disabling. + * + * Sets the value of the blow timer, accel register and the clock + * and voltage settings. + * + * Prints messages if there are errors so caller doesn't need to. + * + * Return: 0 or -err. + */ +static int qfprom_enable_fuse_blowing(const struct qfprom_priv *priv, + struct qfprom_touched_values *old) +{ + int ret; + int qfprom_blow_uV = priv->soc_data->qfprom_blow_uV; + + ret = clk_prepare_enable(priv->secclk); + if (ret) { + dev_err(priv->dev, "Failed to enable clock\n"); + return ret; + } + + old->clk_rate = clk_get_rate(priv->secclk); + ret = clk_set_rate(priv->secclk, priv->soc_data->qfprom_blow_set_freq); + if (ret) { + dev_err(priv->dev, "Failed to set clock rate for enable\n"); + goto err_clk_prepared; + } + + /* + * Hardware requires a minimum voltage for fuse blowing. + * This may be a shared rail so don't specify a maximum. + * Regulator constraints will cap to the actual maximum. + */ + ret = regulator_set_voltage(priv->vcc, qfprom_blow_uV, INT_MAX); + if (ret) { + dev_err(priv->dev, "Failed to set %duV\n", qfprom_blow_uV); + goto err_clk_rate_set; + } + + ret = regulator_enable(priv->vcc); + if (ret) { + dev_err(priv->dev, "Failed to enable regulator\n"); + goto err_clk_rate_set; + } + + ret = pm_runtime_resume_and_get(priv->dev); + if (ret < 0) { + dev_err(priv->dev, "Failed to enable power-domain\n"); + goto err_reg_enable; + } + dev_pm_genpd_set_performance_state(priv->dev, INT_MAX); + + old->timer_val = readl(priv->qfpconf + QFPROM_BLOW_TIMER_OFFSET); + old->accel_val = readl(priv->qfpconf + QFPROM_ACCEL_OFFSET); + writel(priv->soc_data->qfprom_blow_timer_value, + priv->qfpconf + QFPROM_BLOW_TIMER_OFFSET); + writel(priv->soc_data->accel_value, + priv->qfpconf + QFPROM_ACCEL_OFFSET); + + return 0; + +err_reg_enable: + regulator_disable(priv->vcc); +err_clk_rate_set: + clk_set_rate(priv->secclk, old->clk_rate); +err_clk_prepared: + clk_disable_unprepare(priv->secclk); + return ret; +} + +/** + * qfprom_reg_write() - Write to fuses. + * @context: Our driver data. + * @reg: The offset to write at. + * @_val: Pointer to data to write. + * @bytes: The number of bytes to write. + * + * Writes to fuses. WARNING: THIS IS PERMANENT. + * + * Return: 0 or -err. + */ +static int qfprom_reg_write(void *context, unsigned int reg, void *_val, + size_t bytes) +{ + struct qfprom_priv *priv = context; + struct qfprom_touched_values old; + int words = bytes / 4; + u32 *value = _val; + u32 blow_status; + int ret; + int i; + + dev_dbg(priv->dev, + "Writing to raw qfprom region : %#010x of size: %zu\n", + reg, bytes); + + /* + * The hardware only allows us to write word at a time, but we can + * read byte at a time. Until the nvmem framework allows a separate + * word_size and stride for reading vs. writing, we'll enforce here. + */ + if (bytes % 4) { + dev_err(priv->dev, + "%zu is not an integral number of words\n", bytes); + return -EINVAL; + } + if (reg % 4) { + dev_err(priv->dev, + "Invalid offset: %#x. Must be word aligned\n", reg); + return -EINVAL; + } + + ret = qfprom_enable_fuse_blowing(priv, &old); + if (ret) + return ret; + + ret = readl_relaxed_poll_timeout( + priv->qfpconf + QFPROM_BLOW_STATUS_OFFSET, + blow_status, blow_status == QFPROM_BLOW_STATUS_READY, + QFPROM_FUSE_BLOW_POLL_US, QFPROM_FUSE_BLOW_TIMEOUT_US); + + if (ret) { + dev_err(priv->dev, + "Timeout waiting for initial ready; aborting.\n"); + goto exit_enabled_fuse_blowing; + } + + for (i = 0; i < words; i++) + writel(value[i], priv->qfpraw + reg + (i * 4)); + + ret = readl_relaxed_poll_timeout( + priv->qfpconf + QFPROM_BLOW_STATUS_OFFSET, + blow_status, blow_status == QFPROM_BLOW_STATUS_READY, + QFPROM_FUSE_BLOW_POLL_US, QFPROM_FUSE_BLOW_TIMEOUT_US); + + /* Give an error, but not much we can do in this case */ + if (ret) + dev_err(priv->dev, "Timeout waiting for finish.\n"); + +exit_enabled_fuse_blowing: + qfprom_disable_fuse_blowing(priv, &old); + + return ret; +} + +static int qfprom_reg_read(void *context, + unsigned int reg, void *_val, size_t bytes) +{ + struct qfprom_priv *priv = context; + u32 *val = _val; + void __iomem *base = priv->qfpcorrected; + int words = DIV_ROUND_UP(bytes, sizeof(u32)); + int i; + + if (read_raw_data && priv->qfpraw) + base = priv->qfpraw; + + for (i = 0; i < words; i++) + *val++ = readl(base + reg + i * sizeof(u32)); + + return 0; +} + +/* Align reads to word boundary */ +static void qfprom_fixup_dt_cell_info(struct nvmem_device *nvmem, + struct nvmem_cell_info *cell) +{ + unsigned int byte_offset = cell->offset % sizeof(u32); + + cell->bit_offset += byte_offset * BITS_PER_BYTE; + cell->offset -= byte_offset; + if (byte_offset && !cell->nbits) + cell->nbits = cell->bytes * BITS_PER_BYTE; +} + +static void qfprom_runtime_disable(void *data) +{ + pm_runtime_disable(data); +} + +static const struct qfprom_soc_data qfprom_7_8_data = { + .accel_value = 0xD10, + .qfprom_blow_timer_value = 25, + .qfprom_blow_set_freq = 4800000, + .qfprom_blow_uV = 1800000, +}; + +static const struct qfprom_soc_data qfprom_7_15_data = { + .accel_value = 0xD08, + .qfprom_blow_timer_value = 24, + .qfprom_blow_set_freq = 4800000, + .qfprom_blow_uV = 1900000, +}; + +static int qfprom_probe(struct platform_device *pdev) +{ + struct nvmem_config econfig = { + .name = "qfprom", + .add_legacy_fixed_of_cells = true, + .stride = 4, + .word_size = 4, + .id = NVMEM_DEVID_AUTO, + .reg_read = qfprom_reg_read, + .fixup_dt_cell_info = qfprom_fixup_dt_cell_info, + }; + struct device *dev = &pdev->dev; + struct resource *res; + struct nvmem_device *nvmem; + const struct qfprom_soc_compatible_data *soc_data; + struct qfprom_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + /* The corrected section is always provided */ + priv->qfpcorrected = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(priv->qfpcorrected)) + return PTR_ERR(priv->qfpcorrected); + + econfig.size = resource_size(res); + econfig.dev = dev; + econfig.priv = priv; + + priv->dev = dev; + soc_data = device_get_match_data(dev); + if (soc_data) { + econfig.keepout = soc_data->keepout; + econfig.nkeepout = soc_data->nkeepout; + } + + /* + * If more than one region is provided then the OS has the ability + * to write. + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (res) { + u32 version; + int major_version, minor_version; + + priv->qfpraw = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->qfpraw)) + return PTR_ERR(priv->qfpraw); + priv->qfpconf = devm_platform_ioremap_resource(pdev, 2); + if (IS_ERR(priv->qfpconf)) + return PTR_ERR(priv->qfpconf); + priv->qfpsecurity = devm_platform_ioremap_resource(pdev, 3); + if (IS_ERR(priv->qfpsecurity)) + return PTR_ERR(priv->qfpsecurity); + + version = readl(priv->qfpsecurity + QFPROM_VERSION_OFFSET); + major_version = (version & QFPROM_MAJOR_VERSION_MASK) >> + QFPROM_MAJOR_VERSION_SHIFT; + minor_version = (version & QFPROM_MINOR_VERSION_MASK) >> + QFPROM_MINOR_VERSION_SHIFT; + + if (major_version == 7 && minor_version == 8) + priv->soc_data = &qfprom_7_8_data; + else if (major_version == 7 && minor_version == 15) + priv->soc_data = &qfprom_7_15_data; + + priv->vcc = devm_regulator_get(&pdev->dev, "vcc"); + if (IS_ERR(priv->vcc)) + return PTR_ERR(priv->vcc); + + priv->secclk = devm_clk_get_optional(dev, "core"); + if (IS_ERR(priv->secclk)) + return dev_err_probe(dev, PTR_ERR(priv->secclk), "Error getting clock\n"); + + /* Only enable writing if we have SoC data and a valid clock */ + if (priv->soc_data && priv->secclk) + econfig.reg_write = qfprom_reg_write; + } + + pm_runtime_enable(dev); + ret = devm_add_action_or_reset(dev, qfprom_runtime_disable, dev); + if (ret) + return ret; + + nvmem = devm_nvmem_register(dev, &econfig); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id qfprom_of_match[] = { + { .compatible = "qcom,qfprom",}, + { .compatible = "qcom,sc7180-qfprom", .data = &sc7180_qfprom}, + { .compatible = "qcom,sc7280-qfprom", .data = &sc7280_qfprom}, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, qfprom_of_match); + +static struct platform_driver qfprom_driver = { + .probe = qfprom_probe, + .driver = { + .name = "qcom,qfprom", + .of_match_table = qfprom_of_match, + }, +}; +module_platform_driver(qfprom_driver); +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org>"); +MODULE_DESCRIPTION("Qualcomm QFPROM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/qnap-mcu-eeprom.c b/drivers/nvmem/qnap-mcu-eeprom.c new file mode 100644 index 000000000..07bdaa2a3 --- /dev/null +++ b/drivers/nvmem/qnap-mcu-eeprom.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ee1004 - driver for DDR4 SPD EEPROMs + * + * Copyright (C) 2017-2019 Jean Delvare + * + * Based on the at24 driver: + * Copyright (C) 2005-2007 David Brownell + * Copyright (C) 2008 Wolfram Sang, Pengutronix + */ + +#include <linux/mfd/qnap-mcu.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* Determined by trial and error until read anomalies appeared */ +#define QNAP_MCU_EEPROM_SIZE 256 +#define QNAP_MCU_EEPROM_BLOCK_SIZE 32 + +static int qnap_mcu_eeprom_read_block(struct qnap_mcu *mcu, unsigned int offset, + void *val, size_t bytes) +{ + const u8 cmd[] = { 0xf7, 0xa1, offset, bytes }; + u8 *reply; + int ret = 0; + + reply = kzalloc(bytes + sizeof(cmd), GFP_KERNEL); + if (!reply) + return -ENOMEM; + + ret = qnap_mcu_exec(mcu, cmd, sizeof(cmd), reply, bytes + sizeof(cmd)); + if (ret) + goto out; + + /* First bytes must mirror the sent command */ + if (memcmp(cmd, reply, sizeof(cmd))) { + ret = -EIO; + goto out; + } + + memcpy(val, reply + sizeof(cmd), bytes); + +out: + kfree(reply); + return ret; +} + +static int qnap_mcu_eeprom_read(void *priv, unsigned int offset, void *val, size_t bytes) +{ + struct qnap_mcu *mcu = priv; + int pos = 0, ret; + u8 *buf = val; + + if (unlikely(!bytes)) + return 0; + + while (bytes > 0) { + size_t to_read = (bytes > QNAP_MCU_EEPROM_BLOCK_SIZE) ? + QNAP_MCU_EEPROM_BLOCK_SIZE : bytes; + + ret = qnap_mcu_eeprom_read_block(mcu, offset + pos, &buf[pos], to_read); + if (ret < 0) + return ret; + + pos += to_read; + bytes -= to_read; + } + + return 0; +} + +static int qnap_mcu_eeprom_probe(struct platform_device *pdev) +{ + struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent); + struct nvmem_config nvcfg = {}; + struct nvmem_device *ndev; + + nvcfg.dev = &pdev->dev; + nvcfg.of_node = pdev->dev.parent->of_node; + nvcfg.name = dev_name(&pdev->dev); + nvcfg.id = NVMEM_DEVID_NONE; + nvcfg.owner = THIS_MODULE; + nvcfg.type = NVMEM_TYPE_EEPROM; + nvcfg.read_only = true; + nvcfg.root_only = false; + nvcfg.reg_read = qnap_mcu_eeprom_read; + nvcfg.size = QNAP_MCU_EEPROM_SIZE; + nvcfg.word_size = 1; + nvcfg.stride = 1; + nvcfg.priv = mcu; + + ndev = devm_nvmem_register(&pdev->dev, &nvcfg); + if (IS_ERR(ndev)) + return PTR_ERR(ndev); + + return 0; +} + +static struct platform_driver qnap_mcu_eeprom_driver = { + .probe = qnap_mcu_eeprom_probe, + .driver = { + .name = "qnap-mcu-eeprom", + }, +}; +module_platform_driver(qnap_mcu_eeprom_driver); + +MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); +MODULE_DESCRIPTION("QNAP MCU EEPROM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/qoriq-efuse.c b/drivers/nvmem/qoriq-efuse.c new file mode 100644 index 000000000..80f514939 --- /dev/null +++ b/drivers/nvmem/qoriq-efuse.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Westermo Network Technologies AB + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> + +struct qoriq_efuse_priv { + void __iomem *base; +}; + +static int qoriq_efuse_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct qoriq_efuse_priv *priv = context; + + /* .stride = 4 so offset is guaranteed to be aligned */ + __ioread32_copy(val, priv->base + offset, bytes / 4); + + /* Ignore trailing bytes (there shouldn't be any) */ + + return 0; +} + +static int qoriq_efuse_probe(struct platform_device *pdev) +{ + struct nvmem_config config = { + .dev = &pdev->dev, + .read_only = true, + .reg_read = qoriq_efuse_read, + .stride = sizeof(u32), + .word_size = sizeof(u32), + .name = "qoriq_efuse_read", + .id = NVMEM_DEVID_AUTO, + .root_only = true, + }; + struct qoriq_efuse_priv *priv; + struct nvmem_device *nvmem; + struct resource *res; + + priv = devm_kzalloc(config.dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + config.size = resource_size(res); + config.priv = priv; + nvmem = devm_nvmem_register(config.dev, &config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id qoriq_efuse_of_match[] = { + { .compatible = "fsl,t1023-sfp", }, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, qoriq_efuse_of_match); + +static struct platform_driver qoriq_efuse_driver = { + .probe = qoriq_efuse_probe, + .driver = { + .name = "qoriq-efuse", + .of_match_table = qoriq_efuse_of_match, + }, +}; +module_platform_driver(qoriq_efuse_driver); + +MODULE_AUTHOR("Richard Alpe <richard.alpe@bit42.se>"); +MODULE_DESCRIPTION("NXP QorIQ Security Fuse Processor (SFP) Reader"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/rave-sp-eeprom.c b/drivers/nvmem/rave-sp-eeprom.c new file mode 100644 index 000000000..9ecf3873c --- /dev/null +++ b/drivers/nvmem/rave-sp-eeprom.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * EEPROM driver for RAVE SP + * + * Copyright (C) 2018 Zodiac Inflight Innovations + * + */ +#include <linux/kernel.h> +#include <linux/mfd/rave-sp.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/sizes.h> + +/** + * enum rave_sp_eeprom_access_type - Supported types of EEPROM access + * + * @RAVE_SP_EEPROM_WRITE: EEPROM write + * @RAVE_SP_EEPROM_READ: EEPROM read + */ +enum rave_sp_eeprom_access_type { + RAVE_SP_EEPROM_WRITE = 0, + RAVE_SP_EEPROM_READ = 1, +}; + +/** + * enum rave_sp_eeprom_header_size - EEPROM command header sizes + * + * @RAVE_SP_EEPROM_HEADER_SMALL: EEPROM header size for "small" devices (< 8K) + * @RAVE_SP_EEPROM_HEADER_BIG: EEPROM header size for "big" devices (> 8K) + */ +enum rave_sp_eeprom_header_size { + RAVE_SP_EEPROM_HEADER_SMALL = 4U, + RAVE_SP_EEPROM_HEADER_BIG = 5U, +}; +#define RAVE_SP_EEPROM_HEADER_MAX RAVE_SP_EEPROM_HEADER_BIG + +#define RAVE_SP_EEPROM_PAGE_SIZE 32U + +/** + * struct rave_sp_eeprom_page - RAVE SP EEPROM page + * + * @type: Access type (see enum rave_sp_eeprom_access_type) + * @success: Success flag (Success = 1, Failure = 0) + * @data: Read data + * + * Note this structure corresponds to RSP_*_EEPROM payload from RAVE + * SP ICD + */ +struct rave_sp_eeprom_page { + u8 type; + u8 success; + u8 data[RAVE_SP_EEPROM_PAGE_SIZE]; +} __packed; + +/** + * struct rave_sp_eeprom - RAVE SP EEPROM device + * + * @sp: Pointer to parent RAVE SP device + * @mutex: Lock protecting access to EEPROM + * @address: EEPROM device address + * @header_size: Size of EEPROM command header for this device + * @dev: Pointer to corresponding struct device used for logging + */ +struct rave_sp_eeprom { + struct rave_sp *sp; + struct mutex mutex; + u8 address; + unsigned int header_size; + struct device *dev; +}; + +/** + * rave_sp_eeprom_io - Low-level part of EEPROM page access + * + * @eeprom: EEPROM device to write to + * @type: EEPROM access type (read or write) + * @idx: number of the EEPROM page + * @page: Data to write or buffer to store result (via page->data) + * + * This function does all of the low-level work required to perform a + * EEPROM access. This includes formatting correct command payload, + * sending it and checking received results. + * + * Returns zero in case of success or negative error code in + * case of failure. + */ +static int rave_sp_eeprom_io(struct rave_sp_eeprom *eeprom, + enum rave_sp_eeprom_access_type type, + u16 idx, + struct rave_sp_eeprom_page *page) +{ + const bool is_write = type == RAVE_SP_EEPROM_WRITE; + const unsigned int data_size = is_write ? sizeof(page->data) : 0; + const unsigned int cmd_size = eeprom->header_size + data_size; + const unsigned int rsp_size = + is_write ? sizeof(*page) - sizeof(page->data) : sizeof(*page); + unsigned int offset = 0; + u8 cmd[RAVE_SP_EEPROM_HEADER_MAX + sizeof(page->data)]; + int ret; + + if (WARN_ON(cmd_size > sizeof(cmd))) + return -EINVAL; + + cmd[offset++] = eeprom->address; + cmd[offset++] = 0; + cmd[offset++] = type; + cmd[offset++] = idx; + + /* + * If there's still room in this command's header it means we + * are talkin to EEPROM that uses 16-bit page numbers and we + * have to specify index's MSB in payload as well. + */ + if (offset < eeprom->header_size) + cmd[offset++] = idx >> 8; + /* + * Copy our data to write to command buffer first. In case of + * a read data_size should be zero and memcpy would become a + * no-op + */ + memcpy(&cmd[offset], page->data, data_size); + + ret = rave_sp_exec(eeprom->sp, cmd, cmd_size, page, rsp_size); + if (ret) + return ret; + + if (page->type != type) + return -EPROTO; + + if (!page->success) + return -EIO; + + return 0; +} + +/** + * rave_sp_eeprom_page_access - Access single EEPROM page + * + * @eeprom: EEPROM device to access + * @type: Access type to perform (read or write) + * @offset: Offset within EEPROM to access + * @data: Data buffer + * @data_len: Size of the data buffer + * + * This function performs a generic access to a single page or a + * portion thereof. Requested access MUST NOT cross the EEPROM page + * boundary. + * + * Returns zero in case of success or negative error code in + * case of failure. + */ +static int +rave_sp_eeprom_page_access(struct rave_sp_eeprom *eeprom, + enum rave_sp_eeprom_access_type type, + unsigned int offset, u8 *data, + size_t data_len) +{ + const unsigned int page_offset = offset % RAVE_SP_EEPROM_PAGE_SIZE; + const unsigned int page_nr = offset / RAVE_SP_EEPROM_PAGE_SIZE; + struct rave_sp_eeprom_page page; + int ret; + + /* + * This function will not work if data access we've been asked + * to do is crossing EEPROM page boundary. Normally this + * should never happen and getting here would indicate a bug + * in the code. + */ + if (WARN_ON(data_len > sizeof(page.data) - page_offset)) + return -EINVAL; + + if (type == RAVE_SP_EEPROM_WRITE) { + /* + * If doing a partial write we need to do a read first + * to fill the rest of the page with correct data. + */ + if (data_len < RAVE_SP_EEPROM_PAGE_SIZE) { + ret = rave_sp_eeprom_io(eeprom, RAVE_SP_EEPROM_READ, + page_nr, &page); + if (ret) + return ret; + } + + memcpy(&page.data[page_offset], data, data_len); + } + + ret = rave_sp_eeprom_io(eeprom, type, page_nr, &page); + if (ret) + return ret; + + /* + * Since we receive the result of the read via 'page.data' + * buffer we need to copy that to 'data' + */ + if (type == RAVE_SP_EEPROM_READ) + memcpy(data, &page.data[page_offset], data_len); + + return 0; +} + +/** + * rave_sp_eeprom_access - Access EEPROM data + * + * @eeprom: EEPROM device to access + * @type: Access type to perform (read or write) + * @offset: Offset within EEPROM to access + * @data: Data buffer + * @data_len: Size of the data buffer + * + * This function performs a generic access (either read or write) at + * arbitrary offset (not necessary page aligned) of arbitrary length + * (is not constrained by EEPROM page size). + * + * Returns zero in case of success or negative error code in case of + * failure. + */ +static int rave_sp_eeprom_access(struct rave_sp_eeprom *eeprom, + enum rave_sp_eeprom_access_type type, + unsigned int offset, u8 *data, + unsigned int data_len) +{ + unsigned int residue; + unsigned int chunk; + unsigned int head; + int ret; + + mutex_lock(&eeprom->mutex); + + head = offset % RAVE_SP_EEPROM_PAGE_SIZE; + residue = data_len; + + do { + /* + * First iteration, if we are doing an access that is + * not 32-byte aligned, we need to access only data up + * to a page boundary to avoid corssing it in + * rave_sp_eeprom_page_access() + */ + if (unlikely(head)) { + chunk = RAVE_SP_EEPROM_PAGE_SIZE - head; + /* + * This can only happen once per + * rave_sp_eeprom_access() call, so we set + * head to zero to process all the other + * iterations normally. + */ + head = 0; + } else { + chunk = RAVE_SP_EEPROM_PAGE_SIZE; + } + + /* + * We should never read more that 'residue' bytes + */ + chunk = min(chunk, residue); + ret = rave_sp_eeprom_page_access(eeprom, type, offset, + data, chunk); + if (ret) + goto out; + + residue -= chunk; + offset += chunk; + data += chunk; + } while (residue); +out: + mutex_unlock(&eeprom->mutex); + return ret; +} + +static int rave_sp_eeprom_reg_read(void *eeprom, unsigned int offset, + void *val, size_t bytes) +{ + return rave_sp_eeprom_access(eeprom, RAVE_SP_EEPROM_READ, + offset, val, bytes); +} + +static int rave_sp_eeprom_reg_write(void *eeprom, unsigned int offset, + void *val, size_t bytes) +{ + return rave_sp_eeprom_access(eeprom, RAVE_SP_EEPROM_WRITE, + offset, val, bytes); +} + +static int rave_sp_eeprom_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rave_sp *sp = dev_get_drvdata(dev->parent); + struct device_node *np = dev->of_node; + struct nvmem_config config = { 0 }; + struct rave_sp_eeprom *eeprom; + struct nvmem_device *nvmem; + u32 reg[2], size; + + if (of_property_read_u32_array(np, "reg", reg, ARRAY_SIZE(reg))) { + dev_err(dev, "Failed to parse \"reg\" property\n"); + return -EINVAL; + } + + size = reg[1]; + /* + * Per ICD, we have no more than 2 bytes to specify EEPROM + * page. + */ + if (size > U16_MAX * RAVE_SP_EEPROM_PAGE_SIZE) { + dev_err(dev, "Specified size is too big\n"); + return -EINVAL; + } + + eeprom = devm_kzalloc(dev, sizeof(*eeprom), GFP_KERNEL); + if (!eeprom) + return -ENOMEM; + + eeprom->address = reg[0]; + eeprom->sp = sp; + eeprom->dev = dev; + + if (size > SZ_8K) + eeprom->header_size = RAVE_SP_EEPROM_HEADER_BIG; + else + eeprom->header_size = RAVE_SP_EEPROM_HEADER_SMALL; + + mutex_init(&eeprom->mutex); + + config.id = -1; + of_property_read_string(np, "zii,eeprom-name", &config.name); + config.priv = eeprom; + config.dev = dev; + config.add_legacy_fixed_of_cells = true; + config.size = size; + config.reg_read = rave_sp_eeprom_reg_read; + config.reg_write = rave_sp_eeprom_reg_write; + config.word_size = 1; + config.stride = 1; + + nvmem = devm_nvmem_register(dev, &config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id rave_sp_eeprom_of_match[] = { + { .compatible = "zii,rave-sp-eeprom" }, + {} +}; +MODULE_DEVICE_TABLE(of, rave_sp_eeprom_of_match); + +static struct platform_driver rave_sp_eeprom_driver = { + .probe = rave_sp_eeprom_probe, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = rave_sp_eeprom_of_match, + }, +}; +module_platform_driver(rave_sp_eeprom_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Andrey Vostrikov <andrey.vostrikov@cogentembedded.com>"); +MODULE_AUTHOR("Nikita Yushchenko <nikita.yoush@cogentembedded.com>"); +MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>"); +MODULE_DESCRIPTION("RAVE SP EEPROM driver"); diff --git a/drivers/nvmem/rcar-efuse.c b/drivers/nvmem/rcar-efuse.c new file mode 100644 index 000000000..b94ff83b7 --- /dev/null +++ b/drivers/nvmem/rcar-efuse.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Renesas R-Car E-FUSE/OTP Driver + * + * Copyright (C) 2024 Glider bv + */ + +#include <linux/device.h> +#include <linux/export.h> +#include <linux/io.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> + +struct rcar_fuse { + struct nvmem_keepout keepouts[2]; + struct nvmem_device *nvmem; + struct device *dev; + void __iomem *base; +}; + +struct rcar_fuse_data { + unsigned int bank; /* 0: PFC + E-FUSE, 1: OPT_MEM + E-FUSE */ + unsigned int start; /* inclusive */ + unsigned int end; /* exclusive */ +}; + +static int rcar_fuse_reg_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct rcar_fuse *fuse = priv; + int ret; + + ret = pm_runtime_resume_and_get(fuse->dev); + if (ret < 0) + return ret; + + __ioread32_copy(val, fuse->base + offset, bytes / 4); + + pm_runtime_put(fuse->dev); + + return 0; +} + +static int rcar_fuse_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct rcar_fuse_data *data = device_get_match_data(dev); + struct nvmem_config config = { + .dev = dev, + .name = "rcar-fuse", + .id = NVMEM_DEVID_NONE, + .owner = THIS_MODULE, + .type = NVMEM_TYPE_OTP, + .read_only = true, + .root_only = true, + .reg_read = rcar_fuse_reg_read, + .word_size = 4, + .stride = 4, + }; + struct rcar_fuse *fuse; + struct resource *res; + int ret; + + ret = devm_pm_runtime_enable(dev); + if (ret < 0) + return ret; + + fuse = devm_kzalloc(dev, sizeof(*fuse), GFP_KERNEL); + if (!fuse) + return -ENOMEM; + + fuse->base = devm_platform_get_and_ioremap_resource(pdev, data->bank, + &res); + if (IS_ERR(fuse->base)) + return PTR_ERR(fuse->base); + + fuse->dev = dev; + fuse->keepouts[0].start = 0; + fuse->keepouts[0].end = data->start; + fuse->keepouts[1].start = data->end; + fuse->keepouts[1].end = resource_size(res); + + config.keepout = fuse->keepouts; + config.nkeepout = ARRAY_SIZE(fuse->keepouts); + config.size = resource_size(res); + config.priv = fuse; + + fuse->nvmem = devm_nvmem_register(dev, &config); + if (IS_ERR(fuse->nvmem)) + return dev_err_probe(dev, PTR_ERR(fuse->nvmem), + "Failed to register NVMEM device\n"); + + return 0; +} + +static const struct rcar_fuse_data rcar_fuse_v3u = { + .bank = 0, + .start = 0x0c0, + .end = 0x0e8, +}; + +static const struct rcar_fuse_data rcar_fuse_s4 = { + .bank = 0, + .start = 0x0c0, + .end = 0x14c, +}; + +static const struct rcar_fuse_data rcar_fuse_v4h = { + .bank = 1, + .start = 0x100, + .end = 0x1a0, +}; + +static const struct rcar_fuse_data rcar_fuse_v4m = { + .bank = 1, + .start = 0x100, + .end = 0x110, +}; + +static const struct of_device_id rcar_fuse_match[] = { + { .compatible = "renesas,r8a779a0-efuse", .data = &rcar_fuse_v3u }, + { .compatible = "renesas,r8a779f0-efuse", .data = &rcar_fuse_s4 }, + { .compatible = "renesas,r8a779g0-otp", .data = &rcar_fuse_v4h }, + { .compatible = "renesas,r8a779h0-otp", .data = &rcar_fuse_v4m }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rcar_fuse_match); + +static struct platform_driver rcar_fuse_driver = { + .probe = rcar_fuse_probe, + .driver = { + .name = "rcar_fuse", + .of_match_table = rcar_fuse_match, + }, +}; +module_platform_driver(rcar_fuse_driver); + +MODULE_DESCRIPTION("Renesas R-Car E-FUSE/OTP driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Geert Uytterhoeven"); diff --git a/drivers/nvmem/rmem.c b/drivers/nvmem/rmem.c new file mode 100644 index 000000000..b39d628cb --- /dev/null +++ b/drivers/nvmem/rmem.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne@suse.de> + */ + +#include <linux/crc32.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of_reserved_mem.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct rmem { + struct device *dev; + struct nvmem_device *nvmem; + struct reserved_mem *mem; +}; + +struct rmem_match_data { + int (*checksum)(struct rmem *priv); +}; + +struct __packed rmem_eyeq5_header { + u32 magic; + u32 version; + u32 size; +}; + +#define RMEM_EYEQ5_MAGIC ((u32)0xDABBAD00) + +static int rmem_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct rmem *priv = context; + void *addr; + + if ((phys_addr_t)offset + bytes > priv->mem->size) + return -EIO; + + /* + * Only map the reserved memory at this point to avoid potential rogue + * kernel threads inadvertently modifying it. Based on the current + * uses-cases for this driver, the performance hit isn't a concern. + * Nor is likely to be, given the nature of the subsystem. Most nvmem + * devices operate over slow buses to begin with. + * + * An alternative would be setting the memory as RO, set_memory_ro(), + * but as of Dec 2020 this isn't possible on arm64. + */ + addr = memremap(priv->mem->base, priv->mem->size, MEMREMAP_WB); + if (!addr) { + dev_err(priv->dev, "Failed to remap memory region\n"); + return -ENOMEM; + } + + memcpy(val, addr + offset, bytes); + + memunmap(addr); + + return 0; +} + +static int rmem_eyeq5_checksum(struct rmem *priv) +{ + void *buf __free(kfree) = NULL; + struct rmem_eyeq5_header header; + u32 computed_crc, *target_crc; + size_t data_size; + int ret; + + ret = rmem_read(priv, 0, &header, sizeof(header)); + if (ret) + return ret; + + if (header.magic != RMEM_EYEQ5_MAGIC) + return -EINVAL; + + /* + * Avoid massive kmalloc() if header read is invalid; + * the check would be done by the next rmem_read() anyway. + */ + if (header.size > priv->mem->size) + return -EINVAL; + + /* + * 0 +-------------------+ + * | Header (12 bytes) | \ + * +-------------------+ | + * | | | data to be CRCed + * | ... | | + * | | / + * data_size +-------------------+ + * | CRC (4 bytes) | + * header.size +-------------------+ + */ + + buf = kmalloc(header.size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = rmem_read(priv, 0, buf, header.size); + if (ret) + return ret; + + data_size = header.size - sizeof(*target_crc); + target_crc = buf + data_size; + computed_crc = crc32(U32_MAX, buf, data_size) ^ U32_MAX; + + if (computed_crc == *target_crc) + return 0; + + dev_err(priv->dev, + "checksum failed: computed %#x, expected %#x, header (%#x, %#x, %#x)\n", + computed_crc, *target_crc, header.magic, header.version, header.size); + return -EINVAL; +} + +static int rmem_probe(struct platform_device *pdev) +{ + struct nvmem_config config = { }; + struct device *dev = &pdev->dev; + const struct rmem_match_data *match_data = device_get_match_data(dev); + struct reserved_mem *mem; + struct rmem *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->dev = dev; + + mem = of_reserved_mem_lookup(dev->of_node); + if (!mem) { + dev_err(dev, "Failed to lookup reserved memory\n"); + return -EINVAL; + } + priv->mem = mem; + + config.dev = dev; + config.priv = priv; + config.name = "rmem"; + config.id = NVMEM_DEVID_AUTO; + config.size = mem->size; + config.reg_read = rmem_read; + + if (match_data && match_data->checksum) { + int ret = match_data->checksum(priv); + + if (ret) + return ret; + } + + return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &config)); +} + +static const struct rmem_match_data rmem_eyeq5_match_data = { + .checksum = rmem_eyeq5_checksum, +}; + +static const struct of_device_id rmem_match[] = { + { .compatible = "mobileye,eyeq5-bootloader-config", .data = &rmem_eyeq5_match_data }, + { .compatible = "nvmem-rmem", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rmem_match); + +static struct platform_driver rmem_driver = { + .probe = rmem_probe, + .driver = { + .name = "rmem", + .of_match_table = rmem_match, + }, +}; +module_platform_driver(rmem_driver); + +MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de>"); +MODULE_DESCRIPTION("Reserved Memory Based nvmem Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/rockchip-efuse.c b/drivers/nvmem/rockchip-efuse.c new file mode 100644 index 000000000..013e67136 --- /dev/null +++ b/drivers/nvmem/rockchip-efuse.c @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Rockchip eFuse Driver + * + * Copyright (c) 2015 Rockchip Electronics Co. Ltd. + * Author: Caesar Wang <wxt@rock-chips.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +#define RK3288_A_SHIFT 6 +#define RK3288_A_MASK 0x3ff +#define RK3288_PGENB BIT(3) +#define RK3288_LOAD BIT(2) +#define RK3288_STROBE BIT(1) +#define RK3288_CSB BIT(0) + +#define RK3328_SECURE_SIZES 96 +#define RK3328_INT_STATUS 0x0018 +#define RK3328_DOUT 0x0020 +#define RK3328_AUTO_CTRL 0x0024 +#define RK3328_INT_FINISH BIT(0) +#define RK3328_AUTO_ENB BIT(0) +#define RK3328_AUTO_RD BIT(1) + +#define RK3399_A_SHIFT 16 +#define RK3399_A_MASK 0x3ff +#define RK3399_NBYTES 4 +#define RK3399_STROBSFTSEL BIT(9) +#define RK3399_RSB BIT(7) +#define RK3399_PD BIT(5) +#define RK3399_PGENB BIT(3) +#define RK3399_LOAD BIT(2) +#define RK3399_STROBE BIT(1) +#define RK3399_CSB BIT(0) + +#define REG_EFUSE_CTRL 0x0000 +#define REG_EFUSE_DOUT 0x0004 + +struct rockchip_efuse_chip { + struct device *dev; + void __iomem *base; + struct clk *clk; +}; + +static int rockchip_rk3288_efuse_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct rockchip_efuse_chip *efuse = context; + u8 *buf = val; + int ret; + + ret = clk_prepare_enable(efuse->clk); + if (ret < 0) { + dev_err(efuse->dev, "failed to prepare/enable efuse clk\n"); + return ret; + } + + writel(RK3288_LOAD | RK3288_PGENB, efuse->base + REG_EFUSE_CTRL); + udelay(1); + while (bytes--) { + writel(readl(efuse->base + REG_EFUSE_CTRL) & + (~(RK3288_A_MASK << RK3288_A_SHIFT)), + efuse->base + REG_EFUSE_CTRL); + writel(readl(efuse->base + REG_EFUSE_CTRL) | + ((offset++ & RK3288_A_MASK) << RK3288_A_SHIFT), + efuse->base + REG_EFUSE_CTRL); + udelay(1); + writel(readl(efuse->base + REG_EFUSE_CTRL) | + RK3288_STROBE, efuse->base + REG_EFUSE_CTRL); + udelay(1); + *buf++ = readb(efuse->base + REG_EFUSE_DOUT); + writel(readl(efuse->base + REG_EFUSE_CTRL) & + (~RK3288_STROBE), efuse->base + REG_EFUSE_CTRL); + udelay(1); + } + + /* Switch to standby mode */ + writel(RK3288_PGENB | RK3288_CSB, efuse->base + REG_EFUSE_CTRL); + + clk_disable_unprepare(efuse->clk); + + return 0; +} + +static int rockchip_rk3328_efuse_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct rockchip_efuse_chip *efuse = context; + unsigned int addr_start, addr_end, addr_offset, addr_len; + u32 out_value, status; + u8 *buf; + int ret, i = 0; + + ret = clk_prepare_enable(efuse->clk); + if (ret < 0) { + dev_err(efuse->dev, "failed to prepare/enable efuse clk\n"); + return ret; + } + + /* 128 Byte efuse, 96 Byte for secure, 32 Byte for non-secure */ + offset += RK3328_SECURE_SIZES; + addr_start = rounddown(offset, RK3399_NBYTES) / RK3399_NBYTES; + addr_end = roundup(offset + bytes, RK3399_NBYTES) / RK3399_NBYTES; + addr_offset = offset % RK3399_NBYTES; + addr_len = addr_end - addr_start; + + buf = kzalloc(array3_size(addr_len, RK3399_NBYTES, sizeof(*buf)), + GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto nomem; + } + + while (addr_len--) { + writel(RK3328_AUTO_RD | RK3328_AUTO_ENB | + ((addr_start++ & RK3399_A_MASK) << RK3399_A_SHIFT), + efuse->base + RK3328_AUTO_CTRL); + udelay(4); + status = readl(efuse->base + RK3328_INT_STATUS); + if (!(status & RK3328_INT_FINISH)) { + ret = -EIO; + goto err; + } + out_value = readl(efuse->base + RK3328_DOUT); + writel(RK3328_INT_FINISH, efuse->base + RK3328_INT_STATUS); + + memcpy(&buf[i], &out_value, RK3399_NBYTES); + i += RK3399_NBYTES; + } + + memcpy(val, buf + addr_offset, bytes); +err: + kfree(buf); +nomem: + clk_disable_unprepare(efuse->clk); + + return ret; +} + +static int rockchip_rk3399_efuse_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct rockchip_efuse_chip *efuse = context; + unsigned int addr_start, addr_end, addr_offset, addr_len; + u32 out_value; + u8 *buf; + int ret, i = 0; + + ret = clk_prepare_enable(efuse->clk); + if (ret < 0) { + dev_err(efuse->dev, "failed to prepare/enable efuse clk\n"); + return ret; + } + + addr_start = rounddown(offset, RK3399_NBYTES) / RK3399_NBYTES; + addr_end = roundup(offset + bytes, RK3399_NBYTES) / RK3399_NBYTES; + addr_offset = offset % RK3399_NBYTES; + addr_len = addr_end - addr_start; + + buf = kzalloc(array3_size(addr_len, RK3399_NBYTES, sizeof(*buf)), + GFP_KERNEL); + if (!buf) { + clk_disable_unprepare(efuse->clk); + return -ENOMEM; + } + + writel(RK3399_LOAD | RK3399_PGENB | RK3399_STROBSFTSEL | RK3399_RSB, + efuse->base + REG_EFUSE_CTRL); + udelay(1); + while (addr_len--) { + writel(readl(efuse->base + REG_EFUSE_CTRL) | RK3399_STROBE | + ((addr_start++ & RK3399_A_MASK) << RK3399_A_SHIFT), + efuse->base + REG_EFUSE_CTRL); + udelay(1); + out_value = readl(efuse->base + REG_EFUSE_DOUT); + writel(readl(efuse->base + REG_EFUSE_CTRL) & (~RK3399_STROBE), + efuse->base + REG_EFUSE_CTRL); + udelay(1); + + memcpy(&buf[i], &out_value, RK3399_NBYTES); + i += RK3399_NBYTES; + } + + /* Switch to standby mode */ + writel(RK3399_PD | RK3399_CSB, efuse->base + REG_EFUSE_CTRL); + + memcpy(val, buf + addr_offset, bytes); + + kfree(buf); + + clk_disable_unprepare(efuse->clk); + + return 0; +} + +static struct nvmem_config econfig = { + .name = "rockchip-efuse", + .add_legacy_fixed_of_cells = true, + .type = NVMEM_TYPE_OTP, + .stride = 1, + .word_size = 1, + .read_only = true, +}; + +static const struct of_device_id rockchip_efuse_match[] = { + /* deprecated but kept around for dts binding compatibility */ + { + .compatible = "rockchip,rockchip-efuse", + .data = (void *)&rockchip_rk3288_efuse_read, + }, + { + .compatible = "rockchip,rk3066a-efuse", + .data = (void *)&rockchip_rk3288_efuse_read, + }, + { + .compatible = "rockchip,rk3188-efuse", + .data = (void *)&rockchip_rk3288_efuse_read, + }, + { + .compatible = "rockchip,rk3228-efuse", + .data = (void *)&rockchip_rk3288_efuse_read, + }, + { + .compatible = "rockchip,rk3288-efuse", + .data = (void *)&rockchip_rk3288_efuse_read, + }, + { + .compatible = "rockchip,rk3368-efuse", + .data = (void *)&rockchip_rk3288_efuse_read, + }, + { + .compatible = "rockchip,rk3328-efuse", + .data = (void *)&rockchip_rk3328_efuse_read, + }, + { + .compatible = "rockchip,rk3399-efuse", + .data = (void *)&rockchip_rk3399_efuse_read, + }, + { /* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, rockchip_efuse_match); + +static int rockchip_efuse_probe(struct platform_device *pdev) +{ + struct resource *res; + struct nvmem_device *nvmem; + struct rockchip_efuse_chip *efuse; + const void *data; + struct device *dev = &pdev->dev; + + data = of_device_get_match_data(dev); + if (!data) { + dev_err(dev, "failed to get match data\n"); + return -EINVAL; + } + + efuse = devm_kzalloc(dev, sizeof(struct rockchip_efuse_chip), + GFP_KERNEL); + if (!efuse) + return -ENOMEM; + + efuse->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(efuse->base)) + return PTR_ERR(efuse->base); + + efuse->clk = devm_clk_get(dev, "pclk_efuse"); + if (IS_ERR(efuse->clk)) + return PTR_ERR(efuse->clk); + + efuse->dev = dev; + if (of_property_read_u32(dev->of_node, "rockchip,efuse-size", + &econfig.size)) + econfig.size = resource_size(res); + econfig.reg_read = data; + econfig.priv = efuse; + econfig.dev = efuse->dev; + nvmem = devm_nvmem_register(dev, &econfig); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static struct platform_driver rockchip_efuse_driver = { + .probe = rockchip_efuse_probe, + .driver = { + .name = "rockchip-efuse", + .of_match_table = rockchip_efuse_match, + }, +}; + +module_platform_driver(rockchip_efuse_driver); +MODULE_DESCRIPTION("rockchip_efuse driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/rockchip-otp.c b/drivers/nvmem/rockchip-otp.c new file mode 100644 index 000000000..2c0feb036 --- /dev/null +++ b/drivers/nvmem/rockchip-otp.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Rockchip OTP Driver + * + * Copyright (c) 2018 Rockchip Electronics Co. Ltd. + * Author: Finley Xiao <finley.xiao@rock-chips.com> + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/reset.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +/* OTP Register Offsets */ +#define OTPC_SBPI_CTRL 0x0020 +#define OTPC_SBPI_CMD_VALID_PRE 0x0024 +#define OTPC_SBPI_CS_VALID_PRE 0x0028 +#define OTPC_SBPI_STATUS 0x002C +#define OTPC_USER_CTRL 0x0100 +#define OTPC_USER_ADDR 0x0104 +#define OTPC_USER_ENABLE 0x0108 +#define OTPC_USER_QP 0x0120 +#define OTPC_USER_Q 0x0124 +#define OTPC_INT_STATUS 0x0304 +#define OTPC_SBPI_CMD0_OFFSET 0x1000 +#define OTPC_SBPI_CMD1_OFFSET 0x1004 + +/* OTP Register bits and masks */ +#define OTPC_USER_ADDR_MASK GENMASK(31, 16) +#define OTPC_USE_USER BIT(0) +#define OTPC_USE_USER_MASK GENMASK(16, 16) +#define OTPC_USER_FSM_ENABLE BIT(0) +#define OTPC_USER_FSM_ENABLE_MASK GENMASK(16, 16) +#define OTPC_SBPI_DONE BIT(1) +#define OTPC_USER_DONE BIT(2) + +#define SBPI_DAP_ADDR 0x02 +#define SBPI_DAP_ADDR_SHIFT 8 +#define SBPI_DAP_ADDR_MASK GENMASK(31, 24) +#define SBPI_CMD_VALID_MASK GENMASK(31, 16) +#define SBPI_DAP_CMD_WRF 0xC0 +#define SBPI_DAP_REG_ECC 0x3A +#define SBPI_ECC_ENABLE 0x00 +#define SBPI_ECC_DISABLE 0x09 +#define SBPI_ENABLE BIT(0) +#define SBPI_ENABLE_MASK GENMASK(16, 16) + +#define OTPC_TIMEOUT 10000 + +/* RK3588 Register */ +#define RK3588_OTPC_AUTO_CTRL 0x04 +#define RK3588_OTPC_AUTO_EN 0x08 +#define RK3588_OTPC_INT_ST 0x84 +#define RK3588_OTPC_DOUT0 0x20 +#define RK3588_BURST_NUM 1 +#define RK3588_BURST_SHIFT 8 +#define RK3588_ADDR_SHIFT 16 +#define RK3588_AUTO_EN BIT(0) +#define RK3588_RD_DONE BIT(1) + +struct rockchip_data { + int size; + int read_offset; + int word_size; + const char * const *clks; + int num_clks; + nvmem_reg_read_t reg_read; +}; + +struct rockchip_otp { + struct device *dev; + void __iomem *base; + struct reset_control *rst; + const struct rockchip_data *data; + struct clk_bulk_data clks[]; +}; + +static int rockchip_otp_reset(struct rockchip_otp *otp) +{ + int ret; + + ret = reset_control_assert(otp->rst); + if (ret) { + dev_err(otp->dev, "failed to assert otp phy %d\n", ret); + return ret; + } + + udelay(2); + + ret = reset_control_deassert(otp->rst); + if (ret) { + dev_err(otp->dev, "failed to deassert otp phy %d\n", ret); + return ret; + } + + return 0; +} + +static int rockchip_otp_wait_status(struct rockchip_otp *otp, + unsigned int reg, u32 flag) +{ + u32 status = 0; + int ret; + + ret = readl_poll_timeout_atomic(otp->base + reg, status, + (status & flag), 1, OTPC_TIMEOUT); + if (ret) + return ret; + + /* clean int status */ + writel(flag, otp->base + reg); + + return 0; +} + +static int rockchip_otp_ecc_enable(struct rockchip_otp *otp, bool enable) +{ + int ret = 0; + + writel(SBPI_DAP_ADDR_MASK | (SBPI_DAP_ADDR << SBPI_DAP_ADDR_SHIFT), + otp->base + OTPC_SBPI_CTRL); + + writel(SBPI_CMD_VALID_MASK | 0x1, otp->base + OTPC_SBPI_CMD_VALID_PRE); + writel(SBPI_DAP_CMD_WRF | SBPI_DAP_REG_ECC, + otp->base + OTPC_SBPI_CMD0_OFFSET); + if (enable) + writel(SBPI_ECC_ENABLE, otp->base + OTPC_SBPI_CMD1_OFFSET); + else + writel(SBPI_ECC_DISABLE, otp->base + OTPC_SBPI_CMD1_OFFSET); + + writel(SBPI_ENABLE_MASK | SBPI_ENABLE, otp->base + OTPC_SBPI_CTRL); + + ret = rockchip_otp_wait_status(otp, OTPC_INT_STATUS, OTPC_SBPI_DONE); + if (ret < 0) + dev_err(otp->dev, "timeout during ecc_enable\n"); + + return ret; +} + +static int px30_otp_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct rockchip_otp *otp = context; + u8 *buf = val; + int ret; + + ret = rockchip_otp_reset(otp); + if (ret) { + dev_err(otp->dev, "failed to reset otp phy\n"); + return ret; + } + + ret = rockchip_otp_ecc_enable(otp, false); + if (ret < 0) { + dev_err(otp->dev, "rockchip_otp_ecc_enable err\n"); + return ret; + } + + writel(OTPC_USE_USER | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL); + udelay(5); + while (bytes--) { + writel(offset++ | OTPC_USER_ADDR_MASK, + otp->base + OTPC_USER_ADDR); + writel(OTPC_USER_FSM_ENABLE | OTPC_USER_FSM_ENABLE_MASK, + otp->base + OTPC_USER_ENABLE); + ret = rockchip_otp_wait_status(otp, OTPC_INT_STATUS, OTPC_USER_DONE); + if (ret < 0) { + dev_err(otp->dev, "timeout during read setup\n"); + goto read_end; + } + *buf++ = readb(otp->base + OTPC_USER_Q); + } + +read_end: + writel(0x0 | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL); + + return ret; +} + +static int rk3568_otp_read(void *context, unsigned int offset, void *val, + size_t count) +{ + struct rockchip_otp *otp = context; + u16 *buf = val; + u32 otp_qp; + int ret; + + ret = rockchip_otp_reset(otp); + if (ret) { + dev_err(otp->dev, "failed to reset otp phy\n"); + return ret; + } + + ret = rockchip_otp_ecc_enable(otp, true); + if (ret) { + dev_err(otp->dev, "rockchip_otp_ecc_enable err\n"); + return ret; + } + + writel(OTPC_USE_USER | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL); + udelay(5); + + while (count--) { + writel(offset++ | OTPC_USER_ADDR_MASK, + otp->base + OTPC_USER_ADDR); + writel(OTPC_USER_FSM_ENABLE | OTPC_USER_FSM_ENABLE_MASK, + otp->base + OTPC_USER_ENABLE); + + ret = rockchip_otp_wait_status(otp, OTPC_INT_STATUS, + OTPC_USER_DONE); + if (ret) { + dev_err(otp->dev, "timeout during read setup\n"); + goto read_end; + } + + otp_qp = readl(otp->base + OTPC_USER_QP); + if (((otp_qp & 0xc0) == 0xc0) || (otp_qp & 0x20)) { + ret = -EIO; + dev_err(otp->dev, "ecc check error during read setup\n"); + goto read_end; + } + + *buf++ = readl(otp->base + OTPC_USER_Q); + } + +read_end: + writel(0x0 | OTPC_USE_USER_MASK, otp->base + OTPC_USER_CTRL); + + return ret; +} + +static int rk3588_otp_read(void *context, unsigned int offset, + void *val, size_t count) +{ + struct rockchip_otp *otp = context; + u32 *buf = val; + int ret; + + while (count--) { + writel((offset++ << RK3588_ADDR_SHIFT) | + (RK3588_BURST_NUM << RK3588_BURST_SHIFT), + otp->base + RK3588_OTPC_AUTO_CTRL); + writel(RK3588_AUTO_EN, otp->base + RK3588_OTPC_AUTO_EN); + + ret = rockchip_otp_wait_status(otp, RK3588_OTPC_INT_ST, + RK3588_RD_DONE); + if (ret) { + dev_err(otp->dev, "timeout during read setup\n"); + return ret; + } + + *buf++ = readl(otp->base + RK3588_OTPC_DOUT0); + } + + return ret; +} + +static int rockchip_otp_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct rockchip_otp *otp = context; + int ret, word_size; + + if (!otp->data || !otp->data->reg_read) + return -EINVAL; + + ret = clk_bulk_prepare_enable(otp->data->num_clks, otp->clks); + if (ret < 0) { + dev_err(otp->dev, "failed to prepare/enable clks\n"); + return ret; + } + + offset += otp->data->read_offset; + word_size = otp->data->word_size; + + if (word_size > 1) { + unsigned int addr_start, addr_end; + size_t count; + u8 *buf; + + addr_start = offset / word_size; + addr_end = DIV_ROUND_UP(offset + bytes, word_size); + count = addr_end - addr_start; + + buf = kzalloc(array_size(count, word_size), GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto err; + } + + ret = otp->data->reg_read(context, addr_start, buf, count); + if (!ret) + memcpy(val, buf + (offset % word_size), bytes); + + kfree(buf); + } else { + ret = otp->data->reg_read(context, offset, val, bytes); + } + +err: + clk_bulk_disable_unprepare(otp->data->num_clks, otp->clks); + + return ret; +} + +static struct nvmem_config otp_config = { + .name = "rockchip-otp", + .owner = THIS_MODULE, + .add_legacy_fixed_of_cells = true, + .type = NVMEM_TYPE_OTP, + .read_only = true, + .stride = 1, + .word_size = sizeof(u8), + .reg_read = rockchip_otp_read, +}; + +static const char * const px30_otp_clocks[] = { + "otp", "apb_pclk", "phy", +}; + +static const struct rockchip_data px30_data = { + .size = 0x40, + .clks = px30_otp_clocks, + .num_clks = ARRAY_SIZE(px30_otp_clocks), + .reg_read = px30_otp_read, +}; + +static const char * const rk3528_otp_clocks[] = { + "otp", "apb_pclk", "sbpi", +}; + +static const struct rockchip_data rk3528_data = { + .size = 0x80, + .word_size = sizeof(u16), + .clks = rk3528_otp_clocks, + .num_clks = ARRAY_SIZE(rk3528_otp_clocks), + .reg_read = rk3568_otp_read, +}; + +static const char * const rk3568_otp_clocks[] = { + "otp", "apb_pclk", "phy", "sbpi", +}; + +static const struct rockchip_data rk3568_data = { + .size = 0x80, + .word_size = sizeof(u16), + .clks = rk3568_otp_clocks, + .num_clks = ARRAY_SIZE(rk3568_otp_clocks), + .reg_read = rk3568_otp_read, +}; + +static const struct rockchip_data rk3576_data = { + .size = 0x100, + .read_offset = 0x700, + .word_size = sizeof(u32), + .clks = px30_otp_clocks, + .num_clks = ARRAY_SIZE(px30_otp_clocks), + .reg_read = rk3588_otp_read, +}; + +static const char * const rk3588_otp_clocks[] = { + "otp", "apb_pclk", "phy", "arb", +}; + +static const struct rockchip_data rk3588_data = { + .size = 0x400, + .read_offset = 0xc00, + .word_size = sizeof(u32), + .clks = rk3588_otp_clocks, + .num_clks = ARRAY_SIZE(rk3588_otp_clocks), + .reg_read = rk3588_otp_read, +}; + +static const struct of_device_id rockchip_otp_match[] = { + { + .compatible = "rockchip,px30-otp", + .data = &px30_data, + }, + { + .compatible = "rockchip,rk3308-otp", + .data = &px30_data, + }, + { + .compatible = "rockchip,rk3528-otp", + .data = &rk3528_data, + }, + { + .compatible = "rockchip,rk3562-otp", + .data = &rk3568_data, + }, + { + .compatible = "rockchip,rk3568-otp", + .data = &rk3568_data, + }, + { + .compatible = "rockchip,rk3576-otp", + .data = &rk3576_data, + }, + { + .compatible = "rockchip,rk3588-otp", + .data = &rk3588_data, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rockchip_otp_match); + +static int rockchip_otp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rockchip_otp *otp; + const struct rockchip_data *data; + struct nvmem_device *nvmem; + int ret, i; + + data = of_device_get_match_data(dev); + if (!data) + return dev_err_probe(dev, -EINVAL, "failed to get match data\n"); + + otp = devm_kzalloc(&pdev->dev, struct_size(otp, clks, data->num_clks), + GFP_KERNEL); + if (!otp) + return -ENOMEM; + + otp->data = data; + otp->dev = dev; + otp->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(otp->base)) + return dev_err_probe(dev, PTR_ERR(otp->base), + "failed to ioremap resource\n"); + + for (i = 0; i < data->num_clks; ++i) + otp->clks[i].id = data->clks[i]; + + ret = devm_clk_bulk_get(dev, data->num_clks, otp->clks); + if (ret) + return dev_err_probe(dev, ret, "failed to get clocks\n"); + + otp->rst = devm_reset_control_array_get_exclusive(dev); + if (IS_ERR(otp->rst)) + return dev_err_probe(dev, PTR_ERR(otp->rst), + "failed to get resets\n"); + + otp_config.size = data->size; + otp_config.priv = otp; + otp_config.dev = dev; + + nvmem = devm_nvmem_register(dev, &otp_config); + if (IS_ERR(nvmem)) + return dev_err_probe(dev, PTR_ERR(nvmem), + "failed to register nvmem device\n"); + return 0; +} + +static struct platform_driver rockchip_otp_driver = { + .probe = rockchip_otp_probe, + .driver = { + .name = "rockchip-otp", + .of_match_table = rockchip_otp_match, + }, +}; + +module_platform_driver(rockchip_otp_driver); +MODULE_DESCRIPTION("Rockchip OTP driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/s32g-ocotp-nvmem.c b/drivers/nvmem/s32g-ocotp-nvmem.c new file mode 100644 index 000000000..119871ab3 --- /dev/null +++ b/drivers/nvmem/s32g-ocotp-nvmem.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2023-2025 NXP + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +struct s32g_ocotp_priv { + struct device *dev; + void __iomem *base; +}; + +static int s32g_ocotp_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct s32g_ocotp_priv *s32g_data = context; + u32 *dst = val; + + while (bytes >= sizeof(u32)) { + *dst++ = ioread32(s32g_data->base + offset); + + bytes -= sizeof(u32); + offset += sizeof(u32); + } + + return 0; +} + +static struct nvmem_keepout s32g_keepouts[] = { + { .start = 0, .end = 520 }, + { .start = 540, .end = 564 }, + { .start = 596, .end = 664 }, + { .start = 668, .end = 676 }, + { .start = 684, .end = 732 }, + { .start = 744, .end = 864 }, + { .start = 908, .end = 924 }, + { .start = 928, .end = 936 }, + { .start = 948, .end = 964 }, + { .start = 968, .end = 976 }, + { .start = 984, .end = 1012 }, +}; + +static struct nvmem_config s32g_ocotp_nvmem_config = { + .name = "s32g-ocotp", + .add_legacy_fixed_of_cells = true, + .read_only = true, + .word_size = 4, + .reg_read = s32g_ocotp_read, + .keepout = s32g_keepouts, + .nkeepout = ARRAY_SIZE(s32g_keepouts), +}; + +static const struct of_device_id ocotp_of_match[] = { + { .compatible = "nxp,s32g2-ocotp" }, + { /* sentinel */ } +}; + +static int s32g_ocotp_probe(struct platform_device *pdev) +{ + struct s32g_ocotp_priv *s32g_data; + struct device *dev = &pdev->dev; + struct nvmem_device *nvmem; + struct resource *res; + + s32g_data = devm_kzalloc(dev, sizeof(*s32g_data), GFP_KERNEL); + if (!s32g_data) + return -ENOMEM; + + s32g_data->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(s32g_data->base)) + return dev_err_probe(dev, PTR_ERR(s32g_data->base), + "Cannot map OCOTP device.\n"); + + s32g_data->dev = dev; + s32g_ocotp_nvmem_config.dev = dev; + s32g_ocotp_nvmem_config.priv = s32g_data; + s32g_ocotp_nvmem_config.size = resource_size(res); + + nvmem = devm_nvmem_register(dev, &s32g_ocotp_nvmem_config); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static struct platform_driver s32g_ocotp_driver = { + .probe = s32g_ocotp_probe, + .driver = { + .name = "s32g-ocotp", + .of_match_table = ocotp_of_match, + }, +}; +module_platform_driver(s32g_ocotp_driver); +MODULE_AUTHOR("NXP"); +MODULE_DESCRIPTION("S32G OCOTP driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/sc27xx-efuse.c b/drivers/nvmem/sc27xx-efuse.c new file mode 100644 index 000000000..4e2ffefac --- /dev/null +++ b/drivers/nvmem/sc27xx-efuse.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2018 Spreadtrum Communications Inc. + +#include <linux/hwspinlock.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/nvmem-provider.h> + +/* PMIC global registers definition */ +#define SC27XX_MODULE_EN 0xc08 +#define SC2730_MODULE_EN 0x1808 +#define SC27XX_EFUSE_EN BIT(6) + +/* Efuse controller registers definition */ +#define SC27XX_EFUSE_GLB_CTRL 0x0 +#define SC27XX_EFUSE_DATA_RD 0x4 +#define SC27XX_EFUSE_DATA_WR 0x8 +#define SC27XX_EFUSE_BLOCK_INDEX 0xc +#define SC27XX_EFUSE_MODE_CTRL 0x10 +#define SC27XX_EFUSE_STATUS 0x14 +#define SC27XX_EFUSE_WR_TIMING_CTRL 0x20 +#define SC27XX_EFUSE_RD_TIMING_CTRL 0x24 +#define SC27XX_EFUSE_EFUSE_DEB_CTRL 0x28 + +/* Mask definition for SC27XX_EFUSE_BLOCK_INDEX register */ +#define SC27XX_EFUSE_BLOCK_MASK GENMASK(4, 0) + +/* Bits definitions for SC27XX_EFUSE_MODE_CTRL register */ +#define SC27XX_EFUSE_PG_START BIT(0) +#define SC27XX_EFUSE_RD_START BIT(1) +#define SC27XX_EFUSE_CLR_RDDONE BIT(2) + +/* Bits definitions for SC27XX_EFUSE_STATUS register */ +#define SC27XX_EFUSE_PGM_BUSY BIT(0) +#define SC27XX_EFUSE_READ_BUSY BIT(1) +#define SC27XX_EFUSE_STANDBY BIT(2) +#define SC27XX_EFUSE_GLOBAL_PROT BIT(3) +#define SC27XX_EFUSE_RD_DONE BIT(4) + +/* Block number and block width (bytes) definitions */ +#define SC27XX_EFUSE_BLOCK_MAX 32 +#define SC27XX_EFUSE_BLOCK_WIDTH 2 + +/* Timeout (ms) for the trylock of hardware spinlocks */ +#define SC27XX_EFUSE_HWLOCK_TIMEOUT 5000 + +/* Timeout (us) of polling the status */ +#define SC27XX_EFUSE_POLL_TIMEOUT 3000000 +#define SC27XX_EFUSE_POLL_DELAY_US 10000 + +/* + * Since different PMICs of SC27xx series can have different + * address , we should save address in the device data structure. + */ +struct sc27xx_efuse_variant_data { + u32 module_en; +}; + +struct sc27xx_efuse { + struct device *dev; + struct regmap *regmap; + struct hwspinlock *hwlock; + struct mutex mutex; + u32 base; + const struct sc27xx_efuse_variant_data *var_data; +}; + +static const struct sc27xx_efuse_variant_data sc2731_edata = { + .module_en = SC27XX_MODULE_EN, +}; + +static const struct sc27xx_efuse_variant_data sc2730_edata = { + .module_en = SC2730_MODULE_EN, +}; + +/* + * On Spreadtrum platform, we have multi-subsystems will access the unique + * efuse controller, so we need one hardware spinlock to synchronize between + * the multiple subsystems. + */ +static int sc27xx_efuse_lock(struct sc27xx_efuse *efuse) +{ + int ret; + + mutex_lock(&efuse->mutex); + + ret = hwspin_lock_timeout_raw(efuse->hwlock, + SC27XX_EFUSE_HWLOCK_TIMEOUT); + if (ret) { + dev_err(efuse->dev, "timeout to get the hwspinlock\n"); + mutex_unlock(&efuse->mutex); + return ret; + } + + return 0; +} + +static void sc27xx_efuse_unlock(struct sc27xx_efuse *efuse) +{ + hwspin_unlock_raw(efuse->hwlock); + mutex_unlock(&efuse->mutex); +} + +static int sc27xx_efuse_poll_status(struct sc27xx_efuse *efuse, u32 bits) +{ + int ret; + u32 val; + + ret = regmap_read_poll_timeout(efuse->regmap, + efuse->base + SC27XX_EFUSE_STATUS, + val, (val & bits), + SC27XX_EFUSE_POLL_DELAY_US, + SC27XX_EFUSE_POLL_TIMEOUT); + if (ret) { + dev_err(efuse->dev, "timeout to update the efuse status\n"); + return ret; + } + + return 0; +} + +static int sc27xx_efuse_read(void *context, u32 offset, void *val, size_t bytes) +{ + struct sc27xx_efuse *efuse = context; + u32 buf, blk_index = offset / SC27XX_EFUSE_BLOCK_WIDTH; + u32 blk_offset = (offset % SC27XX_EFUSE_BLOCK_WIDTH) * BITS_PER_BYTE; + int ret; + + if (blk_index > SC27XX_EFUSE_BLOCK_MAX || + bytes > SC27XX_EFUSE_BLOCK_WIDTH) + return -EINVAL; + + ret = sc27xx_efuse_lock(efuse); + if (ret) + return ret; + + /* Enable the efuse controller. */ + ret = regmap_update_bits(efuse->regmap, efuse->var_data->module_en, + SC27XX_EFUSE_EN, SC27XX_EFUSE_EN); + if (ret) + goto unlock_efuse; + + /* + * Before reading, we should ensure the efuse controller is in + * standby state. + */ + ret = sc27xx_efuse_poll_status(efuse, SC27XX_EFUSE_STANDBY); + if (ret) + goto disable_efuse; + + /* Set the block address to be read. */ + ret = regmap_write(efuse->regmap, + efuse->base + SC27XX_EFUSE_BLOCK_INDEX, + blk_index & SC27XX_EFUSE_BLOCK_MASK); + if (ret) + goto disable_efuse; + + /* Start reading process from efuse memory. */ + ret = regmap_update_bits(efuse->regmap, + efuse->base + SC27XX_EFUSE_MODE_CTRL, + SC27XX_EFUSE_RD_START, + SC27XX_EFUSE_RD_START); + if (ret) + goto disable_efuse; + + /* + * Polling the read done status to make sure the reading process + * is completed, that means the data can be read out now. + */ + ret = sc27xx_efuse_poll_status(efuse, SC27XX_EFUSE_RD_DONE); + if (ret) + goto disable_efuse; + + /* Read data from efuse memory. */ + ret = regmap_read(efuse->regmap, efuse->base + SC27XX_EFUSE_DATA_RD, + &buf); + if (ret) + goto disable_efuse; + + /* Clear the read done flag. */ + ret = regmap_update_bits(efuse->regmap, + efuse->base + SC27XX_EFUSE_MODE_CTRL, + SC27XX_EFUSE_CLR_RDDONE, + SC27XX_EFUSE_CLR_RDDONE); + +disable_efuse: + /* Disable the efuse controller after reading. */ + regmap_update_bits(efuse->regmap, efuse->var_data->module_en, SC27XX_EFUSE_EN, 0); +unlock_efuse: + sc27xx_efuse_unlock(efuse); + + if (!ret) { + buf >>= blk_offset; + memcpy(val, &buf, bytes); + } + + return ret; +} + +static int sc27xx_efuse_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct nvmem_config econfig = { }; + struct nvmem_device *nvmem; + struct sc27xx_efuse *efuse; + int ret; + + efuse = devm_kzalloc(&pdev->dev, sizeof(*efuse), GFP_KERNEL); + if (!efuse) + return -ENOMEM; + + efuse->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!efuse->regmap) { + dev_err(&pdev->dev, "failed to get efuse regmap\n"); + return -ENODEV; + } + + ret = of_property_read_u32(np, "reg", &efuse->base); + if (ret) { + dev_err(&pdev->dev, "failed to get efuse base address\n"); + return ret; + } + + ret = of_hwspin_lock_get_id(np, 0); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get hwspinlock id\n"); + return ret; + } + + efuse->hwlock = devm_hwspin_lock_request_specific(&pdev->dev, ret); + if (!efuse->hwlock) { + dev_err(&pdev->dev, "failed to request hwspinlock\n"); + return -ENXIO; + } + + mutex_init(&efuse->mutex); + efuse->dev = &pdev->dev; + efuse->var_data = of_device_get_match_data(&pdev->dev); + + econfig.stride = 1; + econfig.word_size = 1; + econfig.read_only = true; + econfig.name = "sc27xx-efuse"; + econfig.size = SC27XX_EFUSE_BLOCK_MAX * SC27XX_EFUSE_BLOCK_WIDTH; + econfig.reg_read = sc27xx_efuse_read; + econfig.priv = efuse; + econfig.dev = &pdev->dev; + econfig.add_legacy_fixed_of_cells = true; + nvmem = devm_nvmem_register(&pdev->dev, &econfig); + if (IS_ERR(nvmem)) { + dev_err(&pdev->dev, "failed to register nvmem config\n"); + return PTR_ERR(nvmem); + } + + return 0; +} + +static const struct of_device_id sc27xx_efuse_of_match[] = { + { .compatible = "sprd,sc2731-efuse", .data = &sc2731_edata}, + { .compatible = "sprd,sc2730-efuse", .data = &sc2730_edata}, + { } +}; +MODULE_DEVICE_TABLE(of, sc27xx_efuse_of_match); + +static struct platform_driver sc27xx_efuse_driver = { + .probe = sc27xx_efuse_probe, + .driver = { + .name = "sc27xx-efuse", + .of_match_table = sc27xx_efuse_of_match, + }, +}; + +module_platform_driver(sc27xx_efuse_driver); + +MODULE_AUTHOR("Freeman Liu <freeman.liu@spreadtrum.com>"); +MODULE_DESCRIPTION("Spreadtrum SC27xx efuse driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/sec-qfprom.c b/drivers/nvmem/sec-qfprom.c new file mode 100644 index 000000000..51d21e65a --- /dev/null +++ b/drivers/nvmem/sec-qfprom.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include <linux/firmware/qcom/qcom_scm.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +/** + * struct sec_qfprom - structure holding secure qfprom attributes + * + * @base: starting physical address for secure qfprom corrected address space. + * @dev: qfprom device structure. + */ +struct sec_qfprom { + phys_addr_t base; + struct device *dev; +}; + +static int sec_qfprom_reg_read(void *context, unsigned int reg, void *_val, size_t bytes) +{ + struct sec_qfprom *priv = context; + unsigned int i; + u8 *val = _val; + u32 read_val; + u8 *tmp; + + for (i = 0; i < bytes; i++, reg++) { + if (i == 0 || reg % 4 == 0) { + if (qcom_scm_io_readl(priv->base + (reg & ~3), &read_val)) { + dev_err(priv->dev, "Couldn't access fuse register\n"); + return -EINVAL; + } + tmp = (u8 *)&read_val; + } + + val[i] = tmp[reg & 3]; + } + + return 0; +} + +static int sec_qfprom_probe(struct platform_device *pdev) +{ + struct nvmem_config econfig = { + .name = "sec-qfprom", + .add_legacy_fixed_of_cells = true, + .stride = 1, + .word_size = 1, + .id = NVMEM_DEVID_AUTO, + .reg_read = sec_qfprom_reg_read, + }; + struct device *dev = &pdev->dev; + struct nvmem_device *nvmem; + struct sec_qfprom *priv; + struct resource *res; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EINVAL; + + priv->base = res->start; + + econfig.size = resource_size(res); + econfig.dev = dev; + econfig.priv = priv; + + priv->dev = dev; + + nvmem = devm_nvmem_register(dev, &econfig); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id sec_qfprom_of_match[] = { + { .compatible = "qcom,sec-qfprom" }, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, sec_qfprom_of_match); + +static struct platform_driver qfprom_driver = { + .probe = sec_qfprom_probe, + .driver = { + .name = "qcom_sec_qfprom", + .of_match_table = sec_qfprom_of_match, + }, +}; +module_platform_driver(qfprom_driver); +MODULE_DESCRIPTION("Qualcomm Secure QFPROM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/snvs_lpgpr.c b/drivers/nvmem/snvs_lpgpr.c new file mode 100644 index 000000000..89c271123 --- /dev/null +++ b/drivers/nvmem/snvs_lpgpr.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 Pengutronix, Steffen Trumtrar <kernel@pengutronix.de> + * Copyright (c) 2017 Pengutronix, Oleksij Rempel <kernel@pengutronix.de> + */ + +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define IMX6Q_SNVS_HPLR 0x00 +#define IMX6Q_SNVS_LPLR 0x34 +#define IMX6Q_SNVS_LPGPR 0x68 + +#define IMX7D_SNVS_HPLR 0x00 +#define IMX7D_SNVS_LPLR 0x34 +#define IMX7D_SNVS_LPGPR 0x90 + +#define IMX_GPR_SL BIT(5) +#define IMX_GPR_HL BIT(5) + +struct snvs_lpgpr_cfg { + int offset; + int offset_hplr; + int offset_lplr; + int size; +}; + +struct snvs_lpgpr_priv { + struct device_d *dev; + struct regmap *regmap; + struct nvmem_config cfg; + const struct snvs_lpgpr_cfg *dcfg; +}; + +static const struct snvs_lpgpr_cfg snvs_lpgpr_cfg_imx6q = { + .offset = IMX6Q_SNVS_LPGPR, + .offset_hplr = IMX6Q_SNVS_HPLR, + .offset_lplr = IMX6Q_SNVS_LPLR, + .size = 4, +}; + +static const struct snvs_lpgpr_cfg snvs_lpgpr_cfg_imx7d = { + .offset = IMX7D_SNVS_LPGPR, + .offset_hplr = IMX7D_SNVS_HPLR, + .offset_lplr = IMX7D_SNVS_LPLR, + .size = 16, +}; + +static int snvs_lpgpr_write(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct snvs_lpgpr_priv *priv = context; + const struct snvs_lpgpr_cfg *dcfg = priv->dcfg; + unsigned int lock_reg; + int ret; + + ret = regmap_read(priv->regmap, dcfg->offset_hplr, &lock_reg); + if (ret < 0) + return ret; + + if (lock_reg & IMX_GPR_SL) + return -EPERM; + + ret = regmap_read(priv->regmap, dcfg->offset_lplr, &lock_reg); + if (ret < 0) + return ret; + + if (lock_reg & IMX_GPR_HL) + return -EPERM; + + return regmap_bulk_write(priv->regmap, dcfg->offset + offset, val, + bytes / 4); +} + +static int snvs_lpgpr_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct snvs_lpgpr_priv *priv = context; + const struct snvs_lpgpr_cfg *dcfg = priv->dcfg; + + return regmap_bulk_read(priv->regmap, dcfg->offset + offset, + val, bytes / 4); +} + +static int snvs_lpgpr_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct device_node *syscon_node; + struct snvs_lpgpr_priv *priv; + struct nvmem_config *cfg; + struct nvmem_device *nvmem; + const struct snvs_lpgpr_cfg *dcfg; + + if (!node) + return -ENOENT; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dcfg = of_device_get_match_data(dev); + if (!dcfg) + return -EINVAL; + + syscon_node = of_get_parent(node); + if (!syscon_node) + return -ENODEV; + + priv->regmap = syscon_node_to_regmap(syscon_node); + of_node_put(syscon_node); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->dcfg = dcfg; + + cfg = &priv->cfg; + cfg->priv = priv; + cfg->name = dev_name(dev); + cfg->dev = dev; + cfg->stride = 4; + cfg->word_size = 4; + cfg->size = dcfg->size; + cfg->owner = THIS_MODULE; + cfg->reg_read = snvs_lpgpr_read; + cfg->reg_write = snvs_lpgpr_write; + + nvmem = devm_nvmem_register(dev, cfg); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id snvs_lpgpr_dt_ids[] = { + { .compatible = "fsl,imx6q-snvs-lpgpr", .data = &snvs_lpgpr_cfg_imx6q }, + { .compatible = "fsl,imx6ul-snvs-lpgpr", + .data = &snvs_lpgpr_cfg_imx6q }, + { .compatible = "fsl,imx7d-snvs-lpgpr", .data = &snvs_lpgpr_cfg_imx7d }, + { }, +}; +MODULE_DEVICE_TABLE(of, snvs_lpgpr_dt_ids); + +static struct platform_driver snvs_lpgpr_driver = { + .probe = snvs_lpgpr_probe, + .driver = { + .name = "snvs_lpgpr", + .of_match_table = snvs_lpgpr_dt_ids, + }, +}; +module_platform_driver(snvs_lpgpr_driver); + +MODULE_AUTHOR("Oleksij Rempel <o.rempel@pengutronix.de>"); +MODULE_DESCRIPTION("Low Power General Purpose Register in i.MX6 and i.MX7 Secure Non-Volatile Storage"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/sprd-efuse.c b/drivers/nvmem/sprd-efuse.c new file mode 100644 index 000000000..1a7e4e5d8 --- /dev/null +++ b/drivers/nvmem/sprd-efuse.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Spreadtrum Communications Inc. + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/hwspinlock.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#define SPRD_EFUSE_ENABLE 0x20 +#define SPRD_EFUSE_ERR_FLAG 0x24 +#define SPRD_EFUSE_ERR_CLR 0x28 +#define SPRD_EFUSE_MAGIC_NUM 0x2c +#define SPRD_EFUSE_FW_CFG 0x50 +#define SPRD_EFUSE_PW_SWT 0x54 +#define SPRD_EFUSE_MEM(val) (0x1000 + ((val) << 2)) + +#define SPRD_EFUSE_VDD_EN BIT(0) +#define SPRD_EFUSE_AUTO_CHECK_EN BIT(1) +#define SPRD_EFUSE_DOUBLE_EN BIT(2) +#define SPRD_EFUSE_MARGIN_RD_EN BIT(3) +#define SPRD_EFUSE_LOCK_WR_EN BIT(4) + +#define SPRD_EFUSE_ERR_CLR_MASK GENMASK(13, 0) + +#define SPRD_EFUSE_ENK1_ON BIT(0) +#define SPRD_EFUSE_ENK2_ON BIT(1) +#define SPRD_EFUSE_PROG_EN BIT(2) + +#define SPRD_EFUSE_MAGIC_NUMBER 0x8810 + +/* Block width (bytes) definitions */ +#define SPRD_EFUSE_BLOCK_WIDTH 4 + +/* + * The Spreadtrum AP efuse contains 2 parts: normal efuse and secure efuse, + * and we can only access the normal efuse in kernel. So define the normal + * block offset index and normal block numbers. + */ +#define SPRD_EFUSE_NORMAL_BLOCK_NUMS 24 +#define SPRD_EFUSE_NORMAL_BLOCK_OFFSET 72 + +/* Timeout (ms) for the trylock of hardware spinlocks */ +#define SPRD_EFUSE_HWLOCK_TIMEOUT 5000 + +/* + * Since different Spreadtrum SoC chip can have different normal block numbers + * and offset. And some SoC can support block double feature, which means + * when reading or writing data to efuse memory, the controller can save double + * data in case one data become incorrect after a long period. + * + * Thus we should save them in the device data structure. + */ +struct sprd_efuse_variant_data { + u32 blk_nums; + u32 blk_offset; + bool blk_double; +}; + +struct sprd_efuse { + struct device *dev; + struct clk *clk; + struct hwspinlock *hwlock; + struct mutex mutex; + void __iomem *base; + const struct sprd_efuse_variant_data *data; +}; + +static const struct sprd_efuse_variant_data ums312_data = { + .blk_nums = SPRD_EFUSE_NORMAL_BLOCK_NUMS, + .blk_offset = SPRD_EFUSE_NORMAL_BLOCK_OFFSET, + .blk_double = false, +}; + +/* + * On Spreadtrum platform, we have multi-subsystems will access the unique + * efuse controller, so we need one hardware spinlock to synchronize between + * the multiple subsystems. + */ +static int sprd_efuse_lock(struct sprd_efuse *efuse) +{ + int ret; + + mutex_lock(&efuse->mutex); + + ret = hwspin_lock_timeout_raw(efuse->hwlock, + SPRD_EFUSE_HWLOCK_TIMEOUT); + if (ret) { + dev_err(efuse->dev, "timeout get the hwspinlock\n"); + mutex_unlock(&efuse->mutex); + return ret; + } + + return 0; +} + +static void sprd_efuse_unlock(struct sprd_efuse *efuse) +{ + hwspin_unlock_raw(efuse->hwlock); + mutex_unlock(&efuse->mutex); +} + +static void sprd_efuse_set_prog_power(struct sprd_efuse *efuse, bool en) +{ + u32 val = readl(efuse->base + SPRD_EFUSE_PW_SWT); + + if (en) + val &= ~SPRD_EFUSE_ENK2_ON; + else + val &= ~SPRD_EFUSE_ENK1_ON; + + writel(val, efuse->base + SPRD_EFUSE_PW_SWT); + + /* Open or close efuse power need wait 1000us to make power stable. */ + usleep_range(1000, 1200); + + if (en) + val |= SPRD_EFUSE_ENK1_ON; + else + val |= SPRD_EFUSE_ENK2_ON; + + writel(val, efuse->base + SPRD_EFUSE_PW_SWT); + + /* Open or close efuse power need wait 1000us to make power stable. */ + usleep_range(1000, 1200); +} + +static void sprd_efuse_set_read_power(struct sprd_efuse *efuse, bool en) +{ + u32 val = readl(efuse->base + SPRD_EFUSE_ENABLE); + + if (en) + val |= SPRD_EFUSE_VDD_EN; + else + val &= ~SPRD_EFUSE_VDD_EN; + + writel(val, efuse->base + SPRD_EFUSE_ENABLE); + + /* Open or close efuse power need wait 1000us to make power stable. */ + usleep_range(1000, 1200); +} + +static void sprd_efuse_set_prog_lock(struct sprd_efuse *efuse, bool en) +{ + u32 val = readl(efuse->base + SPRD_EFUSE_ENABLE); + + if (en) + val |= SPRD_EFUSE_LOCK_WR_EN; + else + val &= ~SPRD_EFUSE_LOCK_WR_EN; + + writel(val, efuse->base + SPRD_EFUSE_ENABLE); +} + +static void sprd_efuse_set_auto_check(struct sprd_efuse *efuse, bool en) +{ + u32 val = readl(efuse->base + SPRD_EFUSE_ENABLE); + + if (en) + val |= SPRD_EFUSE_AUTO_CHECK_EN; + else + val &= ~SPRD_EFUSE_AUTO_CHECK_EN; + + writel(val, efuse->base + SPRD_EFUSE_ENABLE); +} + +static void sprd_efuse_set_data_double(struct sprd_efuse *efuse, bool en) +{ + u32 val = readl(efuse->base + SPRD_EFUSE_ENABLE); + + if (en) + val |= SPRD_EFUSE_DOUBLE_EN; + else + val &= ~SPRD_EFUSE_DOUBLE_EN; + + writel(val, efuse->base + SPRD_EFUSE_ENABLE); +} + +static void sprd_efuse_set_prog_en(struct sprd_efuse *efuse, bool en) +{ + u32 val = readl(efuse->base + SPRD_EFUSE_PW_SWT); + + if (en) + val |= SPRD_EFUSE_PROG_EN; + else + val &= ~SPRD_EFUSE_PROG_EN; + + writel(val, efuse->base + SPRD_EFUSE_PW_SWT); +} + +static int sprd_efuse_raw_prog(struct sprd_efuse *efuse, u32 blk, bool doub, + bool lock, u32 *data) +{ + u32 status; + int ret = 0; + + /* + * We need set the correct magic number before writing the efuse to + * allow programming, and block other programming until we clear the + * magic number. + */ + writel(SPRD_EFUSE_MAGIC_NUMBER, + efuse->base + SPRD_EFUSE_MAGIC_NUM); + + /* + * Power on the efuse, enable programme and enable double data + * if asked. + */ + sprd_efuse_set_prog_power(efuse, true); + sprd_efuse_set_prog_en(efuse, true); + sprd_efuse_set_data_double(efuse, doub); + + /* + * Enable the auto-check function to validate if the programming is + * successful. + */ + if (lock) + sprd_efuse_set_auto_check(efuse, true); + + writel(*data, efuse->base + SPRD_EFUSE_MEM(blk)); + + /* Disable auto-check and data double after programming */ + if (lock) + sprd_efuse_set_auto_check(efuse, false); + sprd_efuse_set_data_double(efuse, false); + + /* + * Check the efuse error status, if the programming is successful, + * we should lock this efuse block to avoid programming again. + */ + status = readl(efuse->base + SPRD_EFUSE_ERR_FLAG); + if (status) { + dev_err(efuse->dev, + "write error status %u of block %d\n", status, blk); + + writel(SPRD_EFUSE_ERR_CLR_MASK, + efuse->base + SPRD_EFUSE_ERR_CLR); + ret = -EBUSY; + } else if (lock) { + sprd_efuse_set_prog_lock(efuse, lock); + writel(0, efuse->base + SPRD_EFUSE_MEM(blk)); + sprd_efuse_set_prog_lock(efuse, false); + } + + sprd_efuse_set_prog_power(efuse, false); + writel(0, efuse->base + SPRD_EFUSE_MAGIC_NUM); + + return ret; +} + +static int sprd_efuse_raw_read(struct sprd_efuse *efuse, int blk, u32 *val, + bool doub) +{ + u32 status; + + /* + * Need power on the efuse before reading data from efuse, and will + * power off the efuse after reading process. + */ + sprd_efuse_set_read_power(efuse, true); + + /* Enable double data if asked */ + sprd_efuse_set_data_double(efuse, doub); + + /* Start to read data from efuse block */ + *val = readl(efuse->base + SPRD_EFUSE_MEM(blk)); + + /* Disable double data */ + sprd_efuse_set_data_double(efuse, false); + + /* Power off the efuse */ + sprd_efuse_set_read_power(efuse, false); + + /* + * Check the efuse error status and clear them if there are some + * errors occurred. + */ + status = readl(efuse->base + SPRD_EFUSE_ERR_FLAG); + if (status) { + dev_err(efuse->dev, + "read error status %d of block %d\n", status, blk); + + writel(SPRD_EFUSE_ERR_CLR_MASK, + efuse->base + SPRD_EFUSE_ERR_CLR); + return -EBUSY; + } + + return 0; +} + +static int sprd_efuse_read(void *context, u32 offset, void *val, size_t bytes) +{ + struct sprd_efuse *efuse = context; + bool blk_double = efuse->data->blk_double; + u32 index = offset / SPRD_EFUSE_BLOCK_WIDTH + efuse->data->blk_offset; + u32 blk_offset = (offset % SPRD_EFUSE_BLOCK_WIDTH) * BITS_PER_BYTE; + u32 data; + int ret; + + ret = sprd_efuse_lock(efuse); + if (ret) + return ret; + + ret = clk_prepare_enable(efuse->clk); + if (ret) + goto unlock; + + ret = sprd_efuse_raw_read(efuse, index, &data, blk_double); + if (!ret) { + data >>= blk_offset; + memcpy(val, &data, bytes); + } + + clk_disable_unprepare(efuse->clk); + +unlock: + sprd_efuse_unlock(efuse); + return ret; +} + +static int sprd_efuse_write(void *context, u32 offset, void *val, size_t bytes) +{ + struct sprd_efuse *efuse = context; + bool blk_double = efuse->data->blk_double; + bool lock; + int ret; + + ret = sprd_efuse_lock(efuse); + if (ret) + return ret; + + ret = clk_prepare_enable(efuse->clk); + if (ret) + goto unlock; + + /* + * If the writing bytes are equal with the block width, which means the + * whole block will be programmed. For this case, we should not allow + * this block to be programmed again by locking this block. + * + * If the block was programmed partially, we should allow this block to + * be programmed again. + */ + if (bytes < SPRD_EFUSE_BLOCK_WIDTH) + lock = false; + else + lock = true; + + ret = sprd_efuse_raw_prog(efuse, offset, blk_double, lock, val); + + clk_disable_unprepare(efuse->clk); + +unlock: + sprd_efuse_unlock(efuse); + return ret; +} + +static int sprd_efuse_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct nvmem_device *nvmem; + struct nvmem_config econfig = { }; + struct sprd_efuse *efuse; + const struct sprd_efuse_variant_data *pdata; + int ret; + + pdata = of_device_get_match_data(&pdev->dev); + if (!pdata) { + dev_err(&pdev->dev, "No matching driver data found\n"); + return -EINVAL; + } + + efuse = devm_kzalloc(&pdev->dev, sizeof(*efuse), GFP_KERNEL); + if (!efuse) + return -ENOMEM; + + efuse->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(efuse->base)) + return PTR_ERR(efuse->base); + + ret = of_hwspin_lock_get_id(np, 0); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get hwlock id\n"); + return ret; + } + + efuse->hwlock = devm_hwspin_lock_request_specific(&pdev->dev, ret); + if (!efuse->hwlock) { + dev_err(&pdev->dev, "failed to request hwlock\n"); + return -ENXIO; + } + + efuse->clk = devm_clk_get(&pdev->dev, "enable"); + if (IS_ERR(efuse->clk)) { + dev_err(&pdev->dev, "failed to get enable clock\n"); + return PTR_ERR(efuse->clk); + } + + mutex_init(&efuse->mutex); + efuse->dev = &pdev->dev; + efuse->data = pdata; + + econfig.stride = 1; + econfig.word_size = 1; + econfig.read_only = false; + econfig.name = "sprd-efuse"; + econfig.size = efuse->data->blk_nums * SPRD_EFUSE_BLOCK_WIDTH; + econfig.add_legacy_fixed_of_cells = true; + econfig.reg_read = sprd_efuse_read; + econfig.reg_write = sprd_efuse_write; + econfig.priv = efuse; + econfig.dev = &pdev->dev; + nvmem = devm_nvmem_register(&pdev->dev, &econfig); + if (IS_ERR(nvmem)) { + dev_err(&pdev->dev, "failed to register nvmem\n"); + return PTR_ERR(nvmem); + } + + return 0; +} + +static const struct of_device_id sprd_efuse_of_match[] = { + { .compatible = "sprd,ums312-efuse", .data = &ums312_data }, + { } +}; +MODULE_DEVICE_TABLE(of, sprd_efuse_of_match); + +static struct platform_driver sprd_efuse_driver = { + .probe = sprd_efuse_probe, + .driver = { + .name = "sprd-efuse", + .of_match_table = sprd_efuse_of_match, + }, +}; + +module_platform_driver(sprd_efuse_driver); + +MODULE_AUTHOR("Freeman Liu <freeman.liu@spreadtrum.com>"); +MODULE_DESCRIPTION("Spreadtrum AP efuse driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/stm32-bsec-optee-ta.c b/drivers/nvmem/stm32-bsec-optee-ta.c new file mode 100644 index 000000000..f89ce791d --- /dev/null +++ b/drivers/nvmem/stm32-bsec-optee-ta.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * OP-TEE STM32MP BSEC PTA interface, used by STM32 ROMEM driver + * + * Copyright (C) 2022, STMicroelectronics - All Rights Reserved + */ + +#include <linux/tee_drv.h> + +#include "stm32-bsec-optee-ta.h" + +/* + * Read OTP memory + * + * [in] value[0].a OTP start offset in byte + * [in] value[0].b Access type (0:shadow, 1:fuse, 2:lock) + * [out] memref[1].buffer Output buffer to store read values + * [out] memref[1].size Size of OTP to be read + * + * Return codes: + * TEE_SUCCESS - Invoke command success + * TEE_ERROR_BAD_PARAMETERS - Incorrect input param + * TEE_ERROR_ACCESS_DENIED - OTP not accessible by caller + */ +#define PTA_BSEC_READ_MEM 0x0 + +/* + * Write OTP memory + * + * [in] value[0].a OTP start offset in byte + * [in] value[0].b Access type (0:shadow, 1:fuse, 2:lock) + * [in] memref[1].buffer Input buffer to read values + * [in] memref[1].size Size of OTP to be written + * + * Return codes: + * TEE_SUCCESS - Invoke command success + * TEE_ERROR_BAD_PARAMETERS - Incorrect input param + * TEE_ERROR_ACCESS_DENIED - OTP not accessible by caller + */ +#define PTA_BSEC_WRITE_MEM 0x1 + +/* value of PTA_BSEC access type = value[in] b */ +#define SHADOW_ACCESS 0 +#define FUSE_ACCESS 1 +#define LOCK_ACCESS 2 + +/* Bitfield definition for LOCK status */ +#define LOCK_PERM BIT(30) + +/* OP-TEE STM32MP BSEC TA UUID */ +static const uuid_t stm32mp_bsec_ta_uuid = + UUID_INIT(0x94cf71ad, 0x80e6, 0x40b5, + 0xa7, 0xc6, 0x3d, 0xc5, 0x01, 0xeb, 0x28, 0x03); + +/* + * Check whether this driver supports the BSEC TA in the TEE instance + * represented by the params (ver/data) to this function. + */ +static int stm32_bsec_optee_ta_match(struct tee_ioctl_version_data *ver, + const void *data) +{ + /* Currently this driver only supports GP compliant, OP-TEE based TA */ + if ((ver->impl_id == TEE_IMPL_ID_OPTEE) && + (ver->gen_caps & TEE_GEN_CAP_GP)) + return 1; + else + return 0; +} + +/* Open a session to OP-TEE for STM32MP BSEC TA */ +static int stm32_bsec_ta_open_session(struct tee_context *ctx, u32 *id) +{ + struct tee_ioctl_open_session_arg sess_arg; + int rc; + + memset(&sess_arg, 0, sizeof(sess_arg)); + export_uuid(sess_arg.uuid, &stm32mp_bsec_ta_uuid); + sess_arg.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL; + sess_arg.num_params = 0; + + rc = tee_client_open_session(ctx, &sess_arg, NULL); + if ((rc < 0) || (sess_arg.ret != 0)) { + pr_err("%s: tee_client_open_session failed err:%#x, ret:%#x\n", + __func__, sess_arg.ret, rc); + if (!rc) + rc = -EINVAL; + } else { + *id = sess_arg.session; + } + + return rc; +} + +/* close a session to OP-TEE for STM32MP BSEC TA */ +static void stm32_bsec_ta_close_session(void *ctx, u32 id) +{ + tee_client_close_session(ctx, id); +} + +/* stm32_bsec_optee_ta_open() - initialize the STM32MP BSEC TA */ +int stm32_bsec_optee_ta_open(struct tee_context **ctx) +{ + struct tee_context *tee_ctx; + u32 session_id; + int rc; + + /* Open context with TEE driver */ + tee_ctx = tee_client_open_context(NULL, stm32_bsec_optee_ta_match, NULL, NULL); + if (IS_ERR(tee_ctx)) { + rc = PTR_ERR(tee_ctx); + if (rc == -ENOENT) + return -EPROBE_DEFER; + pr_err("%s: tee_client_open_context failed (%d)\n", __func__, rc); + + return rc; + } + + /* Check STM32MP BSEC TA presence */ + rc = stm32_bsec_ta_open_session(tee_ctx, &session_id); + if (rc) { + tee_client_close_context(tee_ctx); + return rc; + } + + stm32_bsec_ta_close_session(tee_ctx, session_id); + + *ctx = tee_ctx; + + return 0; +} + +/* stm32_bsec_optee_ta_open() - release the PTA STM32MP BSEC TA */ +void stm32_bsec_optee_ta_close(void *ctx) +{ + tee_client_close_context(ctx); +} + +/* stm32_bsec_optee_ta_read() - nvmem read access using PTA client driver */ +int stm32_bsec_optee_ta_read(struct tee_context *ctx, unsigned int offset, + void *buf, size_t bytes) +{ + struct tee_shm *shm; + struct tee_ioctl_invoke_arg arg; + struct tee_param param[2]; + u8 *shm_buf; + u32 start, num_bytes; + int ret; + u32 session_id; + + ret = stm32_bsec_ta_open_session(ctx, &session_id); + if (ret) + return ret; + + memset(&arg, 0, sizeof(arg)); + memset(¶m, 0, sizeof(param)); + + arg.func = PTA_BSEC_READ_MEM; + arg.session = session_id; + arg.num_params = 2; + + /* align access on 32bits */ + start = ALIGN_DOWN(offset, 4); + num_bytes = round_up(offset + bytes - start, 4); + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; + param[0].u.value.a = start; + param[0].u.value.b = SHADOW_ACCESS; + + shm = tee_shm_alloc_kernel_buf(ctx, num_bytes); + if (IS_ERR(shm)) { + ret = PTR_ERR(shm); + goto out_tee_session; + } + + param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT; + param[1].u.memref.shm = shm; + param[1].u.memref.size = num_bytes; + + ret = tee_client_invoke_func(ctx, &arg, param); + if (ret < 0 || arg.ret != 0) { + pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n", + arg.ret, ret); + if (!ret) + ret = -EIO; + } + if (!ret) { + shm_buf = tee_shm_get_va(shm, 0); + if (IS_ERR(shm_buf)) { + ret = PTR_ERR(shm_buf); + pr_err("tee_shm_get_va failed for transmit (%d)\n", ret); + } else { + /* read data from 32 bits aligned buffer */ + memcpy(buf, &shm_buf[offset % 4], bytes); + } + } + + tee_shm_free(shm); + +out_tee_session: + stm32_bsec_ta_close_session(ctx, session_id); + + return ret; +} + +/* stm32_bsec_optee_ta_write() - nvmem write access using PTA client driver */ +int stm32_bsec_optee_ta_write(struct tee_context *ctx, unsigned int lower, + unsigned int offset, void *buf, size_t bytes) +{ struct tee_shm *shm; + struct tee_ioctl_invoke_arg arg; + struct tee_param param[2]; + u8 *shm_buf; + int ret; + u32 session_id; + + ret = stm32_bsec_ta_open_session(ctx, &session_id); + if (ret) + return ret; + + /* Allow only writing complete 32-bits aligned words */ + if ((bytes % 4) || (offset % 4)) + return -EINVAL; + + memset(&arg, 0, sizeof(arg)); + memset(¶m, 0, sizeof(param)); + + arg.func = PTA_BSEC_WRITE_MEM; + arg.session = session_id; + arg.num_params = 2; + + param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; + param[0].u.value.a = offset; + param[0].u.value.b = FUSE_ACCESS; + + shm = tee_shm_alloc_kernel_buf(ctx, bytes); + if (IS_ERR(shm)) { + ret = PTR_ERR(shm); + goto out_tee_session; + } + + param[1].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT; + param[1].u.memref.shm = shm; + param[1].u.memref.size = bytes; + + shm_buf = tee_shm_get_va(shm, 0); + if (IS_ERR(shm_buf)) { + ret = PTR_ERR(shm_buf); + pr_err("tee_shm_get_va failed for transmit (%d)\n", ret); + tee_shm_free(shm); + + goto out_tee_session; + } + + memcpy(shm_buf, buf, bytes); + + ret = tee_client_invoke_func(ctx, &arg, param); + if (ret < 0 || arg.ret != 0) { + pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n", arg.ret, ret); + if (!ret) + ret = -EIO; + } + pr_debug("Write OTPs %d to %zu, ret=%d\n", offset / 4, (offset + bytes) / 4, ret); + + /* Lock the upper OTPs with ECC protection, word programming only */ + if (!ret && ((offset + bytes) >= (lower * 4))) { + u32 start, nb_lock; + u32 *lock = (u32 *)shm_buf; + int i; + + /* + * don't lock the lower OTPs, no ECC protection and incremental + * bit programming, a second write is allowed + */ + start = max_t(u32, offset, lower * 4); + nb_lock = (offset + bytes - start) / 4; + + param[0].u.value.a = start; + param[0].u.value.b = LOCK_ACCESS; + param[1].u.memref.size = nb_lock * 4; + + for (i = 0; i < nb_lock; i++) + lock[i] = LOCK_PERM; + + ret = tee_client_invoke_func(ctx, &arg, param); + if (ret < 0 || arg.ret != 0) { + pr_err("TA_BSEC invoke failed TEE err:%#x, ret:%#x\n", arg.ret, ret); + if (!ret) + ret = -EIO; + } + pr_debug("Lock upper OTPs %d to %d, ret=%d\n", + start / 4, start / 4 + nb_lock, ret); + } + + tee_shm_free(shm); + +out_tee_session: + stm32_bsec_ta_close_session(ctx, session_id); + + return ret; +} diff --git a/drivers/nvmem/stm32-bsec-optee-ta.h b/drivers/nvmem/stm32-bsec-optee-ta.h new file mode 100644 index 000000000..3966a0535 --- /dev/null +++ b/drivers/nvmem/stm32-bsec-optee-ta.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * OP-TEE STM32MP BSEC PTA interface, used by STM32 ROMEM driver + * + * Copyright (C) 2022, STMicroelectronics - All Rights Reserved + */ + +#if IS_ENABLED(CONFIG_NVMEM_STM32_BSEC_OPTEE_TA) +/** + * stm32_bsec_optee_ta_open() - initialize the STM32 BSEC TA + * @ctx: the OP-TEE context on success + * + * Return: + * On success, 0. On failure, -errno. + */ +int stm32_bsec_optee_ta_open(struct tee_context **ctx); + +/** + * stm32_bsec_optee_ta_close() - release the STM32 BSEC TA + * @ctx: the OP-TEE context + * + * This function used to clean the OP-TEE resources initialized in + * stm32_bsec_optee_ta_open(); it can be used as callback to + * devm_add_action_or_reset() + */ +void stm32_bsec_optee_ta_close(void *ctx); + +/** + * stm32_bsec_optee_ta_read() - nvmem read access using TA client driver + * @ctx: the OP-TEE context provided by stm32_bsec_optee_ta_open + * @offset: nvmem offset + * @buf: buffer to fill with nvem values + * @bytes: number of bytes to read + * + * Return: + * On success, 0. On failure, -errno. + */ +int stm32_bsec_optee_ta_read(struct tee_context *ctx, unsigned int offset, + void *buf, size_t bytes); + +/** + * stm32_bsec_optee_ta_write() - nvmem write access using TA client driver + * @ctx: the OP-TEE context provided by stm32_bsec_optee_ta_open + * @lower: number of lower OTP, not protected by ECC + * @offset: nvmem offset + * @buf: buffer with nvem values + * @bytes: number of bytes to write + * + * Return: + * On success, 0. On failure, -errno. + */ +int stm32_bsec_optee_ta_write(struct tee_context *ctx, unsigned int lower, + unsigned int offset, void *buf, size_t bytes); + +#else + +static inline int stm32_bsec_optee_ta_open(struct tee_context **ctx) +{ + return -EOPNOTSUPP; +} + +static inline void stm32_bsec_optee_ta_close(void *ctx) +{ +} + +static inline int stm32_bsec_optee_ta_read(struct tee_context *ctx, + unsigned int offset, void *buf, + size_t bytes) +{ + return -EOPNOTSUPP; +} + +static inline int stm32_bsec_optee_ta_write(struct tee_context *ctx, + unsigned int lower, + unsigned int offset, void *buf, + size_t bytes) +{ + return -EOPNOTSUPP; +} +#endif /* CONFIG_NVMEM_STM32_BSEC_OPTEE_TA */ diff --git a/drivers/nvmem/stm32-romem.c b/drivers/nvmem/stm32-romem.c new file mode 100644 index 000000000..82879b1c9 --- /dev/null +++ b/drivers/nvmem/stm32-romem.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * STM32 Factory-programmed memory read access driver + * + * Copyright (C) 2017, STMicroelectronics - All Rights Reserved + * Author: Fabrice Gasnier <fabrice.gasnier@st.com> for STMicroelectronics. + */ + +#include <linux/arm-smccc.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/tee_drv.h> + +#include "stm32-bsec-optee-ta.h" + +/* BSEC secure service access from non-secure */ +#define STM32_SMC_BSEC 0x82001003 +#define STM32_SMC_READ_SHADOW 0x01 +#define STM32_SMC_PROG_OTP 0x02 +#define STM32_SMC_WRITE_SHADOW 0x03 +#define STM32_SMC_READ_OTP 0x04 + +/* shadow registers offset */ +#define STM32MP15_BSEC_DATA0 0x200 + +struct stm32_romem_cfg { + int size; + u8 lower; + bool ta; +}; + +struct stm32_romem_priv { + void __iomem *base; + struct nvmem_config cfg; + u8 lower; + struct tee_context *ctx; +}; + +static int stm32_romem_read(void *context, unsigned int offset, void *buf, + size_t bytes) +{ + struct stm32_romem_priv *priv = context; + u8 *buf8 = buf; + int i; + + for (i = offset; i < offset + bytes; i++) + *buf8++ = readb_relaxed(priv->base + i); + + return 0; +} + +static int stm32_bsec_smc(u8 op, u32 otp, u32 data, u32 *result) +{ +#if IS_ENABLED(CONFIG_HAVE_ARM_SMCCC) + struct arm_smccc_res res; + + arm_smccc_smc(STM32_SMC_BSEC, op, otp, data, 0, 0, 0, 0, &res); + if (res.a0) + return -EIO; + + if (result) + *result = (u32)res.a1; + + return 0; +#else + return -ENXIO; +#endif +} + +static int stm32_bsec_read(void *context, unsigned int offset, void *buf, + size_t bytes) +{ + struct stm32_romem_priv *priv = context; + struct device *dev = priv->cfg.dev; + u32 roffset, rbytes, val; + u8 *buf8 = buf, *val8 = (u8 *)&val; + int i, j = 0, ret, skip_bytes, size; + + /* Round unaligned access to 32-bits */ + roffset = rounddown(offset, 4); + skip_bytes = offset & 0x3; + rbytes = roundup(bytes + skip_bytes, 4); + + if (roffset + rbytes > priv->cfg.size) + return -EINVAL; + + for (i = roffset; (i < roffset + rbytes); i += 4) { + u32 otp = i >> 2; + + if (otp < priv->lower) { + /* read lower data from shadow registers */ + val = readl_relaxed( + priv->base + STM32MP15_BSEC_DATA0 + i); + } else { + ret = stm32_bsec_smc(STM32_SMC_READ_SHADOW, otp, 0, + &val); + if (ret) { + dev_err(dev, "Can't read data%d (%d)\n", otp, + ret); + return ret; + } + } + /* skip first bytes in case of unaligned read */ + if (skip_bytes) + size = min(bytes, (size_t)(4 - skip_bytes)); + else + size = min(bytes, (size_t)4); + memcpy(&buf8[j], &val8[skip_bytes], size); + bytes -= size; + j += size; + skip_bytes = 0; + } + + return 0; +} + +static int stm32_bsec_write(void *context, unsigned int offset, void *buf, + size_t bytes) +{ + struct stm32_romem_priv *priv = context; + struct device *dev = priv->cfg.dev; + u32 *buf32 = buf; + int ret, i; + + /* Allow only writing complete 32-bits aligned words */ + if ((bytes % 4) || (offset % 4)) + return -EINVAL; + + for (i = offset; i < offset + bytes; i += 4) { + ret = stm32_bsec_smc(STM32_SMC_PROG_OTP, i >> 2, *buf32++, + NULL); + if (ret) { + dev_err(dev, "Can't write data%d (%d)\n", i >> 2, ret); + return ret; + } + } + + if (offset + bytes >= priv->lower * 4) + dev_warn(dev, "Update of upper OTPs with ECC protection (word programming, only once)\n"); + + return 0; +} + +static int stm32_bsec_pta_read(void *context, unsigned int offset, void *buf, + size_t bytes) +{ + struct stm32_romem_priv *priv = context; + + return stm32_bsec_optee_ta_read(priv->ctx, offset, buf, bytes); +} + +static int stm32_bsec_pta_write(void *context, unsigned int offset, void *buf, + size_t bytes) +{ + struct stm32_romem_priv *priv = context; + + return stm32_bsec_optee_ta_write(priv->ctx, priv->lower, offset, buf, bytes); +} + +static bool stm32_bsec_smc_check(void) +{ + u32 val; + int ret; + + /* check that the OP-TEE support the BSEC SMC (legacy mode) */ + ret = stm32_bsec_smc(STM32_SMC_READ_SHADOW, 0, 0, &val); + + return !ret; +} + +static bool optee_presence_check(void) +{ + struct device_node *np; + bool tee_detected = false; + + /* check that the OP-TEE node is present and available. */ + np = of_find_compatible_node(NULL, NULL, "linaro,optee-tz"); + if (np && of_device_is_available(np)) + tee_detected = true; + of_node_put(np); + + return tee_detected; +} + +static int stm32_romem_probe(struct platform_device *pdev) +{ + const struct stm32_romem_cfg *cfg; + struct device *dev = &pdev->dev; + struct stm32_romem_priv *priv; + struct resource *res; + int rc; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->cfg.name = "stm32-romem"; + priv->cfg.word_size = 1; + priv->cfg.stride = 1; + priv->cfg.dev = dev; + priv->cfg.priv = priv; + priv->cfg.owner = THIS_MODULE; + priv->cfg.type = NVMEM_TYPE_OTP; + priv->cfg.add_legacy_fixed_of_cells = true; + + priv->lower = 0; + + cfg = device_get_match_data(dev); + if (!cfg) { + priv->cfg.read_only = true; + priv->cfg.size = resource_size(res); + priv->cfg.reg_read = stm32_romem_read; + } else { + priv->cfg.size = cfg->size; + priv->lower = cfg->lower; + if (cfg->ta || optee_presence_check()) { + rc = stm32_bsec_optee_ta_open(&priv->ctx); + if (rc) { + /* wait for OP-TEE client driver to be up and ready */ + if (rc == -EPROBE_DEFER) + return -EPROBE_DEFER; + /* BSEC PTA is required or SMC not supported */ + if (cfg->ta || !stm32_bsec_smc_check()) + return rc; + } + } + if (priv->ctx) { + rc = devm_add_action_or_reset(dev, stm32_bsec_optee_ta_close, priv->ctx); + if (rc) { + dev_err(dev, "devm_add_action_or_reset() failed (%d)\n", rc); + return rc; + } + priv->cfg.reg_read = stm32_bsec_pta_read; + priv->cfg.reg_write = stm32_bsec_pta_write; + } else { + priv->cfg.reg_read = stm32_bsec_read; + priv->cfg.reg_write = stm32_bsec_write; + } + } + + return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &priv->cfg)); +} + +/* + * STM32MP15/13 BSEC OTP regions: 4096 OTP bits (with 3072 effective bits) + * => 96 x 32-bits data words + * - Lower: 1K bits, 2:1 redundancy, incremental bit programming + * => 32 (x 32-bits) lower shadow registers = words 0 to 31 + * - Upper: 2K bits, ECC protection, word programming only + * => 64 (x 32-bits) = words 32 to 95 + */ +static const struct stm32_romem_cfg stm32mp15_bsec_cfg = { + .size = 384, + .lower = 32, + .ta = false, +}; + +static const struct stm32_romem_cfg stm32mp13_bsec_cfg = { + .size = 384, + .lower = 32, + .ta = true, +}; + +/* + * STM32MP25 BSEC OTP: 3 regions of 32-bits data words + * lower OTP (OTP0 to OTP127), bitwise (1-bit) programmable + * mid OTP (OTP128 to OTP255), bulk (32-bit) programmable + * upper OTP (OTP256 to OTP383), bulk (32-bit) programmable + * but no access to HWKEY and ECIES key: limited at OTP367 + */ +static const struct stm32_romem_cfg stm32mp25_bsec_cfg = { + .size = 368 * 4, + .lower = 127, + .ta = true, +}; + +static const struct of_device_id stm32_romem_of_match[] __maybe_unused = { + { .compatible = "st,stm32f4-otp", }, { + .compatible = "st,stm32mp15-bsec", + .data = (void *)&stm32mp15_bsec_cfg, + }, { + .compatible = "st,stm32mp13-bsec", + .data = (void *)&stm32mp13_bsec_cfg, + }, { + .compatible = "st,stm32mp25-bsec", + .data = (void *)&stm32mp25_bsec_cfg, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, stm32_romem_of_match); + +static struct platform_driver stm32_romem_driver = { + .probe = stm32_romem_probe, + .driver = { + .name = "stm32-romem", + .of_match_table = of_match_ptr(stm32_romem_of_match), + }, +}; +module_platform_driver(stm32_romem_driver); + +MODULE_AUTHOR("Fabrice Gasnier <fabrice.gasnier@st.com>"); +MODULE_DESCRIPTION("STMicroelectronics STM32 RO-MEM"); +MODULE_ALIAS("platform:nvmem-stm32-romem"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/sunplus-ocotp.c b/drivers/nvmem/sunplus-ocotp.c new file mode 100644 index 000000000..6884def3b --- /dev/null +++ b/drivers/nvmem/sunplus-ocotp.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * The OCOTP driver for Sunplus SP7021 + * + * Copyright (C) 2019 Sunplus Technology Inc., All rights reserved. + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> + +/* + * OTP memory + * Each bank contains 4 words (32 bits). + * Bank 0 starts at offset 0 from the base. + */ + +#define OTP_WORDS_PER_BANK 4 +#define OTP_WORD_SIZE sizeof(u32) +#define OTP_BIT_ADDR_OF_BANK (8 * OTP_WORD_SIZE * OTP_WORDS_PER_BANK) +#define QAC628_OTP_NUM_BANKS 8 +#define QAC628_OTP_SIZE (QAC628_OTP_NUM_BANKS * OTP_WORDS_PER_BANK * OTP_WORD_SIZE) +#define OTP_READ_TIMEOUT_US 200000 + +/* HB_GPIO */ +#define ADDRESS_8_DATA 0x20 + +/* OTP_RX */ +#define OTP_CONTROL_2 0x48 +#define OTP_RD_PERIOD GENMASK(15, 8) +#define OTP_RD_PERIOD_MASK ~GENMASK(15, 8) +#define CPU_CLOCK FIELD_PREP(OTP_RD_PERIOD, 30) +#define SEL_BAK_KEY2 BIT(5) +#define SEL_BAK_KEY2_MASK ~BIT(5) +#define SW_TRIM_EN BIT(4) +#define SW_TRIM_EN_MASK ~BIT(4) +#define SEL_BAK_KEY BIT(3) +#define SEL_BAK_KEY_MASK ~BIT(3) +#define OTP_READ BIT(2) +#define OTP_LOAD_SECURE_DATA BIT(1) +#define OTP_LOAD_SECURE_DATA_MASK ~BIT(1) +#define OTP_DO_CRC BIT(0) +#define OTP_DO_CRC_MASK ~BIT(0) +#define OTP_STATUS 0x4c +#define OTP_READ_DONE BIT(4) +#define OTP_READ_DONE_MASK ~BIT(4) +#define OTP_LOAD_SECURE_DONE_MASK ~BIT(2) +#define OTP_READ_ADDRESS 0x50 + +enum base_type { + HB_GPIO, + OTPRX, + BASEMAX, +}; + +struct sp_ocotp_priv { + struct device *dev; + void __iomem *base[BASEMAX]; + struct clk *clk; +}; + +struct sp_ocotp_data { + int size; +}; + +static const struct sp_ocotp_data sp_otp_v0 = { + .size = QAC628_OTP_SIZE, +}; + +static int sp_otp_read_real(struct sp_ocotp_priv *otp, int addr, char *value) +{ + unsigned int addr_data; + unsigned int byte_shift; + unsigned int status; + int ret; + + addr_data = addr % (OTP_WORD_SIZE * OTP_WORDS_PER_BANK); + addr_data = addr_data / OTP_WORD_SIZE; + + byte_shift = addr % (OTP_WORD_SIZE * OTP_WORDS_PER_BANK); + byte_shift = byte_shift % OTP_WORD_SIZE; + + addr = addr / (OTP_WORD_SIZE * OTP_WORDS_PER_BANK); + addr = addr * OTP_BIT_ADDR_OF_BANK; + + writel(readl(otp->base[OTPRX] + OTP_STATUS) & OTP_READ_DONE_MASK & + OTP_LOAD_SECURE_DONE_MASK, otp->base[OTPRX] + OTP_STATUS); + writel(addr, otp->base[OTPRX] + OTP_READ_ADDRESS); + writel(readl(otp->base[OTPRX] + OTP_CONTROL_2) | OTP_READ, + otp->base[OTPRX] + OTP_CONTROL_2); + writel(readl(otp->base[OTPRX] + OTP_CONTROL_2) & SEL_BAK_KEY2_MASK & SW_TRIM_EN_MASK + & SEL_BAK_KEY_MASK & OTP_LOAD_SECURE_DATA_MASK & OTP_DO_CRC_MASK, + otp->base[OTPRX] + OTP_CONTROL_2); + writel((readl(otp->base[OTPRX] + OTP_CONTROL_2) & OTP_RD_PERIOD_MASK) | CPU_CLOCK, + otp->base[OTPRX] + OTP_CONTROL_2); + + ret = readl_poll_timeout(otp->base[OTPRX] + OTP_STATUS, status, + status & OTP_READ_DONE, 10, OTP_READ_TIMEOUT_US); + + if (ret < 0) + return ret; + + *value = (readl(otp->base[HB_GPIO] + ADDRESS_8_DATA + addr_data * OTP_WORD_SIZE) + >> (8 * byte_shift)) & 0xff; + + return ret; +} + +static int sp_ocotp_read(void *priv, unsigned int offset, void *value, size_t bytes) +{ + struct sp_ocotp_priv *otp = priv; + unsigned int addr; + char *buf = value; + char val[4]; + int ret; + + ret = clk_enable(otp->clk); + if (ret) + return ret; + + *buf = 0; + for (addr = offset; addr < (offset + bytes); addr++) { + ret = sp_otp_read_real(otp, addr, val); + if (ret < 0) { + dev_err(otp->dev, "OTP read fail:%d at %d", ret, addr); + goto disable_clk; + } + + *buf++ = *val; + } + +disable_clk: + clk_disable(otp->clk); + + return ret; +} + +static struct nvmem_config sp_ocotp_nvmem_config = { + .name = "sp-ocotp", + .add_legacy_fixed_of_cells = true, + .read_only = true, + .word_size = 1, + .size = QAC628_OTP_SIZE, + .stride = 1, + .reg_read = sp_ocotp_read, + .owner = THIS_MODULE, +}; + +static int sp_ocotp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nvmem_device *nvmem; + struct sp_ocotp_priv *otp; + int ret; + + otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL); + if (!otp) + return -ENOMEM; + + otp->dev = dev; + + otp->base[HB_GPIO] = devm_platform_ioremap_resource_byname(pdev, "hb_gpio"); + if (IS_ERR(otp->base[HB_GPIO])) + return PTR_ERR(otp->base[HB_GPIO]); + + otp->base[OTPRX] = devm_platform_ioremap_resource_byname(pdev, "otprx"); + if (IS_ERR(otp->base[OTPRX])) + return PTR_ERR(otp->base[OTPRX]); + + otp->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(otp->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(otp->clk), + "devm_clk_get fail\n"); + + ret = clk_prepare(otp->clk); + if (ret < 0) { + dev_err(dev, "failed to prepare clk: %d\n", ret); + return ret; + } + + sp_ocotp_nvmem_config.priv = otp; + sp_ocotp_nvmem_config.dev = dev; + + nvmem = devm_nvmem_register(dev, &sp_ocotp_nvmem_config); + if (IS_ERR(nvmem)) { + ret = dev_err_probe(&pdev->dev, PTR_ERR(nvmem), + "register nvmem device fail\n"); + goto err; + } + + platform_set_drvdata(pdev, nvmem); + + dev_dbg(dev, "banks:%d x wpb:%d x wsize:%d = %d", + (int)QAC628_OTP_NUM_BANKS, (int)OTP_WORDS_PER_BANK, + (int)OTP_WORD_SIZE, (int)QAC628_OTP_SIZE); + + return 0; +err: + clk_unprepare(otp->clk); + return ret; +} + +static const struct of_device_id sp_ocotp_dt_ids[] = { + { .compatible = "sunplus,sp7021-ocotp", .data = &sp_otp_v0 }, + { } +}; +MODULE_DEVICE_TABLE(of, sp_ocotp_dt_ids); + +static struct platform_driver sp_otp_driver = { + .probe = sp_ocotp_probe, + .driver = { + .name = "sunplus,sp7021-ocotp", + .of_match_table = sp_ocotp_dt_ids, + } +}; +module_platform_driver(sp_otp_driver); + +MODULE_AUTHOR("Vincent Shih <vincent.sunplus@gmail.com>"); +MODULE_DESCRIPTION("Sunplus On-Chip OTP driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/nvmem/sunxi_sid.c b/drivers/nvmem/sunxi_sid.c new file mode 100644 index 000000000..ba14a7620 --- /dev/null +++ b/drivers/nvmem/sunxi_sid.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Allwinner sunXi SoCs Security ID support. + * + * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl> + * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com> + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/random.h> + +/* Registers and special values for doing register-based SID readout on H3 */ +#define SUN8I_SID_PRCTL 0x40 +#define SUN8I_SID_RDKEY 0x60 + +#define SUN8I_SID_OFFSET_MASK 0x1FF +#define SUN8I_SID_OFFSET_SHIFT 16 +#define SUN8I_SID_OP_LOCK (0xAC << 8) +#define SUN8I_SID_READ BIT(1) + +struct sunxi_sid_cfg { + u32 value_offset; + u32 size; + bool need_register_readout; +}; + +struct sunxi_sid { + void __iomem *base; + u32 value_offset; +}; + +static int sunxi_sid_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct sunxi_sid *sid = context; + u32 word; + + /* .stride = 4 so offset is guaranteed to be aligned */ + __ioread32_copy(val, sid->base + sid->value_offset + offset, bytes / 4); + + val += round_down(bytes, 4); + offset += round_down(bytes, 4); + bytes = bytes % 4; + + if (!bytes) + return 0; + + /* Handle any trailing bytes */ + word = readl_relaxed(sid->base + sid->value_offset + offset); + memcpy(val, &word, bytes); + + return 0; +} + +static int sun8i_sid_register_readout(const struct sunxi_sid *sid, + const unsigned int offset, + u32 *out) +{ + u32 reg_val; + int ret; + + /* Set word, lock access, and set read command */ + reg_val = (offset & SUN8I_SID_OFFSET_MASK) + << SUN8I_SID_OFFSET_SHIFT; + reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; + writel(reg_val, sid->base + SUN8I_SID_PRCTL); + + ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, + !(reg_val & SUN8I_SID_READ), 100, 250000); + if (ret) + return ret; + + if (out) + *out = readl(sid->base + SUN8I_SID_RDKEY); + + writel(0, sid->base + SUN8I_SID_PRCTL); + + return 0; +} + +/* + * On Allwinner H3, the value on the 0x200 offset of the SID controller seems + * to be not reliable at all. + * Read by the registers instead. + */ +static int sun8i_sid_read_by_reg(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct sunxi_sid *sid = context; + u32 word; + int ret; + + /* .stride = 4 so offset is guaranteed to be aligned */ + while (bytes >= 4) { + ret = sun8i_sid_register_readout(sid, offset, val); + if (ret) + return ret; + + val += 4; + offset += 4; + bytes -= 4; + } + + if (!bytes) + return 0; + + /* Handle any trailing bytes */ + ret = sun8i_sid_register_readout(sid, offset, &word); + if (ret) + return ret; + + memcpy(val, &word, bytes); + + return 0; +} + +static int sunxi_sid_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nvmem_config *nvmem_cfg; + struct nvmem_device *nvmem; + struct sunxi_sid *sid; + int size; + char *randomness; + const struct sunxi_sid_cfg *cfg; + + sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); + if (!sid) + return -ENOMEM; + + cfg = of_device_get_match_data(dev); + if (!cfg) + return -EINVAL; + sid->value_offset = cfg->value_offset; + + sid->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(sid->base)) + return PTR_ERR(sid->base); + + size = cfg->size; + + nvmem_cfg = devm_kzalloc(dev, sizeof(*nvmem_cfg), GFP_KERNEL); + if (!nvmem_cfg) + return -ENOMEM; + + nvmem_cfg->dev = dev; + nvmem_cfg->name = "sunxi-sid"; + nvmem_cfg->type = NVMEM_TYPE_OTP; + nvmem_cfg->add_legacy_fixed_of_cells = true; + nvmem_cfg->read_only = true; + nvmem_cfg->size = cfg->size; + nvmem_cfg->word_size = 1; + nvmem_cfg->stride = 4; + nvmem_cfg->priv = sid; + if (cfg->need_register_readout) + nvmem_cfg->reg_read = sun8i_sid_read_by_reg; + else + nvmem_cfg->reg_read = sunxi_sid_read; + + nvmem = devm_nvmem_register(dev, nvmem_cfg); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); + + randomness = kzalloc(size, GFP_KERNEL); + if (!randomness) + return -ENOMEM; + + nvmem_cfg->reg_read(sid, 0, randomness, size); + add_device_randomness(randomness, size); + kfree(randomness); + + platform_set_drvdata(pdev, nvmem); + + return 0; +} + +static const struct sunxi_sid_cfg sun4i_a10_cfg = { + .size = 0x10, +}; + +static const struct sunxi_sid_cfg sun7i_a20_cfg = { + .size = 0x200, +}; + +static const struct sunxi_sid_cfg sun8i_h3_cfg = { + .value_offset = 0x200, + .size = 0x100, + .need_register_readout = true, +}; + +static const struct sunxi_sid_cfg sun50i_a64_cfg = { + .value_offset = 0x200, + .size = 0x100, +}; + +static const struct sunxi_sid_cfg sun50i_h6_cfg = { + .value_offset = 0x200, + .size = 0x200, +}; + +static const struct of_device_id sunxi_sid_of_match[] = { + { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, + { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, + { .compatible = "allwinner,sun8i-a83t-sid", .data = &sun50i_a64_cfg }, + { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, + { .compatible = "allwinner,sun20i-d1-sid", .data = &sun50i_a64_cfg }, + { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg }, + { .compatible = "allwinner,sun50i-h5-sid", .data = &sun50i_a64_cfg }, + { .compatible = "allwinner,sun50i-h6-sid", .data = &sun50i_h6_cfg }, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); + +static struct platform_driver sunxi_sid_driver = { + .probe = sunxi_sid_probe, + .driver = { + .name = "eeprom-sunxi-sid", + .of_match_table = sunxi_sid_of_match, + }, +}; +module_platform_driver(sunxi_sid_driver); + +MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); +MODULE_DESCRIPTION("Allwinner sunxi security id driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/u-boot-env.c b/drivers/nvmem/u-boot-env.c new file mode 100644 index 000000000..467b28891 --- /dev/null +++ b/drivers/nvmem/u-boot-env.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 Rafał Miłecki <rafal@milecki.pl> + */ + +#include <linux/module.h> +#include <linux/mtd/mtd.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include "layouts/u-boot-env.h" + +struct u_boot_env { + struct device *dev; + struct nvmem_device *nvmem; + enum u_boot_env_format format; + + struct mtd_info *mtd; +}; + +static int u_boot_env_read(void *context, unsigned int offset, void *val, + size_t bytes) +{ + struct u_boot_env *priv = context; + struct device *dev = priv->dev; + size_t bytes_read; + int err; + + err = mtd_read(priv->mtd, offset, bytes, &bytes_read, val); + if (err && !mtd_is_bitflip(err)) { + dev_err(dev, "Failed to read from mtd: %d\n", err); + return err; + } + + if (bytes_read != bytes) { + dev_err(dev, "Failed to read %zu bytes\n", bytes); + return -EIO; + } + + return 0; +} + +static int u_boot_env_probe(struct platform_device *pdev) +{ + struct nvmem_config config = { + .name = "u-boot-env", + .reg_read = u_boot_env_read, + }; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct u_boot_env *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->dev = dev; + + priv->format = (uintptr_t)of_device_get_match_data(dev); + + priv->mtd = of_get_mtd_device_by_node(np); + if (IS_ERR(priv->mtd)) { + dev_err_probe(dev, PTR_ERR(priv->mtd), "Failed to get %pOF MTD\n", np); + return PTR_ERR(priv->mtd); + } + + config.dev = dev; + config.priv = priv; + config.size = priv->mtd->size; + + priv->nvmem = devm_nvmem_register(dev, &config); + if (IS_ERR(priv->nvmem)) + return PTR_ERR(priv->nvmem); + + return u_boot_env_parse(dev, priv->nvmem, priv->format); +} + +static const struct of_device_id u_boot_env_of_match_table[] = { + { .compatible = "u-boot,env", .data = (void *)U_BOOT_FORMAT_SINGLE, }, + { .compatible = "u-boot,env-redundant-bool", .data = (void *)U_BOOT_FORMAT_REDUNDANT, }, + { .compatible = "u-boot,env-redundant-count", .data = (void *)U_BOOT_FORMAT_REDUNDANT, }, + { .compatible = "brcm,env", .data = (void *)U_BOOT_FORMAT_BROADCOM, }, + {}, +}; + +static struct platform_driver u_boot_env_driver = { + .probe = u_boot_env_probe, + .driver = { + .name = "u_boot_env", + .of_match_table = u_boot_env_of_match_table, + }, +}; +module_platform_driver(u_boot_env_driver); + +MODULE_AUTHOR("Rafał Miłecki"); +MODULE_DESCRIPTION("U-Boot environment variables support module"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, u_boot_env_of_match_table); diff --git a/drivers/nvmem/uniphier-efuse.c b/drivers/nvmem/uniphier-efuse.c new file mode 100644 index 000000000..85f9372fb --- /dev/null +++ b/drivers/nvmem/uniphier-efuse.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * UniPhier eFuse driver + * + * Copyright (C) 2017 Socionext Inc. + */ + +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> + +struct uniphier_efuse_priv { + void __iomem *base; +}; + +static int uniphier_reg_read(void *context, + unsigned int reg, void *_val, size_t bytes) +{ + struct uniphier_efuse_priv *priv = context; + u8 *val = _val; + int offs; + + for (offs = 0; offs < bytes; offs += sizeof(u8)) + *val++ = readb(priv->base + reg + offs); + + return 0; +} + +static int uniphier_efuse_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct nvmem_device *nvmem; + struct nvmem_config econfig = {}; + struct uniphier_efuse_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + econfig.stride = 1; + econfig.word_size = 1; + econfig.read_only = true; + econfig.reg_read = uniphier_reg_read; + econfig.size = resource_size(res); + econfig.priv = priv; + econfig.dev = dev; + econfig.add_legacy_fixed_of_cells = true; + nvmem = devm_nvmem_register(dev, &econfig); + + return PTR_ERR_OR_ZERO(nvmem); +} + +static const struct of_device_id uniphier_efuse_of_match[] = { + { .compatible = "socionext,uniphier-efuse",}, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, uniphier_efuse_of_match); + +static struct platform_driver uniphier_efuse_driver = { + .probe = uniphier_efuse_probe, + .driver = { + .name = "uniphier-efuse", + .of_match_table = uniphier_efuse_of_match, + }, +}; +module_platform_driver(uniphier_efuse_driver); + +MODULE_AUTHOR("Keiji Hayashibara <hayashibara.keiji@socionext.com>"); +MODULE_DESCRIPTION("UniPhier eFuse driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/vf610-ocotp.c b/drivers/nvmem/vf610-ocotp.c new file mode 100644 index 000000000..ee9c61ae7 --- /dev/null +++ b/drivers/nvmem/vf610-ocotp.c @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 Toradex AG. + * + * Author: Sanchayan Maity <sanchayan.maity@toradex.com> + * + * Based on the barebox ocotp driver, + * Copyright (c) 2010 Baruch Siach <baruch@tkos.co.il> + * Orex Computed Radiography + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* OCOTP Register Offsets */ +#define OCOTP_CTRL_REG 0x00 +#define OCOTP_CTRL_SET 0x04 +#define OCOTP_CTRL_CLR 0x08 +#define OCOTP_TIMING 0x10 +#define OCOTP_DATA 0x20 +#define OCOTP_READ_CTRL_REG 0x30 +#define OCOTP_READ_FUSE_DATA 0x40 + +/* OCOTP Register bits and masks */ +#define OCOTP_CTRL_WR_UNLOCK 16 +#define OCOTP_CTRL_WR_UNLOCK_KEY 0x3E77 +#define OCOTP_CTRL_WR_UNLOCK_MASK GENMASK(31, 16) +#define OCOTP_CTRL_ADDR 0 +#define OCOTP_CTRL_ADDR_MASK GENMASK(6, 0) +#define OCOTP_CTRL_RELOAD_SHADOWS BIT(10) +#define OCOTP_CTRL_ERR BIT(9) +#define OCOTP_CTRL_BUSY BIT(8) + +#define OCOTP_TIMING_STROBE_READ 16 +#define OCOTP_TIMING_STROBE_READ_MASK GENMASK(21, 16) +#define OCOTP_TIMING_RELAX 12 +#define OCOTP_TIMING_RELAX_MASK GENMASK(15, 12) +#define OCOTP_TIMING_STROBE_PROG 0 +#define OCOTP_TIMING_STROBE_PROG_MASK GENMASK(11, 0) + +#define OCOTP_READ_CTRL_READ_FUSE 0x1 + +#define VF610_OCOTP_TIMEOUT 100000 + +#define BF(value, field) (((value) << field) & field##_MASK) + +#define DEF_RELAX 20 + +static const int base_to_fuse_addr_mappings[][2] = { + {0x400, 0x00}, + {0x410, 0x01}, + {0x420, 0x02}, + {0x450, 0x05}, + {0x4F0, 0x0F}, + {0x600, 0x20}, + {0x610, 0x21}, + {0x620, 0x22}, + {0x630, 0x23}, + {0x640, 0x24}, + {0x650, 0x25}, + {0x660, 0x26}, + {0x670, 0x27}, + {0x6F0, 0x2F}, + {0x880, 0x38}, + {0x890, 0x39}, + {0x8A0, 0x3A}, + {0x8B0, 0x3B}, + {0x8C0, 0x3C}, + {0x8D0, 0x3D}, + {0x8E0, 0x3E}, + {0x8F0, 0x3F}, + {0xC80, 0x78}, + {0xC90, 0x79}, + {0xCA0, 0x7A}, + {0xCB0, 0x7B}, + {0xCC0, 0x7C}, + {0xCD0, 0x7D}, + {0xCE0, 0x7E}, + {0xCF0, 0x7F}, +}; + +struct vf610_ocotp { + void __iomem *base; + struct clk *clk; + struct device *dev; + struct nvmem_device *nvmem; + int timing; +}; + +static int vf610_ocotp_wait_busy(void __iomem *base) +{ + int timeout = VF610_OCOTP_TIMEOUT; + + while ((readl(base) & OCOTP_CTRL_BUSY) && --timeout) + udelay(10); + + if (!timeout) { + writel(OCOTP_CTRL_ERR, base + OCOTP_CTRL_CLR); + return -ETIMEDOUT; + } + + udelay(10); + + return 0; +} + +static int vf610_ocotp_calculate_timing(struct vf610_ocotp *ocotp_dev) +{ + u32 clk_rate; + u32 relax, strobe_read, strobe_prog; + u32 timing; + + clk_rate = clk_get_rate(ocotp_dev->clk); + + /* Refer section OTP read/write timing parameters in TRM */ + relax = clk_rate / (1000000000 / DEF_RELAX) - 1; + strobe_prog = clk_rate / (1000000000 / 10000) + 2 * (DEF_RELAX + 1) - 1; + strobe_read = clk_rate / (1000000000 / 40) + 2 * (DEF_RELAX + 1) - 1; + + timing = BF(relax, OCOTP_TIMING_RELAX); + timing |= BF(strobe_read, OCOTP_TIMING_STROBE_READ); + timing |= BF(strobe_prog, OCOTP_TIMING_STROBE_PROG); + + return timing; +} + +static int vf610_get_fuse_address(int base_addr_offset) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(base_to_fuse_addr_mappings); i++) { + if (base_to_fuse_addr_mappings[i][0] == base_addr_offset) + return base_to_fuse_addr_mappings[i][1]; + } + + return -EINVAL; +} + +static int vf610_ocotp_read(void *context, unsigned int offset, + void *val, size_t bytes) +{ + struct vf610_ocotp *ocotp = context; + void __iomem *base = ocotp->base; + u32 reg, *buf = val; + int fuse_addr; + int ret; + + while (bytes > 0) { + fuse_addr = vf610_get_fuse_address(offset); + if (fuse_addr > 0) { + writel(ocotp->timing, base + OCOTP_TIMING); + ret = vf610_ocotp_wait_busy(base + OCOTP_CTRL_REG); + if (ret) + return ret; + + reg = readl(base + OCOTP_CTRL_REG); + reg &= ~OCOTP_CTRL_ADDR_MASK; + reg &= ~OCOTP_CTRL_WR_UNLOCK_MASK; + reg |= BF(fuse_addr, OCOTP_CTRL_ADDR); + writel(reg, base + OCOTP_CTRL_REG); + + writel(OCOTP_READ_CTRL_READ_FUSE, + base + OCOTP_READ_CTRL_REG); + ret = vf610_ocotp_wait_busy(base + OCOTP_CTRL_REG); + if (ret) + return ret; + + if (readl(base) & OCOTP_CTRL_ERR) { + dev_dbg(ocotp->dev, "Error reading from fuse address %x\n", + fuse_addr); + writel(OCOTP_CTRL_ERR, base + OCOTP_CTRL_CLR); + } + + /* + * In case of error, we do not abort and expect to read + * 0xBADABADA as mentioned by the TRM. We just read this + * value and return. + */ + *buf = readl(base + OCOTP_READ_FUSE_DATA); + } else { + *buf = 0; + } + + buf++; + bytes -= 4; + offset += 4; + } + + return 0; +} + +static struct nvmem_config ocotp_config = { + .name = "ocotp", + .stride = 4, + .word_size = 4, + .reg_read = vf610_ocotp_read, +}; + +static const struct of_device_id ocotp_of_match[] = { + { .compatible = "fsl,vf610-ocotp", }, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, ocotp_of_match); + +static int vf610_ocotp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct vf610_ocotp *ocotp_dev; + + ocotp_dev = devm_kzalloc(dev, sizeof(struct vf610_ocotp), GFP_KERNEL); + if (!ocotp_dev) + return -ENOMEM; + + ocotp_dev->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(ocotp_dev->base)) + return PTR_ERR(ocotp_dev->base); + + ocotp_dev->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ocotp_dev->clk)) { + dev_err(dev, "failed getting clock, err = %ld\n", + PTR_ERR(ocotp_dev->clk)); + return PTR_ERR(ocotp_dev->clk); + } + ocotp_dev->dev = dev; + ocotp_dev->timing = vf610_ocotp_calculate_timing(ocotp_dev); + + ocotp_config.size = resource_size(res); + ocotp_config.priv = ocotp_dev; + ocotp_config.dev = dev; + + ocotp_dev->nvmem = devm_nvmem_register(dev, &ocotp_config); + + return PTR_ERR_OR_ZERO(ocotp_dev->nvmem); +} + +static struct platform_driver vf610_ocotp_driver = { + .probe = vf610_ocotp_probe, + .driver = { + .name = "vf610-ocotp", + .of_match_table = ocotp_of_match, + }, +}; +module_platform_driver(vf610_ocotp_driver); +MODULE_AUTHOR("Sanchayan Maity <sanchayan.maity@toradex.com>"); +MODULE_DESCRIPTION("Vybrid OCOTP driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/zynqmp_nvmem.c b/drivers/nvmem/zynqmp_nvmem.c new file mode 100644 index 000000000..d297ff150 --- /dev/null +++ b/drivers/nvmem/zynqmp_nvmem.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019 Xilinx, Inc. + * Copyright (C) 2022 - 2023, Advanced Micro Devices, Inc. + */ + +#include <linux/dma-mapping.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/firmware/xlnx-zynqmp.h> + +#define SILICON_REVISION_MASK 0xF +#define P_USER_0_64_UPPER_MASK GENMASK(31, 16) +#define P_USER_127_LOWER_4_BIT_MASK GENMASK(3, 0) +#define WORD_INBYTES 4 +#define SOC_VER_SIZE 0x4 +#define EFUSE_MEMORY_SIZE 0x177 +#define UNUSED_SPACE 0x8 +#define ZYNQMP_NVMEM_SIZE (SOC_VER_SIZE + UNUSED_SPACE + \ + EFUSE_MEMORY_SIZE) +#define SOC_VERSION_OFFSET 0x0 +#define EFUSE_START_OFFSET 0xC +#define EFUSE_END_OFFSET 0xFC +#define EFUSE_PUF_START_OFFSET 0x100 +#define EFUSE_PUF_MID_OFFSET 0x140 +#define EFUSE_PUF_END_OFFSET 0x17F +#define EFUSE_NOT_ENABLED 29 + +/* + * efuse access type + */ +enum efuse_access { + EFUSE_READ = 0, + EFUSE_WRITE +}; + +/** + * struct xilinx_efuse - the basic structure + * @src: address of the buffer to store the data to be write/read + * @size: read/write word count + * @offset: read/write offset + * @flag: 0 - represents efuse read and 1- represents efuse write + * @pufuserfuse:0 - represents non-puf efuses, offset is used for read/write + * 1 - represents puf user fuse row number. + * + * this structure stores all the required details to + * read/write efuse memory. + */ +struct xilinx_efuse { + u64 src; + u32 size; + u32 offset; + enum efuse_access flag; + u32 pufuserfuse; +}; + +static int zynqmp_efuse_access(void *context, unsigned int offset, + void *val, size_t bytes, enum efuse_access flag, + unsigned int pufflag) +{ + struct device *dev = context; + struct xilinx_efuse *efuse; + dma_addr_t dma_addr; + dma_addr_t dma_buf; + size_t words = bytes / WORD_INBYTES; + int ret; + unsigned int value; + char *data; + + if (bytes % WORD_INBYTES != 0) { + dev_err(dev, "Bytes requested should be word aligned\n"); + return -EOPNOTSUPP; + } + + if (pufflag == 0 && offset % WORD_INBYTES) { + dev_err(dev, "Offset requested should be word aligned\n"); + return -EOPNOTSUPP; + } + + if (pufflag == 1 && flag == EFUSE_WRITE) { + memcpy(&value, val, sizeof(value)); + if ((offset == EFUSE_PUF_START_OFFSET || + offset == EFUSE_PUF_MID_OFFSET) && + value & P_USER_0_64_UPPER_MASK) { + dev_err(dev, "Only lower 4 bytes are allowed to be programmed in P_USER_0 & P_USER_64\n"); + return -EOPNOTSUPP; + } + + if (offset == EFUSE_PUF_END_OFFSET && + (value & P_USER_127_LOWER_4_BIT_MASK)) { + dev_err(dev, "Only MSB 28 bits are allowed to be programmed for P_USER_127\n"); + return -EOPNOTSUPP; + } + } + + efuse = dma_alloc_coherent(dev, sizeof(struct xilinx_efuse), + &dma_addr, GFP_KERNEL); + if (!efuse) + return -ENOMEM; + + data = dma_alloc_coherent(dev, bytes, + &dma_buf, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto efuse_data_fail; + } + + if (flag == EFUSE_WRITE) { + memcpy(data, val, bytes); + efuse->flag = EFUSE_WRITE; + } else { + efuse->flag = EFUSE_READ; + } + + efuse->src = dma_buf; + efuse->size = words; + efuse->offset = offset; + efuse->pufuserfuse = pufflag; + + zynqmp_pm_efuse_access(dma_addr, (u32 *)&ret); + if (ret != 0) { + if (ret == EFUSE_NOT_ENABLED) { + dev_err(dev, "efuse access is not enabled\n"); + ret = -EOPNOTSUPP; + } else { + dev_err(dev, "Error in efuse read %x\n", ret); + ret = -EPERM; + } + goto efuse_access_err; + } + + if (flag == EFUSE_READ) + memcpy(val, data, bytes); +efuse_access_err: + dma_free_coherent(dev, bytes, + data, dma_buf); +efuse_data_fail: + dma_free_coherent(dev, sizeof(struct xilinx_efuse), + efuse, dma_addr); + + return ret; +} + +static int zynqmp_nvmem_read(void *context, unsigned int offset, void *val, size_t bytes) +{ + struct device *dev = context; + int ret; + int pufflag = 0; + int idcode; + int version; + + if (offset >= EFUSE_PUF_START_OFFSET && offset <= EFUSE_PUF_END_OFFSET) + pufflag = 1; + + switch (offset) { + /* Soc version offset is zero */ + case SOC_VERSION_OFFSET: + if (bytes != SOC_VER_SIZE) + return -EOPNOTSUPP; + + ret = zynqmp_pm_get_chipid((u32 *)&idcode, (u32 *)&version); + if (ret < 0) + return ret; + + dev_dbg(dev, "Read chipid val %x %x\n", idcode, version); + *(int *)val = version & SILICON_REVISION_MASK; + break; + /* Efuse offset starts from 0xc */ + case EFUSE_START_OFFSET ... EFUSE_END_OFFSET: + case EFUSE_PUF_START_OFFSET ... EFUSE_PUF_END_OFFSET: + ret = zynqmp_efuse_access(context, offset, val, + bytes, EFUSE_READ, pufflag); + break; + default: + *(u32 *)val = 0xDEADBEEF; + ret = 0; + break; + } + + return ret; +} + +static int zynqmp_nvmem_write(void *context, + unsigned int offset, void *val, size_t bytes) +{ + int pufflag = 0; + + if (offset < EFUSE_START_OFFSET || offset > EFUSE_PUF_END_OFFSET) + return -EOPNOTSUPP; + + if (offset >= EFUSE_PUF_START_OFFSET && offset <= EFUSE_PUF_END_OFFSET) + pufflag = 1; + + return zynqmp_efuse_access(context, offset, + val, bytes, EFUSE_WRITE, pufflag); +} + +static const struct of_device_id zynqmp_nvmem_match[] = { + { .compatible = "xlnx,zynqmp-nvmem-fw", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, zynqmp_nvmem_match); + +static int zynqmp_nvmem_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nvmem_config econfig = {}; + + econfig.name = "zynqmp-nvmem"; + econfig.owner = THIS_MODULE; + econfig.word_size = 1; + econfig.size = ZYNQMP_NVMEM_SIZE; + econfig.dev = dev; + econfig.priv = dev; + econfig.add_legacy_fixed_of_cells = true; + econfig.reg_read = zynqmp_nvmem_read; + econfig.reg_write = zynqmp_nvmem_write; + + return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &econfig)); +} + +static struct platform_driver zynqmp_nvmem_driver = { + .probe = zynqmp_nvmem_probe, + .driver = { + .name = "zynqmp-nvmem", + .of_match_table = zynqmp_nvmem_match, + }, +}; + +module_platform_driver(zynqmp_nvmem_driver); + +MODULE_AUTHOR("Michal Simek <michal.simek@amd.com>, Nava kishore Manne <nava.kishore.manne@amd.com>"); +MODULE_DESCRIPTION("ZynqMP NVMEM driver"); +MODULE_LICENSE("GPL"); |
