Blame view

drivers/spi/spi-rb4xx.c 5.2 KB
d2912cb15   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-only
05aec3578   Bert Vermeulen   spi: Add SPI driv...
2
3
4
5
6
7
8
9
  /*
   * SPI controller driver for the Mikrotik RB4xx boards
   *
   * Copyright (C) 2010 Gabor Juhos <juhosg@openwrt.org>
   * Copyright (C) 2015 Bert Vermeulen <bert@biot.com>
   *
   * This file was based on the patches for Linux 2.6.27.39 published by
   * MikroTik for their RouterBoard 4xx series devices.
05aec3578   Bert Vermeulen   spi: Add SPI driv...
10
11
12
13
14
15
16
   */
  
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/platform_device.h>
  #include <linux/clk.h>
  #include <linux/spi/spi.h>
9a436c62f   Christopher Hill   spi: rb4xx: updat...
17
  #include <linux/of.h>
05aec3578   Bert Vermeulen   spi: Add SPI driv...
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
  
  #include <asm/mach-ath79/ar71xx_regs.h>
  
  struct rb4xx_spi {
  	void __iomem *base;
  	struct clk *clk;
  };
  
  static inline u32 rb4xx_read(struct rb4xx_spi *rbspi, u32 reg)
  {
  	return __raw_readl(rbspi->base + reg);
  }
  
  static inline void rb4xx_write(struct rb4xx_spi *rbspi, u32 reg, u32 value)
  {
  	__raw_writel(value, rbspi->base + reg);
  }
  
  static inline void do_spi_clk(struct rb4xx_spi *rbspi, u32 spi_ioc, int value)
  {
  	u32 regval;
  
  	regval = spi_ioc;
  	if (value & BIT(0))
  		regval |= AR71XX_SPI_IOC_DO;
  
  	rb4xx_write(rbspi, AR71XX_SPI_REG_IOC, regval);
  	rb4xx_write(rbspi, AR71XX_SPI_REG_IOC, regval | AR71XX_SPI_IOC_CLK);
  }
  
  static void do_spi_byte(struct rb4xx_spi *rbspi, u32 spi_ioc, u8 byte)
  {
  	int i;
  
  	for (i = 7; i >= 0; i--)
  		do_spi_clk(rbspi, spi_ioc, byte >> i);
  }
  
  /* The CS2 pin is used to clock in a second bit per clock cycle. */
  static inline void do_spi_clk_two(struct rb4xx_spi *rbspi, u32 spi_ioc,
  				   u8 value)
  {
  	u32 regval;
  
  	regval = spi_ioc;
  	if (value & BIT(1))
  		regval |= AR71XX_SPI_IOC_DO;
  	if (value & BIT(0))
  		regval |= AR71XX_SPI_IOC_CS2;
  
  	rb4xx_write(rbspi, AR71XX_SPI_REG_IOC, regval);
  	rb4xx_write(rbspi, AR71XX_SPI_REG_IOC, regval | AR71XX_SPI_IOC_CLK);
  }
  
  /* Two bits at a time, msb first */
  static void do_spi_byte_two(struct rb4xx_spi *rbspi, u32 spi_ioc, u8 byte)
  {
  	do_spi_clk_two(rbspi, spi_ioc, byte >> 6);
  	do_spi_clk_two(rbspi, spi_ioc, byte >> 4);
  	do_spi_clk_two(rbspi, spi_ioc, byte >> 2);
  	do_spi_clk_two(rbspi, spi_ioc, byte >> 0);
  }
  
  static void rb4xx_set_cs(struct spi_device *spi, bool enable)
  {
  	struct rb4xx_spi *rbspi = spi_master_get_devdata(spi->master);
  
  	/*
  	 * Setting CS is done along with bitbanging the actual values,
  	 * since it's all on the same hardware register. However the
  	 * CPLD needs CS deselected after every command.
  	 */
4a1ae8be4   Bert Vermeulen   spi: rb4xx: Fix s...
90
  	if (enable)
05aec3578   Bert Vermeulen   spi: Add SPI driv...
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
  		rb4xx_write(rbspi, AR71XX_SPI_REG_IOC,
  			    AR71XX_SPI_IOC_CS0 | AR71XX_SPI_IOC_CS1);
  }
  
  static int rb4xx_transfer_one(struct spi_master *master,
  			      struct spi_device *spi, struct spi_transfer *t)
  {
  	struct rb4xx_spi *rbspi = spi_master_get_devdata(master);
  	int i;
  	u32 spi_ioc;
  	u8 *rx_buf;
  	const u8 *tx_buf;
  
  	/*
  	 * Prime the SPI register with the SPI device selected. The m25p80 boot
  	 * flash and CPLD share the CS0 pin. This works because the CPLD's
  	 * command set was designed to almost not clash with that of the
  	 * boot flash.
  	 */
  	if (spi->chip_select == 2)
  		/* MMC */
  		spi_ioc = AR71XX_SPI_IOC_CS0;
  	else
  		/* Boot flash and CPLD */
  		spi_ioc = AR71XX_SPI_IOC_CS1;
  
  	tx_buf = t->tx_buf;
  	rx_buf = t->rx_buf;
  	for (i = 0; i < t->len; ++i) {
  		if (t->tx_nbits == SPI_NBITS_DUAL)
  			/* CPLD can use two-wire transfers */
  			do_spi_byte_two(rbspi, spi_ioc, tx_buf[i]);
  		else
  			do_spi_byte(rbspi, spi_ioc, tx_buf[i]);
  		if (!rx_buf)
  			continue;
  		rx_buf[i] = rb4xx_read(rbspi, AR71XX_SPI_REG_RDS);
  	}
  	spi_finalize_current_transfer(master);
  
  	return 0;
  }
  
  static int rb4xx_spi_probe(struct platform_device *pdev)
  {
  	struct spi_master *master;
  	struct clk *ahb_clk;
  	struct rb4xx_spi *rbspi;
05aec3578   Bert Vermeulen   spi: Add SPI driv...
139
140
  	int err;
  	void __iomem *spi_base;
7d4c20832   YueHaibing   spi: rb4xx: use d...
141
  	spi_base = devm_platform_ioremap_resource(pdev, 0);
b2b3024ca   Axel Lin   spi: rb4xx: Fix c...
142
  	if (IS_ERR(spi_base))
05aec3578   Bert Vermeulen   spi: Add SPI driv...
143
  		return PTR_ERR(spi_base);
a3e86ed72   Lukas Wunner   spi: rb4xx: Don't...
144
  	master = devm_spi_alloc_master(&pdev->dev, sizeof(*rbspi));
05aec3578   Bert Vermeulen   spi: Add SPI driv...
145
146
147
148
149
150
  	if (!master)
  		return -ENOMEM;
  
  	ahb_clk = devm_clk_get(&pdev->dev, "ahb");
  	if (IS_ERR(ahb_clk))
  		return PTR_ERR(ahb_clk);
9a436c62f   Christopher Hill   spi: rb4xx: updat...
151
  	master->dev.of_node = pdev->dev.of_node;
05aec3578   Bert Vermeulen   spi: Add SPI driv...
152
153
154
  	master->bus_num = 0;
  	master->num_chipselect = 3;
  	master->mode_bits = SPI_TX_DUAL;
bed2e8f4e   Axel Lin   spi: rb4xx: Use S...
155
  	master->bits_per_word_mask = SPI_BPW_MASK(8);
05aec3578   Bert Vermeulen   spi: Add SPI driv...
156
157
158
  	master->flags = SPI_MASTER_MUST_TX;
  	master->transfer_one = rb4xx_transfer_one;
  	master->set_cs = rb4xx_set_cs;
678e5e1e4   Christopher Hill   spi: rb4xx: null ...
159
160
161
162
  	rbspi = spi_master_get_devdata(master);
  	rbspi->base = spi_base;
  	rbspi->clk = ahb_clk;
  	platform_set_drvdata(pdev, rbspi);
05aec3578   Bert Vermeulen   spi: Add SPI driv...
163
164
165
166
167
168
169
170
171
172
  	err = devm_spi_register_master(&pdev->dev, master);
  	if (err) {
  		dev_err(&pdev->dev, "failed to register SPI master
  ");
  		return err;
  	}
  
  	err = clk_prepare_enable(ahb_clk);
  	if (err)
  		return err;
05aec3578   Bert Vermeulen   spi: Add SPI driv...
173
174
175
176
177
178
179
180
181
182
183
184
185
186
  	/* Enable SPI */
  	rb4xx_write(rbspi, AR71XX_SPI_REG_FS, AR71XX_SPI_FS_GPIO);
  
  	return 0;
  }
  
  static int rb4xx_spi_remove(struct platform_device *pdev)
  {
  	struct rb4xx_spi *rbspi = platform_get_drvdata(pdev);
  
  	clk_disable_unprepare(rbspi->clk);
  
  	return 0;
  }
9a436c62f   Christopher Hill   spi: rb4xx: updat...
187
188
189
190
191
  static const struct of_device_id rb4xx_spi_dt_match[] = {
  	{ .compatible = "mikrotik,rb4xx-spi" },
  	{ },
  };
  MODULE_DEVICE_TABLE(of, rb4xx_spi_dt_match);
05aec3578   Bert Vermeulen   spi: Add SPI driv...
192
193
194
195
196
  static struct platform_driver rb4xx_spi_drv = {
  	.probe = rb4xx_spi_probe,
  	.remove = rb4xx_spi_remove,
  	.driver = {
  		.name = "rb4xx-spi",
9a436c62f   Christopher Hill   spi: rb4xx: updat...
197
  		.of_match_table = of_match_ptr(rb4xx_spi_dt_match),
05aec3578   Bert Vermeulen   spi: Add SPI driv...
198
199
200
201
202
203
204
205
206
  	},
  };
  
  module_platform_driver(rb4xx_spi_drv);
  
  MODULE_DESCRIPTION("Mikrotik RB4xx SPI controller driver");
  MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>");
  MODULE_AUTHOR("Bert Vermeulen <bert@biot.com>");
  MODULE_LICENSE("GPL v2");