Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Martin Kurbanov | 1999 | 99.95% | 3 | 75.00% |
Yang Yingliang | 1 | 0.05% | 1 | 25.00% |
Total | 2000 | 4 |
// SPDX-License-Identifier: GPL-2.0 /* * Driver for Amlogic A1 SPI flash controller (SPIFC) * * Copyright (c) 2023, SberDevices. All Rights Reserved. * * Author: Martin Kurbanov <mmkurbanov@sberdevices.ru> */ #include <linux/bitfield.h> #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/of.h> #include <linux/platform_device.h> #include <linux/pm_runtime.h> #include <linux/spi/spi.h> #include <linux/spi/spi-mem.h> #include <linux/types.h> #define SPIFC_A1_AHB_CTRL_REG 0x0 #define SPIFC_A1_AHB_BUS_EN BIT(31) #define SPIFC_A1_USER_CTRL0_REG 0x200 #define SPIFC_A1_USER_REQUEST_ENABLE BIT(31) #define SPIFC_A1_USER_REQUEST_FINISH BIT(30) #define SPIFC_A1_USER_DATA_UPDATED BIT(0) #define SPIFC_A1_USER_CTRL1_REG 0x204 #define SPIFC_A1_USER_CMD_ENABLE BIT(30) #define SPIFC_A1_USER_CMD_MODE GENMASK(29, 28) #define SPIFC_A1_USER_CMD_CODE GENMASK(27, 20) #define SPIFC_A1_USER_ADDR_ENABLE BIT(19) #define SPIFC_A1_USER_ADDR_MODE GENMASK(18, 17) #define SPIFC_A1_USER_ADDR_BYTES GENMASK(16, 15) #define SPIFC_A1_USER_DOUT_ENABLE BIT(14) #define SPIFC_A1_USER_DOUT_MODE GENMASK(11, 10) #define SPIFC_A1_USER_DOUT_BYTES GENMASK(9, 0) #define SPIFC_A1_USER_CTRL2_REG 0x208 #define SPIFC_A1_USER_DUMMY_ENABLE BIT(31) #define SPIFC_A1_USER_DUMMY_MODE GENMASK(30, 29) #define SPIFC_A1_USER_DUMMY_CLK_SYCLES GENMASK(28, 23) #define SPIFC_A1_USER_CTRL3_REG 0x20c #define SPIFC_A1_USER_DIN_ENABLE BIT(31) #define SPIFC_A1_USER_DIN_MODE GENMASK(28, 27) #define SPIFC_A1_USER_DIN_BYTES GENMASK(25, 16) #define SPIFC_A1_USER_ADDR_REG 0x210 #define SPIFC_A1_AHB_REQ_CTRL_REG 0x214 #define SPIFC_A1_AHB_REQ_ENABLE BIT(31) #define SPIFC_A1_ACTIMING0_REG (0x0088 << 2) #define SPIFC_A1_TSLCH GENMASK(31, 30) #define SPIFC_A1_TCLSH GENMASK(29, 28) #define SPIFC_A1_TSHWL GENMASK(20, 16) #define SPIFC_A1_TSHSL2 GENMASK(15, 12) #define SPIFC_A1_TSHSL1 GENMASK(11, 8) #define SPIFC_A1_TWHSL GENMASK(7, 0) #define SPIFC_A1_DBUF_CTRL_REG 0x240 #define SPIFC_A1_DBUF_DIR BIT(31) #define SPIFC_A1_DBUF_AUTO_UPDATE_ADDR BIT(30) #define SPIFC_A1_DBUF_ADDR GENMASK(7, 0) #define SPIFC_A1_DBUF_DATA_REG 0x244 #define SPIFC_A1_USER_DBUF_ADDR_REG 0x248 #define SPIFC_A1_BUFFER_SIZE 512U #define SPIFC_A1_MAX_HZ 200000000 #define SPIFC_A1_MIN_HZ 1000000 #define SPIFC_A1_USER_CMD(op) ( \ SPIFC_A1_USER_CMD_ENABLE | \ FIELD_PREP(SPIFC_A1_USER_CMD_CODE, (op)->cmd.opcode) | \ FIELD_PREP(SPIFC_A1_USER_CMD_MODE, ilog2((op)->cmd.buswidth))) #define SPIFC_A1_USER_ADDR(op) ( \ SPIFC_A1_USER_ADDR_ENABLE | \ FIELD_PREP(SPIFC_A1_USER_ADDR_MODE, ilog2((op)->addr.buswidth)) | \ FIELD_PREP(SPIFC_A1_USER_ADDR_BYTES, (op)->addr.nbytes - 1)) #define SPIFC_A1_USER_DUMMY(op) ( \ SPIFC_A1_USER_DUMMY_ENABLE | \ FIELD_PREP(SPIFC_A1_USER_DUMMY_MODE, ilog2((op)->dummy.buswidth)) | \ FIELD_PREP(SPIFC_A1_USER_DUMMY_CLK_SYCLES, (op)->dummy.nbytes << 3)) #define SPIFC_A1_TSLCH_VAL FIELD_PREP(SPIFC_A1_TSLCH, 1) #define SPIFC_A1_TCLSH_VAL FIELD_PREP(SPIFC_A1_TCLSH, 1) #define SPIFC_A1_TSHWL_VAL FIELD_PREP(SPIFC_A1_TSHWL, 7) #define SPIFC_A1_TSHSL2_VAL FIELD_PREP(SPIFC_A1_TSHSL2, 7) #define SPIFC_A1_TSHSL1_VAL FIELD_PREP(SPIFC_A1_TSHSL1, 7) #define SPIFC_A1_TWHSL_VAL FIELD_PREP(SPIFC_A1_TWHSL, 2) #define SPIFC_A1_ACTIMING0_VAL (SPIFC_A1_TSLCH_VAL | SPIFC_A1_TCLSH_VAL | \ SPIFC_A1_TSHWL_VAL | SPIFC_A1_TSHSL2_VAL | \ SPIFC_A1_TSHSL1_VAL | SPIFC_A1_TWHSL_VAL) struct amlogic_spifc_a1 { struct spi_controller *ctrl; struct clk *clk; struct device *dev; void __iomem *base; u32 curr_speed_hz; }; static int amlogic_spifc_a1_request(struct amlogic_spifc_a1 *spifc, bool read) { u32 mask = SPIFC_A1_USER_REQUEST_FINISH | (read ? SPIFC_A1_USER_DATA_UPDATED : 0); u32 val; writel(SPIFC_A1_USER_REQUEST_ENABLE, spifc->base + SPIFC_A1_USER_CTRL0_REG); return readl_poll_timeout(spifc->base + SPIFC_A1_USER_CTRL0_REG, val, (val & mask) == mask, 0, 200 * USEC_PER_MSEC); } static void amlogic_spifc_a1_drain_buffer(struct amlogic_spifc_a1 *spifc, char *buf, u32 len) { u32 data; const u32 count = len / sizeof(data); const u32 pad = len % sizeof(data); writel(SPIFC_A1_DBUF_AUTO_UPDATE_ADDR, spifc->base + SPIFC_A1_DBUF_CTRL_REG); ioread32_rep(spifc->base + SPIFC_A1_DBUF_DATA_REG, buf, count); if (pad) { data = readl(spifc->base + SPIFC_A1_DBUF_DATA_REG); memcpy(buf + len - pad, &data, pad); } } static void amlogic_spifc_a1_fill_buffer(struct amlogic_spifc_a1 *spifc, const char *buf, u32 len) { u32 data; const u32 count = len / sizeof(data); const u32 pad = len % sizeof(data); writel(SPIFC_A1_DBUF_DIR | SPIFC_A1_DBUF_AUTO_UPDATE_ADDR, spifc->base + SPIFC_A1_DBUF_CTRL_REG); iowrite32_rep(spifc->base + SPIFC_A1_DBUF_DATA_REG, buf, count); if (pad) { memcpy(&data, buf + len - pad, pad); writel(data, spifc->base + SPIFC_A1_DBUF_DATA_REG); } } static void amlogic_spifc_a1_user_init(struct amlogic_spifc_a1 *spifc) { writel(0, spifc->base + SPIFC_A1_USER_CTRL0_REG); writel(0, spifc->base + SPIFC_A1_USER_CTRL1_REG); writel(0, spifc->base + SPIFC_A1_USER_CTRL2_REG); writel(0, spifc->base + SPIFC_A1_USER_CTRL3_REG); } static void amlogic_spifc_a1_set_cmd(struct amlogic_spifc_a1 *spifc, u32 cmd_cfg) { u32 val; val = readl(spifc->base + SPIFC_A1_USER_CTRL1_REG); val &= ~(SPIFC_A1_USER_CMD_MODE | SPIFC_A1_USER_CMD_CODE); val |= cmd_cfg; writel(val, spifc->base + SPIFC_A1_USER_CTRL1_REG); } static void amlogic_spifc_a1_set_addr(struct amlogic_spifc_a1 *spifc, u32 addr, u32 addr_cfg) { u32 val; writel(addr, spifc->base + SPIFC_A1_USER_ADDR_REG); val = readl(spifc->base + SPIFC_A1_USER_CTRL1_REG); val &= ~(SPIFC_A1_USER_ADDR_MODE | SPIFC_A1_USER_ADDR_BYTES); val |= addr_cfg; writel(val, spifc->base + SPIFC_A1_USER_CTRL1_REG); } static void amlogic_spifc_a1_set_dummy(struct amlogic_spifc_a1 *spifc, u32 dummy_cfg) { u32 val = readl(spifc->base + SPIFC_A1_USER_CTRL2_REG); val &= ~(SPIFC_A1_USER_DUMMY_MODE | SPIFC_A1_USER_DUMMY_CLK_SYCLES); val |= dummy_cfg; writel(val, spifc->base + SPIFC_A1_USER_CTRL2_REG); } static int amlogic_spifc_a1_read(struct amlogic_spifc_a1 *spifc, void *buf, u32 size, u32 mode) { u32 val = readl(spifc->base + SPIFC_A1_USER_CTRL3_REG); int ret; val &= ~(SPIFC_A1_USER_DIN_MODE | SPIFC_A1_USER_DIN_BYTES); val |= SPIFC_A1_USER_DIN_ENABLE; val |= FIELD_PREP(SPIFC_A1_USER_DIN_MODE, mode); val |= FIELD_PREP(SPIFC_A1_USER_DIN_BYTES, size); writel(val, spifc->base + SPIFC_A1_USER_CTRL3_REG); ret = amlogic_spifc_a1_request(spifc, true); if (!ret) amlogic_spifc_a1_drain_buffer(spifc, buf, size); return ret; } static int amlogic_spifc_a1_write(struct amlogic_spifc_a1 *spifc, const void *buf, u32 size, u32 mode) { u32 val; amlogic_spifc_a1_fill_buffer(spifc, buf, size); val = readl(spifc->base + SPIFC_A1_USER_CTRL1_REG); val &= ~(SPIFC_A1_USER_DOUT_MODE | SPIFC_A1_USER_DOUT_BYTES); val |= FIELD_PREP(SPIFC_A1_USER_DOUT_MODE, mode); val |= FIELD_PREP(SPIFC_A1_USER_DOUT_BYTES, size); val |= SPIFC_A1_USER_DOUT_ENABLE; writel(val, spifc->base + SPIFC_A1_USER_CTRL1_REG); return amlogic_spifc_a1_request(spifc, false); } static int amlogic_spifc_a1_set_freq(struct amlogic_spifc_a1 *spifc, u32 freq) { int ret; if (freq == spifc->curr_speed_hz) return 0; ret = clk_set_rate(spifc->clk, freq); if (ret) return ret; spifc->curr_speed_hz = freq; return 0; } static int amlogic_spifc_a1_exec_op(struct spi_mem *mem, const struct spi_mem_op *op) { struct amlogic_spifc_a1 *spifc = spi_controller_get_devdata(mem->spi->controller); size_t data_size = op->data.nbytes; int ret; ret = amlogic_spifc_a1_set_freq(spifc, mem->spi->max_speed_hz); if (ret) return ret; amlogic_spifc_a1_user_init(spifc); amlogic_spifc_a1_set_cmd(spifc, SPIFC_A1_USER_CMD(op)); if (op->addr.nbytes) amlogic_spifc_a1_set_addr(spifc, op->addr.val, SPIFC_A1_USER_ADDR(op)); if (op->dummy.nbytes) amlogic_spifc_a1_set_dummy(spifc, SPIFC_A1_USER_DUMMY(op)); if (data_size) { u32 mode = ilog2(op->data.buswidth); writel(0, spifc->base + SPIFC_A1_USER_DBUF_ADDR_REG); if (op->data.dir == SPI_MEM_DATA_IN) ret = amlogic_spifc_a1_read(spifc, op->data.buf.in, data_size, mode); else ret = amlogic_spifc_a1_write(spifc, op->data.buf.out, data_size, mode); } else { ret = amlogic_spifc_a1_request(spifc, false); } return ret; } static int amlogic_spifc_a1_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op) { op->data.nbytes = min(op->data.nbytes, SPIFC_A1_BUFFER_SIZE); return 0; } static void amlogic_spifc_a1_hw_init(struct amlogic_spifc_a1 *spifc) { u32 regv; regv = readl(spifc->base + SPIFC_A1_AHB_REQ_CTRL_REG); regv &= ~(SPIFC_A1_AHB_REQ_ENABLE); writel(regv, spifc->base + SPIFC_A1_AHB_REQ_CTRL_REG); regv = readl(spifc->base + SPIFC_A1_AHB_CTRL_REG); regv &= ~(SPIFC_A1_AHB_BUS_EN); writel(regv, spifc->base + SPIFC_A1_AHB_CTRL_REG); writel(SPIFC_A1_ACTIMING0_VAL, spifc->base + SPIFC_A1_ACTIMING0_REG); writel(0, spifc->base + SPIFC_A1_USER_DBUF_ADDR_REG); } static const struct spi_controller_mem_ops amlogic_spifc_a1_mem_ops = { .exec_op = amlogic_spifc_a1_exec_op, .adjust_op_size = amlogic_spifc_a1_adjust_op_size, }; static int amlogic_spifc_a1_probe(struct platform_device *pdev) { struct spi_controller *ctrl; struct amlogic_spifc_a1 *spifc; int ret; ctrl = devm_spi_alloc_host(&pdev->dev, sizeof(*spifc)); if (!ctrl) return -ENOMEM; spifc = spi_controller_get_devdata(ctrl); platform_set_drvdata(pdev, spifc); spifc->dev = &pdev->dev; spifc->ctrl = ctrl; spifc->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(spifc->base)) return PTR_ERR(spifc->base); spifc->clk = devm_clk_get_enabled(spifc->dev, NULL); if (IS_ERR(spifc->clk)) return dev_err_probe(spifc->dev, PTR_ERR(spifc->clk), "unable to get clock\n"); amlogic_spifc_a1_hw_init(spifc); pm_runtime_set_autosuspend_delay(spifc->dev, 500); pm_runtime_use_autosuspend(spifc->dev); devm_pm_runtime_enable(spifc->dev); ctrl->num_chipselect = 1; ctrl->dev.of_node = pdev->dev.of_node; ctrl->bits_per_word_mask = SPI_BPW_MASK(8); ctrl->auto_runtime_pm = true; ctrl->mem_ops = &amlogic_spifc_a1_mem_ops; ctrl->min_speed_hz = SPIFC_A1_MIN_HZ; ctrl->max_speed_hz = SPIFC_A1_MAX_HZ; ctrl->mode_bits = (SPI_RX_DUAL | SPI_TX_DUAL | SPI_RX_QUAD | SPI_TX_QUAD); ret = devm_spi_register_controller(spifc->dev, ctrl); if (ret) return dev_err_probe(spifc->dev, ret, "failed to register spi controller\n"); return 0; } #ifdef CONFIG_PM_SLEEP static int amlogic_spifc_a1_suspend(struct device *dev) { struct amlogic_spifc_a1 *spifc = dev_get_drvdata(dev); int ret; ret = spi_controller_suspend(spifc->ctrl); if (ret) return ret; if (!pm_runtime_suspended(dev)) clk_disable_unprepare(spifc->clk); return 0; } static int amlogic_spifc_a1_resume(struct device *dev) { struct amlogic_spifc_a1 *spifc = dev_get_drvdata(dev); int ret = 0; if (!pm_runtime_suspended(dev)) { ret = clk_prepare_enable(spifc->clk); if (ret) return ret; } amlogic_spifc_a1_hw_init(spifc); ret = spi_controller_resume(spifc->ctrl); if (ret) clk_disable_unprepare(spifc->clk); return ret; } #endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_PM static int amlogic_spifc_a1_runtime_suspend(struct device *dev) { struct amlogic_spifc_a1 *spifc = dev_get_drvdata(dev); clk_disable_unprepare(spifc->clk); return 0; } static int amlogic_spifc_a1_runtime_resume(struct device *dev) { struct amlogic_spifc_a1 *spifc = dev_get_drvdata(dev); int ret; ret = clk_prepare_enable(spifc->clk); if (!ret) amlogic_spifc_a1_hw_init(spifc); return ret; } #endif /* CONFIG_PM */ static const struct dev_pm_ops amlogic_spifc_a1_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(amlogic_spifc_a1_suspend, amlogic_spifc_a1_resume) SET_RUNTIME_PM_OPS(amlogic_spifc_a1_runtime_suspend, amlogic_spifc_a1_runtime_resume, NULL) }; #ifdef CONFIG_OF static const struct of_device_id amlogic_spifc_a1_dt_match[] = { { .compatible = "amlogic,a1-spifc", }, { }, }; MODULE_DEVICE_TABLE(of, amlogic_spifc_a1_dt_match); #endif /* CONFIG_OF */ static struct platform_driver amlogic_spifc_a1_driver = { .probe = amlogic_spifc_a1_probe, .driver = { .name = "amlogic-spifc-a1", .of_match_table = of_match_ptr(amlogic_spifc_a1_dt_match), .pm = &amlogic_spifc_a1_pm_ops, }, }; module_platform_driver(amlogic_spifc_a1_driver); MODULE_AUTHOR("Martin Kurbanov <mmkurbanov@sberdevices.ru>"); MODULE_DESCRIPTION("Amlogic A1 SPIFC driver"); MODULE_LICENSE("GPL");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1