spi-meson-spifc.c (11226B)
1// SPDX-License-Identifier: GPL-2.0+ 2// 3// Driver for Amlogic Meson SPI flash controller (SPIFC) 4// 5// Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com> 6// 7 8#include <linux/clk.h> 9#include <linux/delay.h> 10#include <linux/device.h> 11#include <linux/io.h> 12#include <linux/kernel.h> 13#include <linux/module.h> 14#include <linux/of.h> 15#include <linux/platform_device.h> 16#include <linux/pm_runtime.h> 17#include <linux/regmap.h> 18#include <linux/spi/spi.h> 19#include <linux/types.h> 20 21/* register map */ 22#define REG_CMD 0x00 23#define REG_ADDR 0x04 24#define REG_CTRL 0x08 25#define REG_CTRL1 0x0c 26#define REG_STATUS 0x10 27#define REG_CTRL2 0x14 28#define REG_CLOCK 0x18 29#define REG_USER 0x1c 30#define REG_USER1 0x20 31#define REG_USER2 0x24 32#define REG_USER3 0x28 33#define REG_USER4 0x2c 34#define REG_SLAVE 0x30 35#define REG_SLAVE1 0x34 36#define REG_SLAVE2 0x38 37#define REG_SLAVE3 0x3c 38#define REG_C0 0x40 39#define REG_B8 0x60 40#define REG_MAX 0x7c 41 42/* register fields */ 43#define CMD_USER BIT(18) 44#define CTRL_ENABLE_AHB BIT(17) 45#define CLOCK_SOURCE BIT(31) 46#define CLOCK_DIV_SHIFT 12 47#define CLOCK_DIV_MASK (0x3f << CLOCK_DIV_SHIFT) 48#define CLOCK_CNT_HIGH_SHIFT 6 49#define CLOCK_CNT_HIGH_MASK (0x3f << CLOCK_CNT_HIGH_SHIFT) 50#define CLOCK_CNT_LOW_SHIFT 0 51#define CLOCK_CNT_LOW_MASK (0x3f << CLOCK_CNT_LOW_SHIFT) 52#define USER_DIN_EN_MS BIT(0) 53#define USER_CMP_MODE BIT(2) 54#define USER_UC_DOUT_SEL BIT(27) 55#define USER_UC_DIN_SEL BIT(28) 56#define USER_UC_MASK ((BIT(5) - 1) << 27) 57#define USER1_BN_UC_DOUT_SHIFT 17 58#define USER1_BN_UC_DOUT_MASK (0xff << 16) 59#define USER1_BN_UC_DIN_SHIFT 8 60#define USER1_BN_UC_DIN_MASK (0xff << 8) 61#define USER4_CS_ACT BIT(30) 62#define SLAVE_TRST_DONE BIT(4) 63#define SLAVE_OP_MODE BIT(30) 64#define SLAVE_SW_RST BIT(31) 65 66#define SPIFC_BUFFER_SIZE 64 67 68/** 69 * struct meson_spifc 70 * @master: the SPI master 71 * @regmap: regmap for device registers 72 * @clk: input clock of the built-in baud rate generator 73 * @dev: the device structure 74 */ 75struct meson_spifc { 76 struct spi_master *master; 77 struct regmap *regmap; 78 struct clk *clk; 79 struct device *dev; 80}; 81 82static const struct regmap_config spifc_regmap_config = { 83 .reg_bits = 32, 84 .val_bits = 32, 85 .reg_stride = 4, 86 .max_register = REG_MAX, 87}; 88 89/** 90 * meson_spifc_wait_ready() - wait for the current operation to terminate 91 * @spifc: the Meson SPI device 92 * Return: 0 on success, a negative value on error 93 */ 94static int meson_spifc_wait_ready(struct meson_spifc *spifc) 95{ 96 unsigned long deadline = jiffies + msecs_to_jiffies(5); 97 u32 data; 98 99 do { 100 regmap_read(spifc->regmap, REG_SLAVE, &data); 101 if (data & SLAVE_TRST_DONE) 102 return 0; 103 cond_resched(); 104 } while (!time_after(jiffies, deadline)); 105 106 return -ETIMEDOUT; 107} 108 109/** 110 * meson_spifc_drain_buffer() - copy data from device buffer to memory 111 * @spifc: the Meson SPI device 112 * @buf: the destination buffer 113 * @len: number of bytes to copy 114 */ 115static void meson_spifc_drain_buffer(struct meson_spifc *spifc, u8 *buf, 116 int len) 117{ 118 u32 data; 119 int i = 0; 120 121 while (i < len) { 122 regmap_read(spifc->regmap, REG_C0 + i, &data); 123 124 if (len - i >= 4) { 125 *((u32 *)buf) = data; 126 buf += 4; 127 } else { 128 memcpy(buf, &data, len - i); 129 break; 130 } 131 i += 4; 132 } 133} 134 135/** 136 * meson_spifc_fill_buffer() - copy data from memory to device buffer 137 * @spifc: the Meson SPI device 138 * @buf: the source buffer 139 * @len: number of bytes to copy 140 */ 141static void meson_spifc_fill_buffer(struct meson_spifc *spifc, const u8 *buf, 142 int len) 143{ 144 u32 data; 145 int i = 0; 146 147 while (i < len) { 148 if (len - i >= 4) 149 data = *(u32 *)buf; 150 else 151 memcpy(&data, buf, len - i); 152 153 regmap_write(spifc->regmap, REG_C0 + i, data); 154 155 buf += 4; 156 i += 4; 157 } 158} 159 160/** 161 * meson_spifc_setup_speed() - program the clock divider 162 * @spifc: the Meson SPI device 163 * @speed: desired speed in Hz 164 */ 165static void meson_spifc_setup_speed(struct meson_spifc *spifc, u32 speed) 166{ 167 unsigned long parent, value; 168 int n; 169 170 parent = clk_get_rate(spifc->clk); 171 n = max_t(int, parent / speed - 1, 1); 172 173 dev_dbg(spifc->dev, "parent %lu, speed %u, n %d\n", parent, 174 speed, n); 175 176 value = (n << CLOCK_DIV_SHIFT) & CLOCK_DIV_MASK; 177 value |= (n << CLOCK_CNT_LOW_SHIFT) & CLOCK_CNT_LOW_MASK; 178 value |= (((n + 1) / 2 - 1) << CLOCK_CNT_HIGH_SHIFT) & 179 CLOCK_CNT_HIGH_MASK; 180 181 regmap_write(spifc->regmap, REG_CLOCK, value); 182} 183 184/** 185 * meson_spifc_txrx() - transfer a chunk of data 186 * @spifc: the Meson SPI device 187 * @xfer: the current SPI transfer 188 * @offset: offset of the data to transfer 189 * @len: length of the data to transfer 190 * @last_xfer: whether this is the last transfer of the message 191 * @last_chunk: whether this is the last chunk of the transfer 192 * Return: 0 on success, a negative value on error 193 */ 194static int meson_spifc_txrx(struct meson_spifc *spifc, 195 struct spi_transfer *xfer, 196 int offset, int len, bool last_xfer, 197 bool last_chunk) 198{ 199 bool keep_cs = true; 200 int ret; 201 202 if (xfer->tx_buf) 203 meson_spifc_fill_buffer(spifc, xfer->tx_buf + offset, len); 204 205 /* enable DOUT stage */ 206 regmap_update_bits(spifc->regmap, REG_USER, USER_UC_MASK, 207 USER_UC_DOUT_SEL); 208 regmap_write(spifc->regmap, REG_USER1, 209 (8 * len - 1) << USER1_BN_UC_DOUT_SHIFT); 210 211 /* enable data input during DOUT */ 212 regmap_update_bits(spifc->regmap, REG_USER, USER_DIN_EN_MS, 213 USER_DIN_EN_MS); 214 215 if (last_chunk) { 216 if (last_xfer) 217 keep_cs = xfer->cs_change; 218 else 219 keep_cs = !xfer->cs_change; 220 } 221 222 regmap_update_bits(spifc->regmap, REG_USER4, USER4_CS_ACT, 223 keep_cs ? USER4_CS_ACT : 0); 224 225 /* clear transition done bit */ 226 regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_TRST_DONE, 0); 227 /* start transfer */ 228 regmap_update_bits(spifc->regmap, REG_CMD, CMD_USER, CMD_USER); 229 230 ret = meson_spifc_wait_ready(spifc); 231 232 if (!ret && xfer->rx_buf) 233 meson_spifc_drain_buffer(spifc, xfer->rx_buf + offset, len); 234 235 return ret; 236} 237 238/** 239 * meson_spifc_transfer_one() - perform a single transfer 240 * @master: the SPI master 241 * @spi: the SPI device 242 * @xfer: the current SPI transfer 243 * Return: 0 on success, a negative value on error 244 */ 245static int meson_spifc_transfer_one(struct spi_master *master, 246 struct spi_device *spi, 247 struct spi_transfer *xfer) 248{ 249 struct meson_spifc *spifc = spi_master_get_devdata(master); 250 int len, done = 0, ret = 0; 251 252 meson_spifc_setup_speed(spifc, xfer->speed_hz); 253 254 regmap_update_bits(spifc->regmap, REG_CTRL, CTRL_ENABLE_AHB, 0); 255 256 while (done < xfer->len && !ret) { 257 len = min_t(int, xfer->len - done, SPIFC_BUFFER_SIZE); 258 ret = meson_spifc_txrx(spifc, xfer, done, len, 259 spi_transfer_is_last(master, xfer), 260 done + len >= xfer->len); 261 done += len; 262 } 263 264 regmap_update_bits(spifc->regmap, REG_CTRL, CTRL_ENABLE_AHB, 265 CTRL_ENABLE_AHB); 266 267 return ret; 268} 269 270/** 271 * meson_spifc_hw_init() - reset and initialize the SPI controller 272 * @spifc: the Meson SPI device 273 */ 274static void meson_spifc_hw_init(struct meson_spifc *spifc) 275{ 276 /* reset device */ 277 regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_SW_RST, 278 SLAVE_SW_RST); 279 /* disable compatible mode */ 280 regmap_update_bits(spifc->regmap, REG_USER, USER_CMP_MODE, 0); 281 /* set master mode */ 282 regmap_update_bits(spifc->regmap, REG_SLAVE, SLAVE_OP_MODE, 0); 283} 284 285static int meson_spifc_probe(struct platform_device *pdev) 286{ 287 struct spi_master *master; 288 struct meson_spifc *spifc; 289 void __iomem *base; 290 unsigned int rate; 291 int ret = 0; 292 293 master = spi_alloc_master(&pdev->dev, sizeof(struct meson_spifc)); 294 if (!master) 295 return -ENOMEM; 296 297 platform_set_drvdata(pdev, master); 298 299 spifc = spi_master_get_devdata(master); 300 spifc->dev = &pdev->dev; 301 302 base = devm_platform_ioremap_resource(pdev, 0); 303 if (IS_ERR(base)) { 304 ret = PTR_ERR(base); 305 goto out_err; 306 } 307 308 spifc->regmap = devm_regmap_init_mmio(spifc->dev, base, 309 &spifc_regmap_config); 310 if (IS_ERR(spifc->regmap)) { 311 ret = PTR_ERR(spifc->regmap); 312 goto out_err; 313 } 314 315 spifc->clk = devm_clk_get(spifc->dev, NULL); 316 if (IS_ERR(spifc->clk)) { 317 dev_err(spifc->dev, "missing clock\n"); 318 ret = PTR_ERR(spifc->clk); 319 goto out_err; 320 } 321 322 ret = clk_prepare_enable(spifc->clk); 323 if (ret) { 324 dev_err(spifc->dev, "can't prepare clock\n"); 325 goto out_err; 326 } 327 328 rate = clk_get_rate(spifc->clk); 329 330 master->num_chipselect = 1; 331 master->dev.of_node = pdev->dev.of_node; 332 master->bits_per_word_mask = SPI_BPW_MASK(8); 333 master->auto_runtime_pm = true; 334 master->transfer_one = meson_spifc_transfer_one; 335 master->min_speed_hz = rate >> 6; 336 master->max_speed_hz = rate >> 1; 337 338 meson_spifc_hw_init(spifc); 339 340 pm_runtime_set_active(spifc->dev); 341 pm_runtime_enable(spifc->dev); 342 343 ret = devm_spi_register_master(spifc->dev, master); 344 if (ret) { 345 dev_err(spifc->dev, "failed to register spi master\n"); 346 goto out_clk; 347 } 348 349 return 0; 350out_clk: 351 clk_disable_unprepare(spifc->clk); 352 pm_runtime_disable(spifc->dev); 353out_err: 354 spi_master_put(master); 355 return ret; 356} 357 358static int meson_spifc_remove(struct platform_device *pdev) 359{ 360 struct spi_master *master = platform_get_drvdata(pdev); 361 struct meson_spifc *spifc = spi_master_get_devdata(master); 362 363 pm_runtime_get_sync(&pdev->dev); 364 clk_disable_unprepare(spifc->clk); 365 pm_runtime_disable(&pdev->dev); 366 367 return 0; 368} 369 370#ifdef CONFIG_PM_SLEEP 371static int meson_spifc_suspend(struct device *dev) 372{ 373 struct spi_master *master = dev_get_drvdata(dev); 374 struct meson_spifc *spifc = spi_master_get_devdata(master); 375 int ret; 376 377 ret = spi_master_suspend(master); 378 if (ret) 379 return ret; 380 381 if (!pm_runtime_suspended(dev)) 382 clk_disable_unprepare(spifc->clk); 383 384 return 0; 385} 386 387static int meson_spifc_resume(struct device *dev) 388{ 389 struct spi_master *master = dev_get_drvdata(dev); 390 struct meson_spifc *spifc = spi_master_get_devdata(master); 391 int ret; 392 393 if (!pm_runtime_suspended(dev)) { 394 ret = clk_prepare_enable(spifc->clk); 395 if (ret) 396 return ret; 397 } 398 399 meson_spifc_hw_init(spifc); 400 401 ret = spi_master_resume(master); 402 if (ret) 403 clk_disable_unprepare(spifc->clk); 404 405 return ret; 406} 407#endif /* CONFIG_PM_SLEEP */ 408 409#ifdef CONFIG_PM 410static int meson_spifc_runtime_suspend(struct device *dev) 411{ 412 struct spi_master *master = dev_get_drvdata(dev); 413 struct meson_spifc *spifc = spi_master_get_devdata(master); 414 415 clk_disable_unprepare(spifc->clk); 416 417 return 0; 418} 419 420static int meson_spifc_runtime_resume(struct device *dev) 421{ 422 struct spi_master *master = dev_get_drvdata(dev); 423 struct meson_spifc *spifc = spi_master_get_devdata(master); 424 425 return clk_prepare_enable(spifc->clk); 426} 427#endif /* CONFIG_PM */ 428 429static const struct dev_pm_ops meson_spifc_pm_ops = { 430 SET_SYSTEM_SLEEP_PM_OPS(meson_spifc_suspend, meson_spifc_resume) 431 SET_RUNTIME_PM_OPS(meson_spifc_runtime_suspend, 432 meson_spifc_runtime_resume, 433 NULL) 434}; 435 436static const struct of_device_id meson_spifc_dt_match[] = { 437 { .compatible = "amlogic,meson6-spifc", }, 438 { .compatible = "amlogic,meson-gxbb-spifc", }, 439 { }, 440}; 441MODULE_DEVICE_TABLE(of, meson_spifc_dt_match); 442 443static struct platform_driver meson_spifc_driver = { 444 .probe = meson_spifc_probe, 445 .remove = meson_spifc_remove, 446 .driver = { 447 .name = "meson-spifc", 448 .of_match_table = of_match_ptr(meson_spifc_dt_match), 449 .pm = &meson_spifc_pm_ops, 450 }, 451}; 452 453module_platform_driver(meson_spifc_driver); 454 455MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>"); 456MODULE_DESCRIPTION("Amlogic Meson SPIFC driver"); 457MODULE_LICENSE("GPL v2");