18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Intel PCH/PCU SPI flash driver. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2016, Intel Corporation 68c2ecf20Sopenharmony_ci * Author: Mika Westerberg <mika.westerberg@linux.intel.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/err.h> 108c2ecf20Sopenharmony_ci#include <linux/io.h> 118c2ecf20Sopenharmony_ci#include <linux/iopoll.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/sched.h> 148c2ecf20Sopenharmony_ci#include <linux/sizes.h> 158c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h> 168c2ecf20Sopenharmony_ci#include <linux/mtd/partitions.h> 178c2ecf20Sopenharmony_ci#include <linux/mtd/spi-nor.h> 188c2ecf20Sopenharmony_ci#include <linux/platform_data/intel-spi.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include "intel-spi.h" 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci/* Offsets are from @ispi->base */ 238c2ecf20Sopenharmony_ci#define BFPREG 0x00 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define HSFSTS_CTL 0x04 268c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FSMIE BIT(31) 278c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FDBC_SHIFT 24 288c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FDBC_MASK (0x3f << HSFSTS_CTL_FDBC_SHIFT) 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FCYCLE_SHIFT 17 318c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FCYCLE_MASK (0x0f << HSFSTS_CTL_FCYCLE_SHIFT) 328c2ecf20Sopenharmony_ci/* HW sequencer opcodes */ 338c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FCYCLE_READ (0x00 << HSFSTS_CTL_FCYCLE_SHIFT) 348c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FCYCLE_WRITE (0x02 << HSFSTS_CTL_FCYCLE_SHIFT) 358c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FCYCLE_ERASE (0x03 << HSFSTS_CTL_FCYCLE_SHIFT) 368c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FCYCLE_ERASE_64K (0x04 << HSFSTS_CTL_FCYCLE_SHIFT) 378c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FCYCLE_RDID (0x06 << HSFSTS_CTL_FCYCLE_SHIFT) 388c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FCYCLE_WRSR (0x07 << HSFSTS_CTL_FCYCLE_SHIFT) 398c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FCYCLE_RDSR (0x08 << HSFSTS_CTL_FCYCLE_SHIFT) 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FGO BIT(16) 428c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FLOCKDN BIT(15) 438c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FDV BIT(14) 448c2ecf20Sopenharmony_ci#define HSFSTS_CTL_SCIP BIT(5) 458c2ecf20Sopenharmony_ci#define HSFSTS_CTL_AEL BIT(2) 468c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FCERR BIT(1) 478c2ecf20Sopenharmony_ci#define HSFSTS_CTL_FDONE BIT(0) 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci#define FADDR 0x08 508c2ecf20Sopenharmony_ci#define DLOCK 0x0c 518c2ecf20Sopenharmony_ci#define FDATA(n) (0x10 + ((n) * 4)) 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci#define FRACC 0x50 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci#define FREG(n) (0x54 + ((n) * 4)) 568c2ecf20Sopenharmony_ci#define FREG_BASE_MASK GENMASK(14, 0) 578c2ecf20Sopenharmony_ci#define FREG_LIMIT_SHIFT 16 588c2ecf20Sopenharmony_ci#define FREG_LIMIT_MASK GENMASK(30, 16) 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci/* Offset is from @ispi->pregs */ 618c2ecf20Sopenharmony_ci#define PR(n) ((n) * 4) 628c2ecf20Sopenharmony_ci#define PR_WPE BIT(31) 638c2ecf20Sopenharmony_ci#define PR_LIMIT_SHIFT 16 648c2ecf20Sopenharmony_ci#define PR_LIMIT_MASK GENMASK(30, 16) 658c2ecf20Sopenharmony_ci#define PR_RPE BIT(15) 668c2ecf20Sopenharmony_ci#define PR_BASE_MASK GENMASK(14, 0) 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci/* Offsets are from @ispi->sregs */ 698c2ecf20Sopenharmony_ci#define SSFSTS_CTL 0x00 708c2ecf20Sopenharmony_ci#define SSFSTS_CTL_FSMIE BIT(23) 718c2ecf20Sopenharmony_ci#define SSFSTS_CTL_DS BIT(22) 728c2ecf20Sopenharmony_ci#define SSFSTS_CTL_DBC_SHIFT 16 738c2ecf20Sopenharmony_ci#define SSFSTS_CTL_SPOP BIT(11) 748c2ecf20Sopenharmony_ci#define SSFSTS_CTL_ACS BIT(10) 758c2ecf20Sopenharmony_ci#define SSFSTS_CTL_SCGO BIT(9) 768c2ecf20Sopenharmony_ci#define SSFSTS_CTL_COP_SHIFT 12 778c2ecf20Sopenharmony_ci#define SSFSTS_CTL_FRS BIT(7) 788c2ecf20Sopenharmony_ci#define SSFSTS_CTL_DOFRS BIT(6) 798c2ecf20Sopenharmony_ci#define SSFSTS_CTL_AEL BIT(4) 808c2ecf20Sopenharmony_ci#define SSFSTS_CTL_FCERR BIT(3) 818c2ecf20Sopenharmony_ci#define SSFSTS_CTL_FDONE BIT(2) 828c2ecf20Sopenharmony_ci#define SSFSTS_CTL_SCIP BIT(0) 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci#define PREOP_OPTYPE 0x04 858c2ecf20Sopenharmony_ci#define OPMENU0 0x08 868c2ecf20Sopenharmony_ci#define OPMENU1 0x0c 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci#define OPTYPE_READ_NO_ADDR 0 898c2ecf20Sopenharmony_ci#define OPTYPE_WRITE_NO_ADDR 1 908c2ecf20Sopenharmony_ci#define OPTYPE_READ_WITH_ADDR 2 918c2ecf20Sopenharmony_ci#define OPTYPE_WRITE_WITH_ADDR 3 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci/* CPU specifics */ 948c2ecf20Sopenharmony_ci#define BYT_PR 0x74 958c2ecf20Sopenharmony_ci#define BYT_SSFSTS_CTL 0x90 968c2ecf20Sopenharmony_ci#define BYT_BCR 0xfc 978c2ecf20Sopenharmony_ci#define BYT_BCR_WPD BIT(0) 988c2ecf20Sopenharmony_ci#define BYT_FREG_NUM 5 998c2ecf20Sopenharmony_ci#define BYT_PR_NUM 5 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci#define LPT_PR 0x74 1028c2ecf20Sopenharmony_ci#define LPT_SSFSTS_CTL 0x90 1038c2ecf20Sopenharmony_ci#define LPT_FREG_NUM 5 1048c2ecf20Sopenharmony_ci#define LPT_PR_NUM 5 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci#define BXT_PR 0x84 1078c2ecf20Sopenharmony_ci#define BXT_SSFSTS_CTL 0xa0 1088c2ecf20Sopenharmony_ci#define BXT_FREG_NUM 12 1098c2ecf20Sopenharmony_ci#define BXT_PR_NUM 6 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci#define CNL_PR 0x84 1128c2ecf20Sopenharmony_ci#define CNL_FREG_NUM 6 1138c2ecf20Sopenharmony_ci#define CNL_PR_NUM 5 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci#define LVSCC 0xc4 1168c2ecf20Sopenharmony_ci#define UVSCC 0xc8 1178c2ecf20Sopenharmony_ci#define ERASE_OPCODE_SHIFT 8 1188c2ecf20Sopenharmony_ci#define ERASE_OPCODE_MASK (0xff << ERASE_OPCODE_SHIFT) 1198c2ecf20Sopenharmony_ci#define ERASE_64K_OPCODE_SHIFT 16 1208c2ecf20Sopenharmony_ci#define ERASE_64K_OPCODE_MASK (0xff << ERASE_64K_OPCODE_SHIFT) 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci#define INTEL_SPI_TIMEOUT 5000 /* ms */ 1238c2ecf20Sopenharmony_ci#define INTEL_SPI_FIFO_SZ 64 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci/** 1268c2ecf20Sopenharmony_ci * struct intel_spi - Driver private data 1278c2ecf20Sopenharmony_ci * @dev: Device pointer 1288c2ecf20Sopenharmony_ci * @info: Pointer to board specific info 1298c2ecf20Sopenharmony_ci * @nor: SPI NOR layer structure 1308c2ecf20Sopenharmony_ci * @base: Beginning of MMIO space 1318c2ecf20Sopenharmony_ci * @pregs: Start of protection registers 1328c2ecf20Sopenharmony_ci * @sregs: Start of software sequencer registers 1338c2ecf20Sopenharmony_ci * @nregions: Maximum number of regions 1348c2ecf20Sopenharmony_ci * @pr_num: Maximum number of protected range registers 1358c2ecf20Sopenharmony_ci * @locked: Is SPI setting locked 1368c2ecf20Sopenharmony_ci * @swseq_reg: Use SW sequencer in register reads/writes 1378c2ecf20Sopenharmony_ci * @swseq_erase: Use SW sequencer in erase operation 1388c2ecf20Sopenharmony_ci * @erase_64k: 64k erase supported 1398c2ecf20Sopenharmony_ci * @atomic_preopcode: Holds preopcode when atomic sequence is requested 1408c2ecf20Sopenharmony_ci * @opcodes: Opcodes which are supported. This are programmed by BIOS 1418c2ecf20Sopenharmony_ci * before it locks down the controller. 1428c2ecf20Sopenharmony_ci */ 1438c2ecf20Sopenharmony_cistruct intel_spi { 1448c2ecf20Sopenharmony_ci struct device *dev; 1458c2ecf20Sopenharmony_ci const struct intel_spi_boardinfo *info; 1468c2ecf20Sopenharmony_ci struct spi_nor nor; 1478c2ecf20Sopenharmony_ci void __iomem *base; 1488c2ecf20Sopenharmony_ci void __iomem *pregs; 1498c2ecf20Sopenharmony_ci void __iomem *sregs; 1508c2ecf20Sopenharmony_ci size_t nregions; 1518c2ecf20Sopenharmony_ci size_t pr_num; 1528c2ecf20Sopenharmony_ci bool locked; 1538c2ecf20Sopenharmony_ci bool swseq_reg; 1548c2ecf20Sopenharmony_ci bool swseq_erase; 1558c2ecf20Sopenharmony_ci bool erase_64k; 1568c2ecf20Sopenharmony_ci u8 atomic_preopcode; 1578c2ecf20Sopenharmony_ci u8 opcodes[8]; 1588c2ecf20Sopenharmony_ci}; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_cistatic bool writeable; 1618c2ecf20Sopenharmony_cimodule_param(writeable, bool, 0); 1628c2ecf20Sopenharmony_ciMODULE_PARM_DESC(writeable, "Enable write access to SPI flash chip (default=0)"); 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_cistatic void intel_spi_dump_regs(struct intel_spi *ispi) 1658c2ecf20Sopenharmony_ci{ 1668c2ecf20Sopenharmony_ci u32 value; 1678c2ecf20Sopenharmony_ci int i; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "BFPREG=0x%08x\n", readl(ispi->base + BFPREG)); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci value = readl(ispi->base + HSFSTS_CTL); 1728c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "HSFSTS_CTL=0x%08x\n", value); 1738c2ecf20Sopenharmony_ci if (value & HSFSTS_CTL_FLOCKDN) 1748c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "-> Locked\n"); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "FADDR=0x%08x\n", readl(ispi->base + FADDR)); 1778c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "DLOCK=0x%08x\n", readl(ispi->base + DLOCK)); 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci for (i = 0; i < 16; i++) 1808c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "FDATA(%d)=0x%08x\n", 1818c2ecf20Sopenharmony_ci i, readl(ispi->base + FDATA(i))); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "FRACC=0x%08x\n", readl(ispi->base + FRACC)); 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci for (i = 0; i < ispi->nregions; i++) 1868c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "FREG(%d)=0x%08x\n", i, 1878c2ecf20Sopenharmony_ci readl(ispi->base + FREG(i))); 1888c2ecf20Sopenharmony_ci for (i = 0; i < ispi->pr_num; i++) 1898c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "PR(%d)=0x%08x\n", i, 1908c2ecf20Sopenharmony_ci readl(ispi->pregs + PR(i))); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci if (ispi->sregs) { 1938c2ecf20Sopenharmony_ci value = readl(ispi->sregs + SSFSTS_CTL); 1948c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "SSFSTS_CTL=0x%08x\n", value); 1958c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "PREOP_OPTYPE=0x%08x\n", 1968c2ecf20Sopenharmony_ci readl(ispi->sregs + PREOP_OPTYPE)); 1978c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "OPMENU0=0x%08x\n", 1988c2ecf20Sopenharmony_ci readl(ispi->sregs + OPMENU0)); 1998c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "OPMENU1=0x%08x\n", 2008c2ecf20Sopenharmony_ci readl(ispi->sregs + OPMENU1)); 2018c2ecf20Sopenharmony_ci } 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci if (ispi->info->type == INTEL_SPI_BYT) 2048c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "BCR=0x%08x\n", readl(ispi->base + BYT_BCR)); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "LVSCC=0x%08x\n", readl(ispi->base + LVSCC)); 2078c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "UVSCC=0x%08x\n", readl(ispi->base + UVSCC)); 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "Protected regions:\n"); 2108c2ecf20Sopenharmony_ci for (i = 0; i < ispi->pr_num; i++) { 2118c2ecf20Sopenharmony_ci u32 base, limit; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci value = readl(ispi->pregs + PR(i)); 2148c2ecf20Sopenharmony_ci if (!(value & (PR_WPE | PR_RPE))) 2158c2ecf20Sopenharmony_ci continue; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci limit = (value & PR_LIMIT_MASK) >> PR_LIMIT_SHIFT; 2188c2ecf20Sopenharmony_ci base = value & PR_BASE_MASK; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, " %02d base: 0x%08x limit: 0x%08x [%c%c]\n", 2218c2ecf20Sopenharmony_ci i, base << 12, (limit << 12) | 0xfff, 2228c2ecf20Sopenharmony_ci value & PR_WPE ? 'W' : '.', 2238c2ecf20Sopenharmony_ci value & PR_RPE ? 'R' : '.'); 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "Flash regions:\n"); 2278c2ecf20Sopenharmony_ci for (i = 0; i < ispi->nregions; i++) { 2288c2ecf20Sopenharmony_ci u32 region, base, limit; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci region = readl(ispi->base + FREG(i)); 2318c2ecf20Sopenharmony_ci base = region & FREG_BASE_MASK; 2328c2ecf20Sopenharmony_ci limit = (region & FREG_LIMIT_MASK) >> FREG_LIMIT_SHIFT; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci if (base >= limit || (i > 0 && limit == 0)) 2358c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, " %02d disabled\n", i); 2368c2ecf20Sopenharmony_ci else 2378c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, " %02d base: 0x%08x limit: 0x%08x\n", 2388c2ecf20Sopenharmony_ci i, base << 12, (limit << 12) | 0xfff); 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "Using %cW sequencer for register access\n", 2428c2ecf20Sopenharmony_ci ispi->swseq_reg ? 'S' : 'H'); 2438c2ecf20Sopenharmony_ci dev_dbg(ispi->dev, "Using %cW sequencer for erase operation\n", 2448c2ecf20Sopenharmony_ci ispi->swseq_erase ? 'S' : 'H'); 2458c2ecf20Sopenharmony_ci} 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci/* Reads max INTEL_SPI_FIFO_SZ bytes from the device fifo */ 2488c2ecf20Sopenharmony_cistatic int intel_spi_read_block(struct intel_spi *ispi, void *buf, size_t size) 2498c2ecf20Sopenharmony_ci{ 2508c2ecf20Sopenharmony_ci size_t bytes; 2518c2ecf20Sopenharmony_ci int i = 0; 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci if (size > INTEL_SPI_FIFO_SZ) 2548c2ecf20Sopenharmony_ci return -EINVAL; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci while (size > 0) { 2578c2ecf20Sopenharmony_ci bytes = min_t(size_t, size, 4); 2588c2ecf20Sopenharmony_ci memcpy_fromio(buf, ispi->base + FDATA(i), bytes); 2598c2ecf20Sopenharmony_ci size -= bytes; 2608c2ecf20Sopenharmony_ci buf += bytes; 2618c2ecf20Sopenharmony_ci i++; 2628c2ecf20Sopenharmony_ci } 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci return 0; 2658c2ecf20Sopenharmony_ci} 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci/* Writes max INTEL_SPI_FIFO_SZ bytes to the device fifo */ 2688c2ecf20Sopenharmony_cistatic int intel_spi_write_block(struct intel_spi *ispi, const void *buf, 2698c2ecf20Sopenharmony_ci size_t size) 2708c2ecf20Sopenharmony_ci{ 2718c2ecf20Sopenharmony_ci size_t bytes; 2728c2ecf20Sopenharmony_ci int i = 0; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci if (size > INTEL_SPI_FIFO_SZ) 2758c2ecf20Sopenharmony_ci return -EINVAL; 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci while (size > 0) { 2788c2ecf20Sopenharmony_ci bytes = min_t(size_t, size, 4); 2798c2ecf20Sopenharmony_ci memcpy_toio(ispi->base + FDATA(i), buf, bytes); 2808c2ecf20Sopenharmony_ci size -= bytes; 2818c2ecf20Sopenharmony_ci buf += bytes; 2828c2ecf20Sopenharmony_ci i++; 2838c2ecf20Sopenharmony_ci } 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci return 0; 2868c2ecf20Sopenharmony_ci} 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_cistatic int intel_spi_wait_hw_busy(struct intel_spi *ispi) 2898c2ecf20Sopenharmony_ci{ 2908c2ecf20Sopenharmony_ci u32 val; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci return readl_poll_timeout(ispi->base + HSFSTS_CTL, val, 2938c2ecf20Sopenharmony_ci !(val & HSFSTS_CTL_SCIP), 0, 2948c2ecf20Sopenharmony_ci INTEL_SPI_TIMEOUT * 1000); 2958c2ecf20Sopenharmony_ci} 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_cistatic int intel_spi_wait_sw_busy(struct intel_spi *ispi) 2988c2ecf20Sopenharmony_ci{ 2998c2ecf20Sopenharmony_ci u32 val; 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci return readl_poll_timeout(ispi->sregs + SSFSTS_CTL, val, 3028c2ecf20Sopenharmony_ci !(val & SSFSTS_CTL_SCIP), 0, 3038c2ecf20Sopenharmony_ci INTEL_SPI_TIMEOUT * 1000); 3048c2ecf20Sopenharmony_ci} 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_cistatic bool intel_spi_set_writeable(struct intel_spi *ispi) 3078c2ecf20Sopenharmony_ci{ 3088c2ecf20Sopenharmony_ci if (!ispi->info->set_writeable) 3098c2ecf20Sopenharmony_ci return false; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci return ispi->info->set_writeable(ispi->base, ispi->info->data); 3128c2ecf20Sopenharmony_ci} 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_cistatic int intel_spi_init(struct intel_spi *ispi) 3158c2ecf20Sopenharmony_ci{ 3168c2ecf20Sopenharmony_ci u32 opmenu0, opmenu1, lvscc, uvscc, val; 3178c2ecf20Sopenharmony_ci int i; 3188c2ecf20Sopenharmony_ci 3198c2ecf20Sopenharmony_ci switch (ispi->info->type) { 3208c2ecf20Sopenharmony_ci case INTEL_SPI_BYT: 3218c2ecf20Sopenharmony_ci ispi->sregs = ispi->base + BYT_SSFSTS_CTL; 3228c2ecf20Sopenharmony_ci ispi->pregs = ispi->base + BYT_PR; 3238c2ecf20Sopenharmony_ci ispi->nregions = BYT_FREG_NUM; 3248c2ecf20Sopenharmony_ci ispi->pr_num = BYT_PR_NUM; 3258c2ecf20Sopenharmony_ci ispi->swseq_reg = true; 3268c2ecf20Sopenharmony_ci break; 3278c2ecf20Sopenharmony_ci 3288c2ecf20Sopenharmony_ci case INTEL_SPI_LPT: 3298c2ecf20Sopenharmony_ci ispi->sregs = ispi->base + LPT_SSFSTS_CTL; 3308c2ecf20Sopenharmony_ci ispi->pregs = ispi->base + LPT_PR; 3318c2ecf20Sopenharmony_ci ispi->nregions = LPT_FREG_NUM; 3328c2ecf20Sopenharmony_ci ispi->pr_num = LPT_PR_NUM; 3338c2ecf20Sopenharmony_ci ispi->swseq_reg = true; 3348c2ecf20Sopenharmony_ci break; 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci case INTEL_SPI_BXT: 3378c2ecf20Sopenharmony_ci ispi->sregs = ispi->base + BXT_SSFSTS_CTL; 3388c2ecf20Sopenharmony_ci ispi->pregs = ispi->base + BXT_PR; 3398c2ecf20Sopenharmony_ci ispi->nregions = BXT_FREG_NUM; 3408c2ecf20Sopenharmony_ci ispi->pr_num = BXT_PR_NUM; 3418c2ecf20Sopenharmony_ci ispi->erase_64k = true; 3428c2ecf20Sopenharmony_ci break; 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci case INTEL_SPI_CNL: 3458c2ecf20Sopenharmony_ci ispi->sregs = NULL; 3468c2ecf20Sopenharmony_ci ispi->pregs = ispi->base + CNL_PR; 3478c2ecf20Sopenharmony_ci ispi->nregions = CNL_FREG_NUM; 3488c2ecf20Sopenharmony_ci ispi->pr_num = CNL_PR_NUM; 3498c2ecf20Sopenharmony_ci break; 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci default: 3528c2ecf20Sopenharmony_ci return -EINVAL; 3538c2ecf20Sopenharmony_ci } 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_ci /* Try to disable write protection if user asked to do so */ 3568c2ecf20Sopenharmony_ci if (writeable && !intel_spi_set_writeable(ispi)) { 3578c2ecf20Sopenharmony_ci dev_warn(ispi->dev, "can't disable chip write protection\n"); 3588c2ecf20Sopenharmony_ci writeable = false; 3598c2ecf20Sopenharmony_ci } 3608c2ecf20Sopenharmony_ci 3618c2ecf20Sopenharmony_ci /* Disable #SMI generation from HW sequencer */ 3628c2ecf20Sopenharmony_ci val = readl(ispi->base + HSFSTS_CTL); 3638c2ecf20Sopenharmony_ci val &= ~HSFSTS_CTL_FSMIE; 3648c2ecf20Sopenharmony_ci writel(val, ispi->base + HSFSTS_CTL); 3658c2ecf20Sopenharmony_ci 3668c2ecf20Sopenharmony_ci /* 3678c2ecf20Sopenharmony_ci * Determine whether erase operation should use HW or SW sequencer. 3688c2ecf20Sopenharmony_ci * 3698c2ecf20Sopenharmony_ci * The HW sequencer has a predefined list of opcodes, with only the 3708c2ecf20Sopenharmony_ci * erase opcode being programmable in LVSCC and UVSCC registers. 3718c2ecf20Sopenharmony_ci * If these registers don't contain a valid erase opcode, erase 3728c2ecf20Sopenharmony_ci * cannot be done using HW sequencer. 3738c2ecf20Sopenharmony_ci */ 3748c2ecf20Sopenharmony_ci lvscc = readl(ispi->base + LVSCC); 3758c2ecf20Sopenharmony_ci uvscc = readl(ispi->base + UVSCC); 3768c2ecf20Sopenharmony_ci if (!(lvscc & ERASE_OPCODE_MASK) || !(uvscc & ERASE_OPCODE_MASK)) 3778c2ecf20Sopenharmony_ci ispi->swseq_erase = true; 3788c2ecf20Sopenharmony_ci /* SPI controller on Intel BXT supports 64K erase opcode */ 3798c2ecf20Sopenharmony_ci if (ispi->info->type == INTEL_SPI_BXT && !ispi->swseq_erase) 3808c2ecf20Sopenharmony_ci if (!(lvscc & ERASE_64K_OPCODE_MASK) || 3818c2ecf20Sopenharmony_ci !(uvscc & ERASE_64K_OPCODE_MASK)) 3828c2ecf20Sopenharmony_ci ispi->erase_64k = false; 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci if (ispi->sregs == NULL && (ispi->swseq_reg || ispi->swseq_erase)) { 3858c2ecf20Sopenharmony_ci dev_err(ispi->dev, "software sequencer not supported, but required\n"); 3868c2ecf20Sopenharmony_ci return -EINVAL; 3878c2ecf20Sopenharmony_ci } 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci /* 3908c2ecf20Sopenharmony_ci * Some controllers can only do basic operations using hardware 3918c2ecf20Sopenharmony_ci * sequencer. All other operations are supposed to be carried out 3928c2ecf20Sopenharmony_ci * using software sequencer. 3938c2ecf20Sopenharmony_ci */ 3948c2ecf20Sopenharmony_ci if (ispi->swseq_reg) { 3958c2ecf20Sopenharmony_ci /* Disable #SMI generation from SW sequencer */ 3968c2ecf20Sopenharmony_ci val = readl(ispi->sregs + SSFSTS_CTL); 3978c2ecf20Sopenharmony_ci val &= ~SSFSTS_CTL_FSMIE; 3988c2ecf20Sopenharmony_ci writel(val, ispi->sregs + SSFSTS_CTL); 3998c2ecf20Sopenharmony_ci } 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci /* Check controller's lock status */ 4028c2ecf20Sopenharmony_ci val = readl(ispi->base + HSFSTS_CTL); 4038c2ecf20Sopenharmony_ci ispi->locked = !!(val & HSFSTS_CTL_FLOCKDN); 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci if (ispi->locked && ispi->sregs) { 4068c2ecf20Sopenharmony_ci /* 4078c2ecf20Sopenharmony_ci * BIOS programs allowed opcodes and then locks down the 4088c2ecf20Sopenharmony_ci * register. So read back what opcodes it decided to support. 4098c2ecf20Sopenharmony_ci * That's the set we are going to support as well. 4108c2ecf20Sopenharmony_ci */ 4118c2ecf20Sopenharmony_ci opmenu0 = readl(ispi->sregs + OPMENU0); 4128c2ecf20Sopenharmony_ci opmenu1 = readl(ispi->sregs + OPMENU1); 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci if (opmenu0 && opmenu1) { 4158c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ispi->opcodes) / 2; i++) { 4168c2ecf20Sopenharmony_ci ispi->opcodes[i] = opmenu0 >> i * 8; 4178c2ecf20Sopenharmony_ci ispi->opcodes[i + 4] = opmenu1 >> i * 8; 4188c2ecf20Sopenharmony_ci } 4198c2ecf20Sopenharmony_ci } 4208c2ecf20Sopenharmony_ci } 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci intel_spi_dump_regs(ispi); 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_ci return 0; 4258c2ecf20Sopenharmony_ci} 4268c2ecf20Sopenharmony_ci 4278c2ecf20Sopenharmony_cistatic int intel_spi_opcode_index(struct intel_spi *ispi, u8 opcode, int optype) 4288c2ecf20Sopenharmony_ci{ 4298c2ecf20Sopenharmony_ci int i; 4308c2ecf20Sopenharmony_ci int preop; 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci if (ispi->locked) { 4338c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(ispi->opcodes); i++) 4348c2ecf20Sopenharmony_ci if (ispi->opcodes[i] == opcode) 4358c2ecf20Sopenharmony_ci return i; 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ci return -EINVAL; 4388c2ecf20Sopenharmony_ci } 4398c2ecf20Sopenharmony_ci 4408c2ecf20Sopenharmony_ci /* The lock is off, so just use index 0 */ 4418c2ecf20Sopenharmony_ci writel(opcode, ispi->sregs + OPMENU0); 4428c2ecf20Sopenharmony_ci preop = readw(ispi->sregs + PREOP_OPTYPE); 4438c2ecf20Sopenharmony_ci writel(optype << 16 | preop, ispi->sregs + PREOP_OPTYPE); 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci return 0; 4468c2ecf20Sopenharmony_ci} 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_cistatic int intel_spi_hw_cycle(struct intel_spi *ispi, u8 opcode, size_t len) 4498c2ecf20Sopenharmony_ci{ 4508c2ecf20Sopenharmony_ci u32 val, status; 4518c2ecf20Sopenharmony_ci int ret; 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_ci val = readl(ispi->base + HSFSTS_CTL); 4548c2ecf20Sopenharmony_ci val &= ~(HSFSTS_CTL_FCYCLE_MASK | HSFSTS_CTL_FDBC_MASK); 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci switch (opcode) { 4578c2ecf20Sopenharmony_ci case SPINOR_OP_RDID: 4588c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_FCYCLE_RDID; 4598c2ecf20Sopenharmony_ci break; 4608c2ecf20Sopenharmony_ci case SPINOR_OP_WRSR: 4618c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_FCYCLE_WRSR; 4628c2ecf20Sopenharmony_ci break; 4638c2ecf20Sopenharmony_ci case SPINOR_OP_RDSR: 4648c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_FCYCLE_RDSR; 4658c2ecf20Sopenharmony_ci break; 4668c2ecf20Sopenharmony_ci default: 4678c2ecf20Sopenharmony_ci return -EINVAL; 4688c2ecf20Sopenharmony_ci } 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_ci if (len > INTEL_SPI_FIFO_SZ) 4718c2ecf20Sopenharmony_ci return -EINVAL; 4728c2ecf20Sopenharmony_ci 4738c2ecf20Sopenharmony_ci val |= (len - 1) << HSFSTS_CTL_FDBC_SHIFT; 4748c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE; 4758c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_FGO; 4768c2ecf20Sopenharmony_ci writel(val, ispi->base + HSFSTS_CTL); 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci ret = intel_spi_wait_hw_busy(ispi); 4798c2ecf20Sopenharmony_ci if (ret) 4808c2ecf20Sopenharmony_ci return ret; 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci status = readl(ispi->base + HSFSTS_CTL); 4838c2ecf20Sopenharmony_ci if (status & HSFSTS_CTL_FCERR) 4848c2ecf20Sopenharmony_ci return -EIO; 4858c2ecf20Sopenharmony_ci else if (status & HSFSTS_CTL_AEL) 4868c2ecf20Sopenharmony_ci return -EACCES; 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci return 0; 4898c2ecf20Sopenharmony_ci} 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_cistatic int intel_spi_sw_cycle(struct intel_spi *ispi, u8 opcode, size_t len, 4928c2ecf20Sopenharmony_ci int optype) 4938c2ecf20Sopenharmony_ci{ 4948c2ecf20Sopenharmony_ci u32 val = 0, status; 4958c2ecf20Sopenharmony_ci u8 atomic_preopcode; 4968c2ecf20Sopenharmony_ci int ret; 4978c2ecf20Sopenharmony_ci 4988c2ecf20Sopenharmony_ci ret = intel_spi_opcode_index(ispi, opcode, optype); 4998c2ecf20Sopenharmony_ci if (ret < 0) 5008c2ecf20Sopenharmony_ci return ret; 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci if (len > INTEL_SPI_FIFO_SZ) 5038c2ecf20Sopenharmony_ci return -EINVAL; 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ci /* 5068c2ecf20Sopenharmony_ci * Always clear it after each SW sequencer operation regardless 5078c2ecf20Sopenharmony_ci * of whether it is successful or not. 5088c2ecf20Sopenharmony_ci */ 5098c2ecf20Sopenharmony_ci atomic_preopcode = ispi->atomic_preopcode; 5108c2ecf20Sopenharmony_ci ispi->atomic_preopcode = 0; 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_ci /* Only mark 'Data Cycle' bit when there is data to be transferred */ 5138c2ecf20Sopenharmony_ci if (len > 0) 5148c2ecf20Sopenharmony_ci val = ((len - 1) << SSFSTS_CTL_DBC_SHIFT) | SSFSTS_CTL_DS; 5158c2ecf20Sopenharmony_ci val |= ret << SSFSTS_CTL_COP_SHIFT; 5168c2ecf20Sopenharmony_ci val |= SSFSTS_CTL_FCERR | SSFSTS_CTL_FDONE; 5178c2ecf20Sopenharmony_ci val |= SSFSTS_CTL_SCGO; 5188c2ecf20Sopenharmony_ci if (atomic_preopcode) { 5198c2ecf20Sopenharmony_ci u16 preop; 5208c2ecf20Sopenharmony_ci 5218c2ecf20Sopenharmony_ci switch (optype) { 5228c2ecf20Sopenharmony_ci case OPTYPE_WRITE_NO_ADDR: 5238c2ecf20Sopenharmony_ci case OPTYPE_WRITE_WITH_ADDR: 5248c2ecf20Sopenharmony_ci /* Pick matching preopcode for the atomic sequence */ 5258c2ecf20Sopenharmony_ci preop = readw(ispi->sregs + PREOP_OPTYPE); 5268c2ecf20Sopenharmony_ci if ((preop & 0xff) == atomic_preopcode) 5278c2ecf20Sopenharmony_ci ; /* Do nothing */ 5288c2ecf20Sopenharmony_ci else if ((preop >> 8) == atomic_preopcode) 5298c2ecf20Sopenharmony_ci val |= SSFSTS_CTL_SPOP; 5308c2ecf20Sopenharmony_ci else 5318c2ecf20Sopenharmony_ci return -EINVAL; 5328c2ecf20Sopenharmony_ci 5338c2ecf20Sopenharmony_ci /* Enable atomic sequence */ 5348c2ecf20Sopenharmony_ci val |= SSFSTS_CTL_ACS; 5358c2ecf20Sopenharmony_ci break; 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci default: 5388c2ecf20Sopenharmony_ci return -EINVAL; 5398c2ecf20Sopenharmony_ci } 5408c2ecf20Sopenharmony_ci 5418c2ecf20Sopenharmony_ci } 5428c2ecf20Sopenharmony_ci writel(val, ispi->sregs + SSFSTS_CTL); 5438c2ecf20Sopenharmony_ci 5448c2ecf20Sopenharmony_ci ret = intel_spi_wait_sw_busy(ispi); 5458c2ecf20Sopenharmony_ci if (ret) 5468c2ecf20Sopenharmony_ci return ret; 5478c2ecf20Sopenharmony_ci 5488c2ecf20Sopenharmony_ci status = readl(ispi->sregs + SSFSTS_CTL); 5498c2ecf20Sopenharmony_ci if (status & SSFSTS_CTL_FCERR) 5508c2ecf20Sopenharmony_ci return -EIO; 5518c2ecf20Sopenharmony_ci else if (status & SSFSTS_CTL_AEL) 5528c2ecf20Sopenharmony_ci return -EACCES; 5538c2ecf20Sopenharmony_ci 5548c2ecf20Sopenharmony_ci return 0; 5558c2ecf20Sopenharmony_ci} 5568c2ecf20Sopenharmony_ci 5578c2ecf20Sopenharmony_cistatic int intel_spi_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf, 5588c2ecf20Sopenharmony_ci size_t len) 5598c2ecf20Sopenharmony_ci{ 5608c2ecf20Sopenharmony_ci struct intel_spi *ispi = nor->priv; 5618c2ecf20Sopenharmony_ci int ret; 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_ci /* Address of the first chip */ 5648c2ecf20Sopenharmony_ci writel(0, ispi->base + FADDR); 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ci if (ispi->swseq_reg) 5678c2ecf20Sopenharmony_ci ret = intel_spi_sw_cycle(ispi, opcode, len, 5688c2ecf20Sopenharmony_ci OPTYPE_READ_NO_ADDR); 5698c2ecf20Sopenharmony_ci else 5708c2ecf20Sopenharmony_ci ret = intel_spi_hw_cycle(ispi, opcode, len); 5718c2ecf20Sopenharmony_ci 5728c2ecf20Sopenharmony_ci if (ret) 5738c2ecf20Sopenharmony_ci return ret; 5748c2ecf20Sopenharmony_ci 5758c2ecf20Sopenharmony_ci return intel_spi_read_block(ispi, buf, len); 5768c2ecf20Sopenharmony_ci} 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_cistatic int intel_spi_write_reg(struct spi_nor *nor, u8 opcode, const u8 *buf, 5798c2ecf20Sopenharmony_ci size_t len) 5808c2ecf20Sopenharmony_ci{ 5818c2ecf20Sopenharmony_ci struct intel_spi *ispi = nor->priv; 5828c2ecf20Sopenharmony_ci int ret; 5838c2ecf20Sopenharmony_ci 5848c2ecf20Sopenharmony_ci /* 5858c2ecf20Sopenharmony_ci * This is handled with atomic operation and preop code in Intel 5868c2ecf20Sopenharmony_ci * controller so we only verify that it is available. If the 5878c2ecf20Sopenharmony_ci * controller is not locked, program the opcode to the PREOP 5888c2ecf20Sopenharmony_ci * register for later use. 5898c2ecf20Sopenharmony_ci * 5908c2ecf20Sopenharmony_ci * When hardware sequencer is used there is no need to program 5918c2ecf20Sopenharmony_ci * any opcodes (it handles them automatically as part of a command). 5928c2ecf20Sopenharmony_ci */ 5938c2ecf20Sopenharmony_ci if (opcode == SPINOR_OP_WREN) { 5948c2ecf20Sopenharmony_ci u16 preop; 5958c2ecf20Sopenharmony_ci 5968c2ecf20Sopenharmony_ci if (!ispi->swseq_reg) 5978c2ecf20Sopenharmony_ci return 0; 5988c2ecf20Sopenharmony_ci 5998c2ecf20Sopenharmony_ci preop = readw(ispi->sregs + PREOP_OPTYPE); 6008c2ecf20Sopenharmony_ci if ((preop & 0xff) != opcode && (preop >> 8) != opcode) { 6018c2ecf20Sopenharmony_ci if (ispi->locked) 6028c2ecf20Sopenharmony_ci return -EINVAL; 6038c2ecf20Sopenharmony_ci writel(opcode, ispi->sregs + PREOP_OPTYPE); 6048c2ecf20Sopenharmony_ci } 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci /* 6078c2ecf20Sopenharmony_ci * This enables atomic sequence on next SW sycle. Will 6088c2ecf20Sopenharmony_ci * be cleared after next operation. 6098c2ecf20Sopenharmony_ci */ 6108c2ecf20Sopenharmony_ci ispi->atomic_preopcode = opcode; 6118c2ecf20Sopenharmony_ci return 0; 6128c2ecf20Sopenharmony_ci } 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_ci /* 6158c2ecf20Sopenharmony_ci * We hope that HW sequencer will do the right thing automatically and 6168c2ecf20Sopenharmony_ci * with the SW sequencer we cannot use preopcode anyway, so just ignore 6178c2ecf20Sopenharmony_ci * the Write Disable operation and pretend it was completed 6188c2ecf20Sopenharmony_ci * successfully. 6198c2ecf20Sopenharmony_ci */ 6208c2ecf20Sopenharmony_ci if (opcode == SPINOR_OP_WRDI) 6218c2ecf20Sopenharmony_ci return 0; 6228c2ecf20Sopenharmony_ci 6238c2ecf20Sopenharmony_ci writel(0, ispi->base + FADDR); 6248c2ecf20Sopenharmony_ci 6258c2ecf20Sopenharmony_ci /* Write the value beforehand */ 6268c2ecf20Sopenharmony_ci ret = intel_spi_write_block(ispi, buf, len); 6278c2ecf20Sopenharmony_ci if (ret) 6288c2ecf20Sopenharmony_ci return ret; 6298c2ecf20Sopenharmony_ci 6308c2ecf20Sopenharmony_ci if (ispi->swseq_reg) 6318c2ecf20Sopenharmony_ci return intel_spi_sw_cycle(ispi, opcode, len, 6328c2ecf20Sopenharmony_ci OPTYPE_WRITE_NO_ADDR); 6338c2ecf20Sopenharmony_ci return intel_spi_hw_cycle(ispi, opcode, len); 6348c2ecf20Sopenharmony_ci} 6358c2ecf20Sopenharmony_ci 6368c2ecf20Sopenharmony_cistatic ssize_t intel_spi_read(struct spi_nor *nor, loff_t from, size_t len, 6378c2ecf20Sopenharmony_ci u_char *read_buf) 6388c2ecf20Sopenharmony_ci{ 6398c2ecf20Sopenharmony_ci struct intel_spi *ispi = nor->priv; 6408c2ecf20Sopenharmony_ci size_t block_size, retlen = 0; 6418c2ecf20Sopenharmony_ci u32 val, status; 6428c2ecf20Sopenharmony_ci ssize_t ret; 6438c2ecf20Sopenharmony_ci 6448c2ecf20Sopenharmony_ci /* 6458c2ecf20Sopenharmony_ci * Atomic sequence is not expected with HW sequencer reads. Make 6468c2ecf20Sopenharmony_ci * sure it is cleared regardless. 6478c2ecf20Sopenharmony_ci */ 6488c2ecf20Sopenharmony_ci if (WARN_ON_ONCE(ispi->atomic_preopcode)) 6498c2ecf20Sopenharmony_ci ispi->atomic_preopcode = 0; 6508c2ecf20Sopenharmony_ci 6518c2ecf20Sopenharmony_ci switch (nor->read_opcode) { 6528c2ecf20Sopenharmony_ci case SPINOR_OP_READ: 6538c2ecf20Sopenharmony_ci case SPINOR_OP_READ_FAST: 6548c2ecf20Sopenharmony_ci case SPINOR_OP_READ_4B: 6558c2ecf20Sopenharmony_ci case SPINOR_OP_READ_FAST_4B: 6568c2ecf20Sopenharmony_ci break; 6578c2ecf20Sopenharmony_ci default: 6588c2ecf20Sopenharmony_ci return -EINVAL; 6598c2ecf20Sopenharmony_ci } 6608c2ecf20Sopenharmony_ci 6618c2ecf20Sopenharmony_ci while (len > 0) { 6628c2ecf20Sopenharmony_ci block_size = min_t(size_t, len, INTEL_SPI_FIFO_SZ); 6638c2ecf20Sopenharmony_ci 6648c2ecf20Sopenharmony_ci /* Read cannot cross 4K boundary */ 6658c2ecf20Sopenharmony_ci block_size = min_t(loff_t, from + block_size, 6668c2ecf20Sopenharmony_ci round_up(from + 1, SZ_4K)) - from; 6678c2ecf20Sopenharmony_ci 6688c2ecf20Sopenharmony_ci writel(from, ispi->base + FADDR); 6698c2ecf20Sopenharmony_ci 6708c2ecf20Sopenharmony_ci val = readl(ispi->base + HSFSTS_CTL); 6718c2ecf20Sopenharmony_ci val &= ~(HSFSTS_CTL_FDBC_MASK | HSFSTS_CTL_FCYCLE_MASK); 6728c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_AEL | HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE; 6738c2ecf20Sopenharmony_ci val |= (block_size - 1) << HSFSTS_CTL_FDBC_SHIFT; 6748c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_FCYCLE_READ; 6758c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_FGO; 6768c2ecf20Sopenharmony_ci writel(val, ispi->base + HSFSTS_CTL); 6778c2ecf20Sopenharmony_ci 6788c2ecf20Sopenharmony_ci ret = intel_spi_wait_hw_busy(ispi); 6798c2ecf20Sopenharmony_ci if (ret) 6808c2ecf20Sopenharmony_ci return ret; 6818c2ecf20Sopenharmony_ci 6828c2ecf20Sopenharmony_ci status = readl(ispi->base + HSFSTS_CTL); 6838c2ecf20Sopenharmony_ci if (status & HSFSTS_CTL_FCERR) 6848c2ecf20Sopenharmony_ci ret = -EIO; 6858c2ecf20Sopenharmony_ci else if (status & HSFSTS_CTL_AEL) 6868c2ecf20Sopenharmony_ci ret = -EACCES; 6878c2ecf20Sopenharmony_ci 6888c2ecf20Sopenharmony_ci if (ret < 0) { 6898c2ecf20Sopenharmony_ci dev_err(ispi->dev, "read error: %llx: %#x\n", from, 6908c2ecf20Sopenharmony_ci status); 6918c2ecf20Sopenharmony_ci return ret; 6928c2ecf20Sopenharmony_ci } 6938c2ecf20Sopenharmony_ci 6948c2ecf20Sopenharmony_ci ret = intel_spi_read_block(ispi, read_buf, block_size); 6958c2ecf20Sopenharmony_ci if (ret) 6968c2ecf20Sopenharmony_ci return ret; 6978c2ecf20Sopenharmony_ci 6988c2ecf20Sopenharmony_ci len -= block_size; 6998c2ecf20Sopenharmony_ci from += block_size; 7008c2ecf20Sopenharmony_ci retlen += block_size; 7018c2ecf20Sopenharmony_ci read_buf += block_size; 7028c2ecf20Sopenharmony_ci } 7038c2ecf20Sopenharmony_ci 7048c2ecf20Sopenharmony_ci return retlen; 7058c2ecf20Sopenharmony_ci} 7068c2ecf20Sopenharmony_ci 7078c2ecf20Sopenharmony_cistatic ssize_t intel_spi_write(struct spi_nor *nor, loff_t to, size_t len, 7088c2ecf20Sopenharmony_ci const u_char *write_buf) 7098c2ecf20Sopenharmony_ci{ 7108c2ecf20Sopenharmony_ci struct intel_spi *ispi = nor->priv; 7118c2ecf20Sopenharmony_ci size_t block_size, retlen = 0; 7128c2ecf20Sopenharmony_ci u32 val, status; 7138c2ecf20Sopenharmony_ci ssize_t ret; 7148c2ecf20Sopenharmony_ci 7158c2ecf20Sopenharmony_ci /* Not needed with HW sequencer write, make sure it is cleared */ 7168c2ecf20Sopenharmony_ci ispi->atomic_preopcode = 0; 7178c2ecf20Sopenharmony_ci 7188c2ecf20Sopenharmony_ci while (len > 0) { 7198c2ecf20Sopenharmony_ci block_size = min_t(size_t, len, INTEL_SPI_FIFO_SZ); 7208c2ecf20Sopenharmony_ci 7218c2ecf20Sopenharmony_ci /* Write cannot cross 4K boundary */ 7228c2ecf20Sopenharmony_ci block_size = min_t(loff_t, to + block_size, 7238c2ecf20Sopenharmony_ci round_up(to + 1, SZ_4K)) - to; 7248c2ecf20Sopenharmony_ci 7258c2ecf20Sopenharmony_ci writel(to, ispi->base + FADDR); 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_ci val = readl(ispi->base + HSFSTS_CTL); 7288c2ecf20Sopenharmony_ci val &= ~(HSFSTS_CTL_FDBC_MASK | HSFSTS_CTL_FCYCLE_MASK); 7298c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_AEL | HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE; 7308c2ecf20Sopenharmony_ci val |= (block_size - 1) << HSFSTS_CTL_FDBC_SHIFT; 7318c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_FCYCLE_WRITE; 7328c2ecf20Sopenharmony_ci 7338c2ecf20Sopenharmony_ci ret = intel_spi_write_block(ispi, write_buf, block_size); 7348c2ecf20Sopenharmony_ci if (ret) { 7358c2ecf20Sopenharmony_ci dev_err(ispi->dev, "failed to write block\n"); 7368c2ecf20Sopenharmony_ci return ret; 7378c2ecf20Sopenharmony_ci } 7388c2ecf20Sopenharmony_ci 7398c2ecf20Sopenharmony_ci /* Start the write now */ 7408c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_FGO; 7418c2ecf20Sopenharmony_ci writel(val, ispi->base + HSFSTS_CTL); 7428c2ecf20Sopenharmony_ci 7438c2ecf20Sopenharmony_ci ret = intel_spi_wait_hw_busy(ispi); 7448c2ecf20Sopenharmony_ci if (ret) { 7458c2ecf20Sopenharmony_ci dev_err(ispi->dev, "timeout\n"); 7468c2ecf20Sopenharmony_ci return ret; 7478c2ecf20Sopenharmony_ci } 7488c2ecf20Sopenharmony_ci 7498c2ecf20Sopenharmony_ci status = readl(ispi->base + HSFSTS_CTL); 7508c2ecf20Sopenharmony_ci if (status & HSFSTS_CTL_FCERR) 7518c2ecf20Sopenharmony_ci ret = -EIO; 7528c2ecf20Sopenharmony_ci else if (status & HSFSTS_CTL_AEL) 7538c2ecf20Sopenharmony_ci ret = -EACCES; 7548c2ecf20Sopenharmony_ci 7558c2ecf20Sopenharmony_ci if (ret < 0) { 7568c2ecf20Sopenharmony_ci dev_err(ispi->dev, "write error: %llx: %#x\n", to, 7578c2ecf20Sopenharmony_ci status); 7588c2ecf20Sopenharmony_ci return ret; 7598c2ecf20Sopenharmony_ci } 7608c2ecf20Sopenharmony_ci 7618c2ecf20Sopenharmony_ci len -= block_size; 7628c2ecf20Sopenharmony_ci to += block_size; 7638c2ecf20Sopenharmony_ci retlen += block_size; 7648c2ecf20Sopenharmony_ci write_buf += block_size; 7658c2ecf20Sopenharmony_ci } 7668c2ecf20Sopenharmony_ci 7678c2ecf20Sopenharmony_ci return retlen; 7688c2ecf20Sopenharmony_ci} 7698c2ecf20Sopenharmony_ci 7708c2ecf20Sopenharmony_cistatic int intel_spi_erase(struct spi_nor *nor, loff_t offs) 7718c2ecf20Sopenharmony_ci{ 7728c2ecf20Sopenharmony_ci size_t erase_size, len = nor->mtd.erasesize; 7738c2ecf20Sopenharmony_ci struct intel_spi *ispi = nor->priv; 7748c2ecf20Sopenharmony_ci u32 val, status, cmd; 7758c2ecf20Sopenharmony_ci int ret; 7768c2ecf20Sopenharmony_ci 7778c2ecf20Sopenharmony_ci /* If the hardware can do 64k erase use that when possible */ 7788c2ecf20Sopenharmony_ci if (len >= SZ_64K && ispi->erase_64k) { 7798c2ecf20Sopenharmony_ci cmd = HSFSTS_CTL_FCYCLE_ERASE_64K; 7808c2ecf20Sopenharmony_ci erase_size = SZ_64K; 7818c2ecf20Sopenharmony_ci } else { 7828c2ecf20Sopenharmony_ci cmd = HSFSTS_CTL_FCYCLE_ERASE; 7838c2ecf20Sopenharmony_ci erase_size = SZ_4K; 7848c2ecf20Sopenharmony_ci } 7858c2ecf20Sopenharmony_ci 7868c2ecf20Sopenharmony_ci if (ispi->swseq_erase) { 7878c2ecf20Sopenharmony_ci while (len > 0) { 7888c2ecf20Sopenharmony_ci writel(offs, ispi->base + FADDR); 7898c2ecf20Sopenharmony_ci 7908c2ecf20Sopenharmony_ci ret = intel_spi_sw_cycle(ispi, nor->erase_opcode, 7918c2ecf20Sopenharmony_ci 0, OPTYPE_WRITE_WITH_ADDR); 7928c2ecf20Sopenharmony_ci if (ret) 7938c2ecf20Sopenharmony_ci return ret; 7948c2ecf20Sopenharmony_ci 7958c2ecf20Sopenharmony_ci offs += erase_size; 7968c2ecf20Sopenharmony_ci len -= erase_size; 7978c2ecf20Sopenharmony_ci } 7988c2ecf20Sopenharmony_ci 7998c2ecf20Sopenharmony_ci return 0; 8008c2ecf20Sopenharmony_ci } 8018c2ecf20Sopenharmony_ci 8028c2ecf20Sopenharmony_ci /* Not needed with HW sequencer erase, make sure it is cleared */ 8038c2ecf20Sopenharmony_ci ispi->atomic_preopcode = 0; 8048c2ecf20Sopenharmony_ci 8058c2ecf20Sopenharmony_ci while (len > 0) { 8068c2ecf20Sopenharmony_ci writel(offs, ispi->base + FADDR); 8078c2ecf20Sopenharmony_ci 8088c2ecf20Sopenharmony_ci val = readl(ispi->base + HSFSTS_CTL); 8098c2ecf20Sopenharmony_ci val &= ~(HSFSTS_CTL_FDBC_MASK | HSFSTS_CTL_FCYCLE_MASK); 8108c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_AEL | HSFSTS_CTL_FCERR | HSFSTS_CTL_FDONE; 8118c2ecf20Sopenharmony_ci val |= cmd; 8128c2ecf20Sopenharmony_ci val |= HSFSTS_CTL_FGO; 8138c2ecf20Sopenharmony_ci writel(val, ispi->base + HSFSTS_CTL); 8148c2ecf20Sopenharmony_ci 8158c2ecf20Sopenharmony_ci ret = intel_spi_wait_hw_busy(ispi); 8168c2ecf20Sopenharmony_ci if (ret) 8178c2ecf20Sopenharmony_ci return ret; 8188c2ecf20Sopenharmony_ci 8198c2ecf20Sopenharmony_ci status = readl(ispi->base + HSFSTS_CTL); 8208c2ecf20Sopenharmony_ci if (status & HSFSTS_CTL_FCERR) 8218c2ecf20Sopenharmony_ci return -EIO; 8228c2ecf20Sopenharmony_ci else if (status & HSFSTS_CTL_AEL) 8238c2ecf20Sopenharmony_ci return -EACCES; 8248c2ecf20Sopenharmony_ci 8258c2ecf20Sopenharmony_ci offs += erase_size; 8268c2ecf20Sopenharmony_ci len -= erase_size; 8278c2ecf20Sopenharmony_ci } 8288c2ecf20Sopenharmony_ci 8298c2ecf20Sopenharmony_ci return 0; 8308c2ecf20Sopenharmony_ci} 8318c2ecf20Sopenharmony_ci 8328c2ecf20Sopenharmony_cistatic bool intel_spi_is_protected(const struct intel_spi *ispi, 8338c2ecf20Sopenharmony_ci unsigned int base, unsigned int limit) 8348c2ecf20Sopenharmony_ci{ 8358c2ecf20Sopenharmony_ci int i; 8368c2ecf20Sopenharmony_ci 8378c2ecf20Sopenharmony_ci for (i = 0; i < ispi->pr_num; i++) { 8388c2ecf20Sopenharmony_ci u32 pr_base, pr_limit, pr_value; 8398c2ecf20Sopenharmony_ci 8408c2ecf20Sopenharmony_ci pr_value = readl(ispi->pregs + PR(i)); 8418c2ecf20Sopenharmony_ci if (!(pr_value & (PR_WPE | PR_RPE))) 8428c2ecf20Sopenharmony_ci continue; 8438c2ecf20Sopenharmony_ci 8448c2ecf20Sopenharmony_ci pr_limit = (pr_value & PR_LIMIT_MASK) >> PR_LIMIT_SHIFT; 8458c2ecf20Sopenharmony_ci pr_base = pr_value & PR_BASE_MASK; 8468c2ecf20Sopenharmony_ci 8478c2ecf20Sopenharmony_ci if (pr_base >= base && pr_limit <= limit) 8488c2ecf20Sopenharmony_ci return true; 8498c2ecf20Sopenharmony_ci } 8508c2ecf20Sopenharmony_ci 8518c2ecf20Sopenharmony_ci return false; 8528c2ecf20Sopenharmony_ci} 8538c2ecf20Sopenharmony_ci 8548c2ecf20Sopenharmony_ci/* 8558c2ecf20Sopenharmony_ci * There will be a single partition holding all enabled flash regions. We 8568c2ecf20Sopenharmony_ci * call this "BIOS". 8578c2ecf20Sopenharmony_ci */ 8588c2ecf20Sopenharmony_cistatic void intel_spi_fill_partition(struct intel_spi *ispi, 8598c2ecf20Sopenharmony_ci struct mtd_partition *part) 8608c2ecf20Sopenharmony_ci{ 8618c2ecf20Sopenharmony_ci u64 end; 8628c2ecf20Sopenharmony_ci int i; 8638c2ecf20Sopenharmony_ci 8648c2ecf20Sopenharmony_ci memset(part, 0, sizeof(*part)); 8658c2ecf20Sopenharmony_ci 8668c2ecf20Sopenharmony_ci /* Start from the mandatory descriptor region */ 8678c2ecf20Sopenharmony_ci part->size = 4096; 8688c2ecf20Sopenharmony_ci part->name = "BIOS"; 8698c2ecf20Sopenharmony_ci 8708c2ecf20Sopenharmony_ci /* 8718c2ecf20Sopenharmony_ci * Now try to find where this partition ends based on the flash 8728c2ecf20Sopenharmony_ci * region registers. 8738c2ecf20Sopenharmony_ci */ 8748c2ecf20Sopenharmony_ci for (i = 1; i < ispi->nregions; i++) { 8758c2ecf20Sopenharmony_ci u32 region, base, limit; 8768c2ecf20Sopenharmony_ci 8778c2ecf20Sopenharmony_ci region = readl(ispi->base + FREG(i)); 8788c2ecf20Sopenharmony_ci base = region & FREG_BASE_MASK; 8798c2ecf20Sopenharmony_ci limit = (region & FREG_LIMIT_MASK) >> FREG_LIMIT_SHIFT; 8808c2ecf20Sopenharmony_ci 8818c2ecf20Sopenharmony_ci if (base >= limit || limit == 0) 8828c2ecf20Sopenharmony_ci continue; 8838c2ecf20Sopenharmony_ci 8848c2ecf20Sopenharmony_ci /* 8858c2ecf20Sopenharmony_ci * If any of the regions have protection bits set, make the 8868c2ecf20Sopenharmony_ci * whole partition read-only to be on the safe side. 8878c2ecf20Sopenharmony_ci * 8888c2ecf20Sopenharmony_ci * Also if the user did not ask the chip to be writeable 8898c2ecf20Sopenharmony_ci * mask the bit too. 8908c2ecf20Sopenharmony_ci */ 8918c2ecf20Sopenharmony_ci if (!writeable || intel_spi_is_protected(ispi, base, limit)) 8928c2ecf20Sopenharmony_ci part->mask_flags |= MTD_WRITEABLE; 8938c2ecf20Sopenharmony_ci 8948c2ecf20Sopenharmony_ci end = (limit << 12) + 4096; 8958c2ecf20Sopenharmony_ci if (end > part->size) 8968c2ecf20Sopenharmony_ci part->size = end; 8978c2ecf20Sopenharmony_ci } 8988c2ecf20Sopenharmony_ci} 8998c2ecf20Sopenharmony_ci 9008c2ecf20Sopenharmony_cistatic const struct spi_nor_controller_ops intel_spi_controller_ops = { 9018c2ecf20Sopenharmony_ci .read_reg = intel_spi_read_reg, 9028c2ecf20Sopenharmony_ci .write_reg = intel_spi_write_reg, 9038c2ecf20Sopenharmony_ci .read = intel_spi_read, 9048c2ecf20Sopenharmony_ci .write = intel_spi_write, 9058c2ecf20Sopenharmony_ci .erase = intel_spi_erase, 9068c2ecf20Sopenharmony_ci}; 9078c2ecf20Sopenharmony_ci 9088c2ecf20Sopenharmony_cistruct intel_spi *intel_spi_probe(struct device *dev, 9098c2ecf20Sopenharmony_ci struct resource *mem, const struct intel_spi_boardinfo *info) 9108c2ecf20Sopenharmony_ci{ 9118c2ecf20Sopenharmony_ci const struct spi_nor_hwcaps hwcaps = { 9128c2ecf20Sopenharmony_ci .mask = SNOR_HWCAPS_READ | 9138c2ecf20Sopenharmony_ci SNOR_HWCAPS_READ_FAST | 9148c2ecf20Sopenharmony_ci SNOR_HWCAPS_PP, 9158c2ecf20Sopenharmony_ci }; 9168c2ecf20Sopenharmony_ci struct mtd_partition part; 9178c2ecf20Sopenharmony_ci struct intel_spi *ispi; 9188c2ecf20Sopenharmony_ci int ret; 9198c2ecf20Sopenharmony_ci 9208c2ecf20Sopenharmony_ci if (!info || !mem) 9218c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 9228c2ecf20Sopenharmony_ci 9238c2ecf20Sopenharmony_ci ispi = devm_kzalloc(dev, sizeof(*ispi), GFP_KERNEL); 9248c2ecf20Sopenharmony_ci if (!ispi) 9258c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 9268c2ecf20Sopenharmony_ci 9278c2ecf20Sopenharmony_ci ispi->base = devm_ioremap_resource(dev, mem); 9288c2ecf20Sopenharmony_ci if (IS_ERR(ispi->base)) 9298c2ecf20Sopenharmony_ci return ERR_CAST(ispi->base); 9308c2ecf20Sopenharmony_ci 9318c2ecf20Sopenharmony_ci ispi->dev = dev; 9328c2ecf20Sopenharmony_ci ispi->info = info; 9338c2ecf20Sopenharmony_ci 9348c2ecf20Sopenharmony_ci ret = intel_spi_init(ispi); 9358c2ecf20Sopenharmony_ci if (ret) 9368c2ecf20Sopenharmony_ci return ERR_PTR(ret); 9378c2ecf20Sopenharmony_ci 9388c2ecf20Sopenharmony_ci ispi->nor.dev = ispi->dev; 9398c2ecf20Sopenharmony_ci ispi->nor.priv = ispi; 9408c2ecf20Sopenharmony_ci ispi->nor.controller_ops = &intel_spi_controller_ops; 9418c2ecf20Sopenharmony_ci 9428c2ecf20Sopenharmony_ci ret = spi_nor_scan(&ispi->nor, NULL, &hwcaps); 9438c2ecf20Sopenharmony_ci if (ret) { 9448c2ecf20Sopenharmony_ci dev_info(dev, "failed to locate the chip\n"); 9458c2ecf20Sopenharmony_ci return ERR_PTR(ret); 9468c2ecf20Sopenharmony_ci } 9478c2ecf20Sopenharmony_ci 9488c2ecf20Sopenharmony_ci intel_spi_fill_partition(ispi, &part); 9498c2ecf20Sopenharmony_ci 9508c2ecf20Sopenharmony_ci ret = mtd_device_register(&ispi->nor.mtd, &part, 1); 9518c2ecf20Sopenharmony_ci if (ret) 9528c2ecf20Sopenharmony_ci return ERR_PTR(ret); 9538c2ecf20Sopenharmony_ci 9548c2ecf20Sopenharmony_ci return ispi; 9558c2ecf20Sopenharmony_ci} 9568c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(intel_spi_probe); 9578c2ecf20Sopenharmony_ci 9588c2ecf20Sopenharmony_ciint intel_spi_remove(struct intel_spi *ispi) 9598c2ecf20Sopenharmony_ci{ 9608c2ecf20Sopenharmony_ci return mtd_device_unregister(&ispi->nor.mtd); 9618c2ecf20Sopenharmony_ci} 9628c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(intel_spi_remove); 9638c2ecf20Sopenharmony_ci 9648c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Intel PCH/PCU SPI flash core driver"); 9658c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); 9668c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 967