xref: /linux/drivers/spi/spidev.c (revision 7ebdfaa52d15b947503f76474477f92854796d96)
1814a8d50SAndrea Paterniani /*
2ca632f55SGrant Likely  * Simple synchronous userspace interface to SPI devices
3814a8d50SAndrea Paterniani  *
4814a8d50SAndrea Paterniani  * Copyright (C) 2006 SWAPP
5814a8d50SAndrea Paterniani  *	Andrea Paterniani <a.paterniani@swapp-eng.it>
6814a8d50SAndrea Paterniani  * Copyright (C) 2007 David Brownell (simplification, cleanup)
7814a8d50SAndrea Paterniani  *
8814a8d50SAndrea Paterniani  * This program is free software; you can redistribute it and/or modify
9814a8d50SAndrea Paterniani  * it under the terms of the GNU General Public License as published by
10814a8d50SAndrea Paterniani  * the Free Software Foundation; either version 2 of the License, or
11814a8d50SAndrea Paterniani  * (at your option) any later version.
12814a8d50SAndrea Paterniani  *
13814a8d50SAndrea Paterniani  * This program is distributed in the hope that it will be useful,
14814a8d50SAndrea Paterniani  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15814a8d50SAndrea Paterniani  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16814a8d50SAndrea Paterniani  * GNU General Public License for more details.
17814a8d50SAndrea Paterniani  *
18814a8d50SAndrea Paterniani  * You should have received a copy of the GNU General Public License
19814a8d50SAndrea Paterniani  * along with this program; if not, write to the Free Software
20814a8d50SAndrea Paterniani  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21814a8d50SAndrea Paterniani  */
22814a8d50SAndrea Paterniani 
23814a8d50SAndrea Paterniani #include <linux/init.h>
24814a8d50SAndrea Paterniani #include <linux/module.h>
25814a8d50SAndrea Paterniani #include <linux/ioctl.h>
26814a8d50SAndrea Paterniani #include <linux/fs.h>
27814a8d50SAndrea Paterniani #include <linux/device.h>
28b2c8daddSDavid Brownell #include <linux/err.h>
29814a8d50SAndrea Paterniani #include <linux/list.h>
30814a8d50SAndrea Paterniani #include <linux/errno.h>
31814a8d50SAndrea Paterniani #include <linux/mutex.h>
32814a8d50SAndrea Paterniani #include <linux/slab.h>
337d48ec36SBernhard Walle #include <linux/compat.h>
34880cfd43SMaxime Ripard #include <linux/of.h>
35880cfd43SMaxime Ripard #include <linux/of_device.h>
36814a8d50SAndrea Paterniani 
37814a8d50SAndrea Paterniani #include <linux/spi/spi.h>
38814a8d50SAndrea Paterniani #include <linux/spi/spidev.h>
39814a8d50SAndrea Paterniani 
40*95c63cfbSJingoo Han #include <linux/uaccess.h>
41814a8d50SAndrea Paterniani 
42814a8d50SAndrea Paterniani 
43814a8d50SAndrea Paterniani /*
44b595076aSUwe Kleine-König  * This supports access to SPI devices using normal userspace I/O calls.
45814a8d50SAndrea Paterniani  * Note that while traditional UNIX/POSIX I/O semantics are half duplex,
46814a8d50SAndrea Paterniani  * and often mask message boundaries, full SPI support requires full duplex
47137f1188SThadeu Lima de Souza Cascardo  * transfers.  There are several kinds of internal message boundaries to
48814a8d50SAndrea Paterniani  * handle chipselect management and other protocol options.
49814a8d50SAndrea Paterniani  *
50814a8d50SAndrea Paterniani  * SPI has a character major number assigned.  We allocate minor numbers
51814a8d50SAndrea Paterniani  * dynamically using a bitmask.  You must use hotplug tools, such as udev
52814a8d50SAndrea Paterniani  * (or mdev with busybox) to create and destroy the /dev/spidevB.C device
53814a8d50SAndrea Paterniani  * nodes, since there is no fixed association of minor numbers with any
54814a8d50SAndrea Paterniani  * particular SPI bus or device.
55814a8d50SAndrea Paterniani  */
56814a8d50SAndrea Paterniani #define SPIDEV_MAJOR			153	/* assigned */
57814a8d50SAndrea Paterniani #define N_SPI_MINORS			32	/* ... up to 256 */
58814a8d50SAndrea Paterniani 
598ae1c924SThadeu Lima de Souza Cascardo static DECLARE_BITMAP(minors, N_SPI_MINORS);
60814a8d50SAndrea Paterniani 
61814a8d50SAndrea Paterniani 
626f166e38SAnton Vorontsov /* Bit masks for spi_device.mode management.  Note that incorrect
63b55f627fSDavid Brownell  * settings for some settings can cause *lots* of trouble for other
64b55f627fSDavid Brownell  * devices on a shared bus:
656f166e38SAnton Vorontsov  *
66b55f627fSDavid Brownell  *  - CS_HIGH ... this device will be active when it shouldn't be
67b55f627fSDavid Brownell  *  - 3WIRE ... when active, it won't behave as it should
68b55f627fSDavid Brownell  *  - NO_CS ... there will be no explicit message boundaries; this
69b55f627fSDavid Brownell  *	is completely incompatible with the shared bus model
70b55f627fSDavid Brownell  *  - READY ... transfers may proceed when they shouldn't.
71b55f627fSDavid Brownell  *
72b55f627fSDavid Brownell  * REVISIT should changing those flags be privileged?
736f166e38SAnton Vorontsov  */
746f166e38SAnton Vorontsov #define SPI_MODE_MASK		(SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \
75b55f627fSDavid Brownell 				| SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \
76dc64d39bSGeert Uytterhoeven 				| SPI_NO_CS | SPI_READY | SPI_TX_DUAL \
77dc64d39bSGeert Uytterhoeven 				| SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD)
78814a8d50SAndrea Paterniani 
79814a8d50SAndrea Paterniani struct spidev_data {
80b2c8daddSDavid Brownell 	dev_t			devt;
8125d5cb4bSDavid Brownell 	spinlock_t		spi_lock;
82814a8d50SAndrea Paterniani 	struct spi_device	*spi;
83814a8d50SAndrea Paterniani 	struct list_head	device_entry;
84814a8d50SAndrea Paterniani 
85865f6d19SRay Jui 	/* TX/RX buffers are NULL unless this device is open (users > 0) */
86814a8d50SAndrea Paterniani 	struct mutex		buf_lock;
87814a8d50SAndrea Paterniani 	unsigned		users;
88865f6d19SRay Jui 	u8			*tx_buffer;
89865f6d19SRay Jui 	u8			*rx_buffer;
9091690516SMark Brown 	u32			speed_hz;
91814a8d50SAndrea Paterniani };
92814a8d50SAndrea Paterniani 
93814a8d50SAndrea Paterniani static LIST_HEAD(device_list);
94814a8d50SAndrea Paterniani static DEFINE_MUTEX(device_list_lock);
95814a8d50SAndrea Paterniani 
96814a8d50SAndrea Paterniani static unsigned bufsiz = 4096;
97814a8d50SAndrea Paterniani module_param(bufsiz, uint, S_IRUGO);
98814a8d50SAndrea Paterniani MODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message");
99814a8d50SAndrea Paterniani 
100814a8d50SAndrea Paterniani /*-------------------------------------------------------------------------*/
101814a8d50SAndrea Paterniani 
10225d5cb4bSDavid Brownell /*
10325d5cb4bSDavid Brownell  * We can't use the standard synchronous wrappers for file I/O; we
10425d5cb4bSDavid Brownell  * need to protect against async removal of the underlying spi_device.
10525d5cb4bSDavid Brownell  */
10625d5cb4bSDavid Brownell static void spidev_complete(void *arg)
10725d5cb4bSDavid Brownell {
10825d5cb4bSDavid Brownell 	complete(arg);
10925d5cb4bSDavid Brownell }
11025d5cb4bSDavid Brownell 
11125d5cb4bSDavid Brownell static ssize_t
11225d5cb4bSDavid Brownell spidev_sync(struct spidev_data *spidev, struct spi_message *message)
11325d5cb4bSDavid Brownell {
11425d5cb4bSDavid Brownell 	DECLARE_COMPLETION_ONSTACK(done);
11525d5cb4bSDavid Brownell 	int status;
11625d5cb4bSDavid Brownell 
11725d5cb4bSDavid Brownell 	message->complete = spidev_complete;
11825d5cb4bSDavid Brownell 	message->context = &done;
11925d5cb4bSDavid Brownell 
12025d5cb4bSDavid Brownell 	spin_lock_irq(&spidev->spi_lock);
12125d5cb4bSDavid Brownell 	if (spidev->spi == NULL)
12225d5cb4bSDavid Brownell 		status = -ESHUTDOWN;
12325d5cb4bSDavid Brownell 	else
12425d5cb4bSDavid Brownell 		status = spi_async(spidev->spi, message);
12525d5cb4bSDavid Brownell 	spin_unlock_irq(&spidev->spi_lock);
12625d5cb4bSDavid Brownell 
12725d5cb4bSDavid Brownell 	if (status == 0) {
12825d5cb4bSDavid Brownell 		wait_for_completion(&done);
12925d5cb4bSDavid Brownell 		status = message->status;
13025d5cb4bSDavid Brownell 		if (status == 0)
13125d5cb4bSDavid Brownell 			status = message->actual_length;
13225d5cb4bSDavid Brownell 	}
13325d5cb4bSDavid Brownell 	return status;
13425d5cb4bSDavid Brownell }
13525d5cb4bSDavid Brownell 
13625d5cb4bSDavid Brownell static inline ssize_t
13725d5cb4bSDavid Brownell spidev_sync_write(struct spidev_data *spidev, size_t len)
13825d5cb4bSDavid Brownell {
13925d5cb4bSDavid Brownell 	struct spi_transfer	t = {
140865f6d19SRay Jui 			.tx_buf		= spidev->tx_buffer,
14125d5cb4bSDavid Brownell 			.len		= len,
14291690516SMark Brown 			.speed_hz	= spidev->speed_hz,
14325d5cb4bSDavid Brownell 		};
14425d5cb4bSDavid Brownell 	struct spi_message	m;
14525d5cb4bSDavid Brownell 
14625d5cb4bSDavid Brownell 	spi_message_init(&m);
14725d5cb4bSDavid Brownell 	spi_message_add_tail(&t, &m);
14825d5cb4bSDavid Brownell 	return spidev_sync(spidev, &m);
14925d5cb4bSDavid Brownell }
15025d5cb4bSDavid Brownell 
15125d5cb4bSDavid Brownell static inline ssize_t
15225d5cb4bSDavid Brownell spidev_sync_read(struct spidev_data *spidev, size_t len)
15325d5cb4bSDavid Brownell {
15425d5cb4bSDavid Brownell 	struct spi_transfer	t = {
155865f6d19SRay Jui 			.rx_buf		= spidev->rx_buffer,
15625d5cb4bSDavid Brownell 			.len		= len,
15791690516SMark Brown 			.speed_hz	= spidev->speed_hz,
15825d5cb4bSDavid Brownell 		};
15925d5cb4bSDavid Brownell 	struct spi_message	m;
16025d5cb4bSDavid Brownell 
16125d5cb4bSDavid Brownell 	spi_message_init(&m);
16225d5cb4bSDavid Brownell 	spi_message_add_tail(&t, &m);
16325d5cb4bSDavid Brownell 	return spidev_sync(spidev, &m);
16425d5cb4bSDavid Brownell }
16525d5cb4bSDavid Brownell 
16625d5cb4bSDavid Brownell /*-------------------------------------------------------------------------*/
16725d5cb4bSDavid Brownell 
168814a8d50SAndrea Paterniani /* Read-only message with current device setup */
169814a8d50SAndrea Paterniani static ssize_t
170814a8d50SAndrea Paterniani spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
171814a8d50SAndrea Paterniani {
172814a8d50SAndrea Paterniani 	struct spidev_data	*spidev;
173814a8d50SAndrea Paterniani 	ssize_t			status = 0;
174814a8d50SAndrea Paterniani 
175814a8d50SAndrea Paterniani 	/* chipselect only toggles at start or end of operation */
176814a8d50SAndrea Paterniani 	if (count > bufsiz)
177814a8d50SAndrea Paterniani 		return -EMSGSIZE;
178814a8d50SAndrea Paterniani 
179814a8d50SAndrea Paterniani 	spidev = filp->private_data;
180814a8d50SAndrea Paterniani 
181814a8d50SAndrea Paterniani 	mutex_lock(&spidev->buf_lock);
18225d5cb4bSDavid Brownell 	status = spidev_sync_read(spidev, count);
1834b1295b0SSebastian Siewior 	if (status > 0) {
184814a8d50SAndrea Paterniani 		unsigned long	missing;
185814a8d50SAndrea Paterniani 
186865f6d19SRay Jui 		missing = copy_to_user(buf, spidev->rx_buffer, status);
1874b1295b0SSebastian Siewior 		if (missing == status)
188814a8d50SAndrea Paterniani 			status = -EFAULT;
189814a8d50SAndrea Paterniani 		else
1904b1295b0SSebastian Siewior 			status = status - missing;
191814a8d50SAndrea Paterniani 	}
192814a8d50SAndrea Paterniani 	mutex_unlock(&spidev->buf_lock);
193814a8d50SAndrea Paterniani 
194814a8d50SAndrea Paterniani 	return status;
195814a8d50SAndrea Paterniani }
196814a8d50SAndrea Paterniani 
197814a8d50SAndrea Paterniani /* Write-only message with current device setup */
198814a8d50SAndrea Paterniani static ssize_t
199814a8d50SAndrea Paterniani spidev_write(struct file *filp, const char __user *buf,
200814a8d50SAndrea Paterniani 		size_t count, loff_t *f_pos)
201814a8d50SAndrea Paterniani {
202814a8d50SAndrea Paterniani 	struct spidev_data	*spidev;
203814a8d50SAndrea Paterniani 	ssize_t			status = 0;
204814a8d50SAndrea Paterniani 	unsigned long		missing;
205814a8d50SAndrea Paterniani 
206814a8d50SAndrea Paterniani 	/* chipselect only toggles at start or end of operation */
207814a8d50SAndrea Paterniani 	if (count > bufsiz)
208814a8d50SAndrea Paterniani 		return -EMSGSIZE;
209814a8d50SAndrea Paterniani 
210814a8d50SAndrea Paterniani 	spidev = filp->private_data;
211814a8d50SAndrea Paterniani 
212814a8d50SAndrea Paterniani 	mutex_lock(&spidev->buf_lock);
213865f6d19SRay Jui 	missing = copy_from_user(spidev->tx_buffer, buf, count);
214*95c63cfbSJingoo Han 	if (missing == 0)
21525d5cb4bSDavid Brownell 		status = spidev_sync_write(spidev, count);
216*95c63cfbSJingoo Han 	else
217814a8d50SAndrea Paterniani 		status = -EFAULT;
218814a8d50SAndrea Paterniani 	mutex_unlock(&spidev->buf_lock);
219814a8d50SAndrea Paterniani 
220814a8d50SAndrea Paterniani 	return status;
221814a8d50SAndrea Paterniani }
222814a8d50SAndrea Paterniani 
223814a8d50SAndrea Paterniani static int spidev_message(struct spidev_data *spidev,
224814a8d50SAndrea Paterniani 		struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
225814a8d50SAndrea Paterniani {
226814a8d50SAndrea Paterniani 	struct spi_message	msg;
227814a8d50SAndrea Paterniani 	struct spi_transfer	*k_xfers;
228814a8d50SAndrea Paterniani 	struct spi_transfer	*k_tmp;
229814a8d50SAndrea Paterniani 	struct spi_ioc_transfer *u_tmp;
230814a8d50SAndrea Paterniani 	unsigned		n, total;
231865f6d19SRay Jui 	u8			*tx_buf, *rx_buf;
232814a8d50SAndrea Paterniani 	int			status = -EFAULT;
233814a8d50SAndrea Paterniani 
234814a8d50SAndrea Paterniani 	spi_message_init(&msg);
235814a8d50SAndrea Paterniani 	k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
236814a8d50SAndrea Paterniani 	if (k_xfers == NULL)
237814a8d50SAndrea Paterniani 		return -ENOMEM;
238814a8d50SAndrea Paterniani 
239814a8d50SAndrea Paterniani 	/* Construct spi_message, copying any tx data to bounce buffer.
240814a8d50SAndrea Paterniani 	 * We walk the array of user-provided transfers, using each one
241814a8d50SAndrea Paterniani 	 * to initialize a kernel version of the same transfer.
242814a8d50SAndrea Paterniani 	 */
243865f6d19SRay Jui 	tx_buf = spidev->tx_buffer;
244865f6d19SRay Jui 	rx_buf = spidev->rx_buffer;
245814a8d50SAndrea Paterniani 	total = 0;
246814a8d50SAndrea Paterniani 	for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
247814a8d50SAndrea Paterniani 			n;
248814a8d50SAndrea Paterniani 			n--, k_tmp++, u_tmp++) {
249814a8d50SAndrea Paterniani 		k_tmp->len = u_tmp->len;
250814a8d50SAndrea Paterniani 
251da90fa8fSDomen Puncer 		total += k_tmp->len;
252da90fa8fSDomen Puncer 		if (total > bufsiz) {
253da90fa8fSDomen Puncer 			status = -EMSGSIZE;
254da90fa8fSDomen Puncer 			goto done;
255da90fa8fSDomen Puncer 		}
256da90fa8fSDomen Puncer 
257814a8d50SAndrea Paterniani 		if (u_tmp->rx_buf) {
258865f6d19SRay Jui 			k_tmp->rx_buf = rx_buf;
25996ddbf50SDavid Brownell 			if (!access_ok(VERIFY_WRITE, (u8 __user *)
260142956afSAl Viro 						(uintptr_t) u_tmp->rx_buf,
26196ddbf50SDavid Brownell 						u_tmp->len))
262814a8d50SAndrea Paterniani 				goto done;
263814a8d50SAndrea Paterniani 		}
264814a8d50SAndrea Paterniani 		if (u_tmp->tx_buf) {
265865f6d19SRay Jui 			k_tmp->tx_buf = tx_buf;
266865f6d19SRay Jui 			if (copy_from_user(tx_buf, (const u8 __user *)
267142956afSAl Viro 						(uintptr_t) u_tmp->tx_buf,
268814a8d50SAndrea Paterniani 					u_tmp->len))
269814a8d50SAndrea Paterniani 				goto done;
270814a8d50SAndrea Paterniani 		}
271865f6d19SRay Jui 		tx_buf += k_tmp->len;
272865f6d19SRay Jui 		rx_buf += k_tmp->len;
273814a8d50SAndrea Paterniani 
274814a8d50SAndrea Paterniani 		k_tmp->cs_change = !!u_tmp->cs_change;
275dc64d39bSGeert Uytterhoeven 		k_tmp->tx_nbits = u_tmp->tx_nbits;
276dc64d39bSGeert Uytterhoeven 		k_tmp->rx_nbits = u_tmp->rx_nbits;
277814a8d50SAndrea Paterniani 		k_tmp->bits_per_word = u_tmp->bits_per_word;
278814a8d50SAndrea Paterniani 		k_tmp->delay_usecs = u_tmp->delay_usecs;
279814a8d50SAndrea Paterniani 		k_tmp->speed_hz = u_tmp->speed_hz;
28091690516SMark Brown 		if (!k_tmp->speed_hz)
28191690516SMark Brown 			k_tmp->speed_hz = spidev->speed_hz;
282814a8d50SAndrea Paterniani #ifdef VERBOSE
28341df70d9SFlorian Fainelli 		dev_dbg(&spidev->spi->dev,
284814a8d50SAndrea Paterniani 			"  xfer len %zd %s%s%s%dbits %u usec %uHz\n",
285814a8d50SAndrea Paterniani 			u_tmp->len,
286814a8d50SAndrea Paterniani 			u_tmp->rx_buf ? "rx " : "",
287814a8d50SAndrea Paterniani 			u_tmp->tx_buf ? "tx " : "",
288814a8d50SAndrea Paterniani 			u_tmp->cs_change ? "cs " : "",
28941df70d9SFlorian Fainelli 			u_tmp->bits_per_word ? : spidev->spi->bits_per_word,
290814a8d50SAndrea Paterniani 			u_tmp->delay_usecs,
29141df70d9SFlorian Fainelli 			u_tmp->speed_hz ? : spidev->spi->max_speed_hz);
292814a8d50SAndrea Paterniani #endif
293814a8d50SAndrea Paterniani 		spi_message_add_tail(k_tmp, &msg);
294814a8d50SAndrea Paterniani 	}
295814a8d50SAndrea Paterniani 
29625d5cb4bSDavid Brownell 	status = spidev_sync(spidev, &msg);
297814a8d50SAndrea Paterniani 	if (status < 0)
298814a8d50SAndrea Paterniani 		goto done;
299814a8d50SAndrea Paterniani 
300814a8d50SAndrea Paterniani 	/* copy any rx data out of bounce buffer */
301865f6d19SRay Jui 	rx_buf = spidev->rx_buffer;
302814a8d50SAndrea Paterniani 	for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
303814a8d50SAndrea Paterniani 		if (u_tmp->rx_buf) {
3044917d927SDavid Brownell 			if (__copy_to_user((u8 __user *)
305865f6d19SRay Jui 					(uintptr_t) u_tmp->rx_buf, rx_buf,
306814a8d50SAndrea Paterniani 					u_tmp->len)) {
307814a8d50SAndrea Paterniani 				status = -EFAULT;
308814a8d50SAndrea Paterniani 				goto done;
309814a8d50SAndrea Paterniani 			}
310814a8d50SAndrea Paterniani 		}
311865f6d19SRay Jui 		rx_buf += u_tmp->len;
312814a8d50SAndrea Paterniani 	}
313814a8d50SAndrea Paterniani 	status = total;
314814a8d50SAndrea Paterniani 
315814a8d50SAndrea Paterniani done:
316814a8d50SAndrea Paterniani 	kfree(k_xfers);
317814a8d50SAndrea Paterniani 	return status;
318814a8d50SAndrea Paterniani }
319814a8d50SAndrea Paterniani 
3204ef754b7SAlan Cox static long
3214ef754b7SAlan Cox spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
322814a8d50SAndrea Paterniani {
323814a8d50SAndrea Paterniani 	int			err = 0;
324814a8d50SAndrea Paterniani 	int			retval = 0;
325814a8d50SAndrea Paterniani 	struct spidev_data	*spidev;
326814a8d50SAndrea Paterniani 	struct spi_device	*spi;
327814a8d50SAndrea Paterniani 	u32			tmp;
328814a8d50SAndrea Paterniani 	unsigned		n_ioc;
329814a8d50SAndrea Paterniani 	struct spi_ioc_transfer	*ioc;
330814a8d50SAndrea Paterniani 
331814a8d50SAndrea Paterniani 	/* Check type and command number */
332814a8d50SAndrea Paterniani 	if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)
333814a8d50SAndrea Paterniani 		return -ENOTTY;
334814a8d50SAndrea Paterniani 
335814a8d50SAndrea Paterniani 	/* Check access direction once here; don't repeat below.
336814a8d50SAndrea Paterniani 	 * IOC_DIR is from the user perspective, while access_ok is
337814a8d50SAndrea Paterniani 	 * from the kernel perspective; so they look reversed.
338814a8d50SAndrea Paterniani 	 */
339814a8d50SAndrea Paterniani 	if (_IOC_DIR(cmd) & _IOC_READ)
340814a8d50SAndrea Paterniani 		err = !access_ok(VERIFY_WRITE,
341814a8d50SAndrea Paterniani 				(void __user *)arg, _IOC_SIZE(cmd));
342814a8d50SAndrea Paterniani 	if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)
343814a8d50SAndrea Paterniani 		err = !access_ok(VERIFY_READ,
344814a8d50SAndrea Paterniani 				(void __user *)arg, _IOC_SIZE(cmd));
345814a8d50SAndrea Paterniani 	if (err)
346814a8d50SAndrea Paterniani 		return -EFAULT;
347814a8d50SAndrea Paterniani 
34825d5cb4bSDavid Brownell 	/* guard against device removal before, or while,
34925d5cb4bSDavid Brownell 	 * we issue this ioctl.
35025d5cb4bSDavid Brownell 	 */
351814a8d50SAndrea Paterniani 	spidev = filp->private_data;
35225d5cb4bSDavid Brownell 	spin_lock_irq(&spidev->spi_lock);
35325d5cb4bSDavid Brownell 	spi = spi_dev_get(spidev->spi);
35425d5cb4bSDavid Brownell 	spin_unlock_irq(&spidev->spi_lock);
35525d5cb4bSDavid Brownell 
35625d5cb4bSDavid Brownell 	if (spi == NULL)
35725d5cb4bSDavid Brownell 		return -ESHUTDOWN;
358814a8d50SAndrea Paterniani 
3594ef754b7SAlan Cox 	/* use the buffer lock here for triple duty:
3604ef754b7SAlan Cox 	 *  - prevent I/O (from us) so calling spi_setup() is safe;
3614ef754b7SAlan Cox 	 *  - prevent concurrent SPI_IOC_WR_* from morphing
3624ef754b7SAlan Cox 	 *    data fields while SPI_IOC_RD_* reads them;
3634ef754b7SAlan Cox 	 *  - SPI_IOC_MESSAGE needs the buffer locked "normally".
3644ef754b7SAlan Cox 	 */
3654ef754b7SAlan Cox 	mutex_lock(&spidev->buf_lock);
3664ef754b7SAlan Cox 
367814a8d50SAndrea Paterniani 	switch (cmd) {
368814a8d50SAndrea Paterniani 	/* read requests */
369814a8d50SAndrea Paterniani 	case SPI_IOC_RD_MODE:
370814a8d50SAndrea Paterniani 		retval = __put_user(spi->mode & SPI_MODE_MASK,
371814a8d50SAndrea Paterniani 					(__u8 __user *)arg);
372814a8d50SAndrea Paterniani 		break;
373dc64d39bSGeert Uytterhoeven 	case SPI_IOC_RD_MODE32:
374dc64d39bSGeert Uytterhoeven 		retval = __put_user(spi->mode & SPI_MODE_MASK,
375dc64d39bSGeert Uytterhoeven 					(__u32 __user *)arg);
376dc64d39bSGeert Uytterhoeven 		break;
377814a8d50SAndrea Paterniani 	case SPI_IOC_RD_LSB_FIRST:
378814a8d50SAndrea Paterniani 		retval = __put_user((spi->mode & SPI_LSB_FIRST) ?  1 : 0,
379814a8d50SAndrea Paterniani 					(__u8 __user *)arg);
380814a8d50SAndrea Paterniani 		break;
381814a8d50SAndrea Paterniani 	case SPI_IOC_RD_BITS_PER_WORD:
382814a8d50SAndrea Paterniani 		retval = __put_user(spi->bits_per_word, (__u8 __user *)arg);
383814a8d50SAndrea Paterniani 		break;
384814a8d50SAndrea Paterniani 	case SPI_IOC_RD_MAX_SPEED_HZ:
38591690516SMark Brown 		retval = __put_user(spidev->speed_hz, (__u32 __user *)arg);
386814a8d50SAndrea Paterniani 		break;
387814a8d50SAndrea Paterniani 
388814a8d50SAndrea Paterniani 	/* write requests */
389814a8d50SAndrea Paterniani 	case SPI_IOC_WR_MODE:
390dc64d39bSGeert Uytterhoeven 	case SPI_IOC_WR_MODE32:
391dc64d39bSGeert Uytterhoeven 		if (cmd == SPI_IOC_WR_MODE)
392814a8d50SAndrea Paterniani 			retval = __get_user(tmp, (u8 __user *)arg);
393dc64d39bSGeert Uytterhoeven 		else
394dc64d39bSGeert Uytterhoeven 			retval = __get_user(tmp, (u32 __user *)arg);
395814a8d50SAndrea Paterniani 		if (retval == 0) {
396e6456186SGeert Uytterhoeven 			u32	save = spi->mode;
397814a8d50SAndrea Paterniani 
398814a8d50SAndrea Paterniani 			if (tmp & ~SPI_MODE_MASK) {
399814a8d50SAndrea Paterniani 				retval = -EINVAL;
400814a8d50SAndrea Paterniani 				break;
401814a8d50SAndrea Paterniani 			}
402814a8d50SAndrea Paterniani 
403814a8d50SAndrea Paterniani 			tmp |= spi->mode & ~SPI_MODE_MASK;
404dc64d39bSGeert Uytterhoeven 			spi->mode = (u16)tmp;
405814a8d50SAndrea Paterniani 			retval = spi_setup(spi);
406814a8d50SAndrea Paterniani 			if (retval < 0)
407814a8d50SAndrea Paterniani 				spi->mode = save;
408814a8d50SAndrea Paterniani 			else
409dc64d39bSGeert Uytterhoeven 				dev_dbg(&spi->dev, "spi mode %x\n", tmp);
410814a8d50SAndrea Paterniani 		}
411814a8d50SAndrea Paterniani 		break;
412814a8d50SAndrea Paterniani 	case SPI_IOC_WR_LSB_FIRST:
413814a8d50SAndrea Paterniani 		retval = __get_user(tmp, (__u8 __user *)arg);
414814a8d50SAndrea Paterniani 		if (retval == 0) {
415e6456186SGeert Uytterhoeven 			u32	save = spi->mode;
416814a8d50SAndrea Paterniani 
417814a8d50SAndrea Paterniani 			if (tmp)
418814a8d50SAndrea Paterniani 				spi->mode |= SPI_LSB_FIRST;
419814a8d50SAndrea Paterniani 			else
420814a8d50SAndrea Paterniani 				spi->mode &= ~SPI_LSB_FIRST;
421814a8d50SAndrea Paterniani 			retval = spi_setup(spi);
422814a8d50SAndrea Paterniani 			if (retval < 0)
423814a8d50SAndrea Paterniani 				spi->mode = save;
424814a8d50SAndrea Paterniani 			else
425814a8d50SAndrea Paterniani 				dev_dbg(&spi->dev, "%csb first\n",
426814a8d50SAndrea Paterniani 						tmp ? 'l' : 'm');
427814a8d50SAndrea Paterniani 		}
428814a8d50SAndrea Paterniani 		break;
429814a8d50SAndrea Paterniani 	case SPI_IOC_WR_BITS_PER_WORD:
430814a8d50SAndrea Paterniani 		retval = __get_user(tmp, (__u8 __user *)arg);
431814a8d50SAndrea Paterniani 		if (retval == 0) {
432814a8d50SAndrea Paterniani 			u8	save = spi->bits_per_word;
433814a8d50SAndrea Paterniani 
434814a8d50SAndrea Paterniani 			spi->bits_per_word = tmp;
435814a8d50SAndrea Paterniani 			retval = spi_setup(spi);
436814a8d50SAndrea Paterniani 			if (retval < 0)
437814a8d50SAndrea Paterniani 				spi->bits_per_word = save;
438814a8d50SAndrea Paterniani 			else
439814a8d50SAndrea Paterniani 				dev_dbg(&spi->dev, "%d bits per word\n", tmp);
440814a8d50SAndrea Paterniani 		}
441814a8d50SAndrea Paterniani 		break;
442814a8d50SAndrea Paterniani 	case SPI_IOC_WR_MAX_SPEED_HZ:
443814a8d50SAndrea Paterniani 		retval = __get_user(tmp, (__u32 __user *)arg);
444814a8d50SAndrea Paterniani 		if (retval == 0) {
445814a8d50SAndrea Paterniani 			u32	save = spi->max_speed_hz;
446814a8d50SAndrea Paterniani 
447814a8d50SAndrea Paterniani 			spi->max_speed_hz = tmp;
448814a8d50SAndrea Paterniani 			retval = spi_setup(spi);
44991690516SMark Brown 			if (retval >= 0)
45091690516SMark Brown 				spidev->speed_hz = tmp;
451814a8d50SAndrea Paterniani 			else
452814a8d50SAndrea Paterniani 				dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
45391690516SMark Brown 			spi->max_speed_hz = save;
454814a8d50SAndrea Paterniani 		}
455814a8d50SAndrea Paterniani 		break;
456814a8d50SAndrea Paterniani 
457814a8d50SAndrea Paterniani 	default:
458814a8d50SAndrea Paterniani 		/* segmented and/or full-duplex I/O request */
459814a8d50SAndrea Paterniani 		if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))
46025d5cb4bSDavid Brownell 				|| _IOC_DIR(cmd) != _IOC_WRITE) {
46125d5cb4bSDavid Brownell 			retval = -ENOTTY;
46225d5cb4bSDavid Brownell 			break;
46325d5cb4bSDavid Brownell 		}
464814a8d50SAndrea Paterniani 
465814a8d50SAndrea Paterniani 		tmp = _IOC_SIZE(cmd);
466814a8d50SAndrea Paterniani 		if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) {
467814a8d50SAndrea Paterniani 			retval = -EINVAL;
468814a8d50SAndrea Paterniani 			break;
469814a8d50SAndrea Paterniani 		}
470814a8d50SAndrea Paterniani 		n_ioc = tmp / sizeof(struct spi_ioc_transfer);
471814a8d50SAndrea Paterniani 		if (n_ioc == 0)
472814a8d50SAndrea Paterniani 			break;
473814a8d50SAndrea Paterniani 
474814a8d50SAndrea Paterniani 		/* copy into scratch area */
475814a8d50SAndrea Paterniani 		ioc = kmalloc(tmp, GFP_KERNEL);
476814a8d50SAndrea Paterniani 		if (!ioc) {
477814a8d50SAndrea Paterniani 			retval = -ENOMEM;
478814a8d50SAndrea Paterniani 			break;
479814a8d50SAndrea Paterniani 		}
480814a8d50SAndrea Paterniani 		if (__copy_from_user(ioc, (void __user *)arg, tmp)) {
4819bea3f29SFlorin Malita 			kfree(ioc);
482814a8d50SAndrea Paterniani 			retval = -EFAULT;
483814a8d50SAndrea Paterniani 			break;
484814a8d50SAndrea Paterniani 		}
485814a8d50SAndrea Paterniani 
486814a8d50SAndrea Paterniani 		/* translate to spi_message, execute */
487814a8d50SAndrea Paterniani 		retval = spidev_message(spidev, ioc, n_ioc);
488814a8d50SAndrea Paterniani 		kfree(ioc);
489814a8d50SAndrea Paterniani 		break;
490814a8d50SAndrea Paterniani 	}
4914ef754b7SAlan Cox 
4924ef754b7SAlan Cox 	mutex_unlock(&spidev->buf_lock);
49325d5cb4bSDavid Brownell 	spi_dev_put(spi);
494814a8d50SAndrea Paterniani 	return retval;
495814a8d50SAndrea Paterniani }
496814a8d50SAndrea Paterniani 
4977d48ec36SBernhard Walle #ifdef CONFIG_COMPAT
4987d48ec36SBernhard Walle static long
4997d48ec36SBernhard Walle spidev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
5007d48ec36SBernhard Walle {
5017d48ec36SBernhard Walle 	return spidev_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
5027d48ec36SBernhard Walle }
5037d48ec36SBernhard Walle #else
5047d48ec36SBernhard Walle #define spidev_compat_ioctl NULL
5057d48ec36SBernhard Walle #endif /* CONFIG_COMPAT */
5067d48ec36SBernhard Walle 
507814a8d50SAndrea Paterniani static int spidev_open(struct inode *inode, struct file *filp)
508814a8d50SAndrea Paterniani {
509814a8d50SAndrea Paterniani 	struct spidev_data	*spidev;
510814a8d50SAndrea Paterniani 	int			status = -ENXIO;
511814a8d50SAndrea Paterniani 
512814a8d50SAndrea Paterniani 	mutex_lock(&device_list_lock);
513814a8d50SAndrea Paterniani 
514814a8d50SAndrea Paterniani 	list_for_each_entry(spidev, &device_list, device_entry) {
515b2c8daddSDavid Brownell 		if (spidev->devt == inode->i_rdev) {
516814a8d50SAndrea Paterniani 			status = 0;
517814a8d50SAndrea Paterniani 			break;
518814a8d50SAndrea Paterniani 		}
519814a8d50SAndrea Paterniani 	}
520865f6d19SRay Jui 
521865f6d19SRay Jui 	if (status) {
522865f6d19SRay Jui 		pr_debug("spidev: nothing for minor %d\n", iminor(inode));
523865f6d19SRay Jui 		goto err_find_dev;
524865f6d19SRay Jui 	}
525865f6d19SRay Jui 
526865f6d19SRay Jui 	if (!spidev->tx_buffer) {
527865f6d19SRay Jui 		spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL);
528865f6d19SRay Jui 		if (!spidev->tx_buffer) {
529814a8d50SAndrea Paterniani 				dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
530814a8d50SAndrea Paterniani 				status = -ENOMEM;
531865f6d19SRay Jui 			goto err_find_dev;
532814a8d50SAndrea Paterniani 			}
533814a8d50SAndrea Paterniani 		}
534865f6d19SRay Jui 
535865f6d19SRay Jui 	if (!spidev->rx_buffer) {
536865f6d19SRay Jui 		spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL);
537865f6d19SRay Jui 		if (!spidev->rx_buffer) {
538865f6d19SRay Jui 			dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
539865f6d19SRay Jui 			status = -ENOMEM;
540865f6d19SRay Jui 			goto err_alloc_rx_buf;
541865f6d19SRay Jui 		}
542865f6d19SRay Jui 	}
543865f6d19SRay Jui 
544814a8d50SAndrea Paterniani 	spidev->users++;
545814a8d50SAndrea Paterniani 	filp->private_data = spidev;
546814a8d50SAndrea Paterniani 	nonseekable_open(inode, filp);
547814a8d50SAndrea Paterniani 
548814a8d50SAndrea Paterniani 	mutex_unlock(&device_list_lock);
549865f6d19SRay Jui 	return 0;
550865f6d19SRay Jui 
551865f6d19SRay Jui err_alloc_rx_buf:
552865f6d19SRay Jui 	kfree(spidev->tx_buffer);
553865f6d19SRay Jui 	spidev->tx_buffer = NULL;
554865f6d19SRay Jui err_find_dev:
555865f6d19SRay Jui 	mutex_unlock(&device_list_lock);
556814a8d50SAndrea Paterniani 	return status;
557814a8d50SAndrea Paterniani }
558814a8d50SAndrea Paterniani 
559814a8d50SAndrea Paterniani static int spidev_release(struct inode *inode, struct file *filp)
560814a8d50SAndrea Paterniani {
561814a8d50SAndrea Paterniani 	struct spidev_data	*spidev;
562814a8d50SAndrea Paterniani 	int			status = 0;
563814a8d50SAndrea Paterniani 
564814a8d50SAndrea Paterniani 	mutex_lock(&device_list_lock);
565814a8d50SAndrea Paterniani 	spidev = filp->private_data;
566814a8d50SAndrea Paterniani 	filp->private_data = NULL;
567b2c8daddSDavid Brownell 
568b2c8daddSDavid Brownell 	/* last close? */
569814a8d50SAndrea Paterniani 	spidev->users--;
570814a8d50SAndrea Paterniani 	if (!spidev->users) {
571b2c8daddSDavid Brownell 		int		dofree;
572b2c8daddSDavid Brownell 
573865f6d19SRay Jui 		kfree(spidev->tx_buffer);
574865f6d19SRay Jui 		spidev->tx_buffer = NULL;
575865f6d19SRay Jui 
576865f6d19SRay Jui 		kfree(spidev->rx_buffer);
577865f6d19SRay Jui 		spidev->rx_buffer = NULL;
578b2c8daddSDavid Brownell 
57991690516SMark Brown 		spidev->speed_hz = spidev->spi->max_speed_hz;
58091690516SMark Brown 
581b2c8daddSDavid Brownell 		/* ... after we unbound from the underlying device? */
582b2c8daddSDavid Brownell 		spin_lock_irq(&spidev->spi_lock);
583b2c8daddSDavid Brownell 		dofree = (spidev->spi == NULL);
584b2c8daddSDavid Brownell 		spin_unlock_irq(&spidev->spi_lock);
585b2c8daddSDavid Brownell 
586b2c8daddSDavid Brownell 		if (dofree)
587b2c8daddSDavid Brownell 			kfree(spidev);
588814a8d50SAndrea Paterniani 	}
589814a8d50SAndrea Paterniani 	mutex_unlock(&device_list_lock);
590814a8d50SAndrea Paterniani 
591814a8d50SAndrea Paterniani 	return status;
592814a8d50SAndrea Paterniani }
593814a8d50SAndrea Paterniani 
594828c0950SAlexey Dobriyan static const struct file_operations spidev_fops = {
595814a8d50SAndrea Paterniani 	.owner =	THIS_MODULE,
596814a8d50SAndrea Paterniani 	/* REVISIT switch to aio primitives, so that userspace
597814a8d50SAndrea Paterniani 	 * gets more complete API coverage.  It'll simplify things
598814a8d50SAndrea Paterniani 	 * too, except for the locking.
599814a8d50SAndrea Paterniani 	 */
600814a8d50SAndrea Paterniani 	.write =	spidev_write,
601814a8d50SAndrea Paterniani 	.read =		spidev_read,
6024ef754b7SAlan Cox 	.unlocked_ioctl = spidev_ioctl,
6037d48ec36SBernhard Walle 	.compat_ioctl = spidev_compat_ioctl,
604814a8d50SAndrea Paterniani 	.open =		spidev_open,
605814a8d50SAndrea Paterniani 	.release =	spidev_release,
6066038f373SArnd Bergmann 	.llseek =	no_llseek,
607814a8d50SAndrea Paterniani };
608814a8d50SAndrea Paterniani 
609814a8d50SAndrea Paterniani /*-------------------------------------------------------------------------*/
610814a8d50SAndrea Paterniani 
611814a8d50SAndrea Paterniani /* The main reason to have this class is to make mdev/udev create the
612814a8d50SAndrea Paterniani  * /dev/spidevB.C character device nodes exposing our userspace API.
613814a8d50SAndrea Paterniani  * It also simplifies memory management.
614814a8d50SAndrea Paterniani  */
615814a8d50SAndrea Paterniani 
616b2c8daddSDavid Brownell static struct class *spidev_class;
617814a8d50SAndrea Paterniani 
618814a8d50SAndrea Paterniani /*-------------------------------------------------------------------------*/
619814a8d50SAndrea Paterniani 
620fd4a319bSGrant Likely static int spidev_probe(struct spi_device *spi)
621814a8d50SAndrea Paterniani {
622814a8d50SAndrea Paterniani 	struct spidev_data	*spidev;
623814a8d50SAndrea Paterniani 	int			status;
624814a8d50SAndrea Paterniani 	unsigned long		minor;
625814a8d50SAndrea Paterniani 
626814a8d50SAndrea Paterniani 	/* Allocate driver data */
627814a8d50SAndrea Paterniani 	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
628814a8d50SAndrea Paterniani 	if (!spidev)
629814a8d50SAndrea Paterniani 		return -ENOMEM;
630814a8d50SAndrea Paterniani 
631814a8d50SAndrea Paterniani 	/* Initialize the driver data */
632814a8d50SAndrea Paterniani 	spidev->spi = spi;
63325d5cb4bSDavid Brownell 	spin_lock_init(&spidev->spi_lock);
634814a8d50SAndrea Paterniani 	mutex_init(&spidev->buf_lock);
635814a8d50SAndrea Paterniani 
636814a8d50SAndrea Paterniani 	INIT_LIST_HEAD(&spidev->device_entry);
637814a8d50SAndrea Paterniani 
638814a8d50SAndrea Paterniani 	/* If we can allocate a minor number, hook up this device.
639814a8d50SAndrea Paterniani 	 * Reusing minors is fine so long as udev or mdev is working.
640814a8d50SAndrea Paterniani 	 */
641814a8d50SAndrea Paterniani 	mutex_lock(&device_list_lock);
6420a4dd778SDomen Puncer 	minor = find_first_zero_bit(minors, N_SPI_MINORS);
643814a8d50SAndrea Paterniani 	if (minor < N_SPI_MINORS) {
644b2c8daddSDavid Brownell 		struct device *dev;
645b2c8daddSDavid Brownell 
646b2c8daddSDavid Brownell 		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
647a9b12619SGreg Kroah-Hartman 		dev = device_create(spidev_class, &spi->dev, spidev->devt,
648a9b12619SGreg Kroah-Hartman 				    spidev, "spidev%d.%d",
649814a8d50SAndrea Paterniani 				    spi->master->bus_num, spi->chip_select);
6508c6ffba0SRusty Russell 		status = PTR_ERR_OR_ZERO(dev);
651814a8d50SAndrea Paterniani 	} else {
652814a8d50SAndrea Paterniani 		dev_dbg(&spi->dev, "no minor number available!\n");
653814a8d50SAndrea Paterniani 		status = -ENODEV;
654814a8d50SAndrea Paterniani 	}
655814a8d50SAndrea Paterniani 	if (status == 0) {
656814a8d50SAndrea Paterniani 		set_bit(minor, minors);
657814a8d50SAndrea Paterniani 		list_add(&spidev->device_entry, &device_list);
658814a8d50SAndrea Paterniani 	}
659814a8d50SAndrea Paterniani 	mutex_unlock(&device_list_lock);
660814a8d50SAndrea Paterniani 
66191690516SMark Brown 	spidev->speed_hz = spi->max_speed_hz;
66291690516SMark Brown 
663aaacf4bbSWolfgang Ocker 	if (status == 0)
664aaacf4bbSWolfgang Ocker 		spi_set_drvdata(spi, spidev);
665aaacf4bbSWolfgang Ocker 	else
666814a8d50SAndrea Paterniani 		kfree(spidev);
667814a8d50SAndrea Paterniani 
668814a8d50SAndrea Paterniani 	return status;
669814a8d50SAndrea Paterniani }
670814a8d50SAndrea Paterniani 
671fd4a319bSGrant Likely static int spidev_remove(struct spi_device *spi)
672814a8d50SAndrea Paterniani {
673b2c8daddSDavid Brownell 	struct spidev_data	*spidev = spi_get_drvdata(spi);
674814a8d50SAndrea Paterniani 
67525d5cb4bSDavid Brownell 	/* make sure ops on existing fds can abort cleanly */
67625d5cb4bSDavid Brownell 	spin_lock_irq(&spidev->spi_lock);
67725d5cb4bSDavid Brownell 	spidev->spi = NULL;
67825d5cb4bSDavid Brownell 	spin_unlock_irq(&spidev->spi_lock);
679814a8d50SAndrea Paterniani 
68025d5cb4bSDavid Brownell 	/* prevent new opens */
681814a8d50SAndrea Paterniani 	mutex_lock(&device_list_lock);
682814a8d50SAndrea Paterniani 	list_del(&spidev->device_entry);
683b2c8daddSDavid Brownell 	device_destroy(spidev_class, spidev->devt);
684b2c8daddSDavid Brownell 	clear_bit(MINOR(spidev->devt), minors);
685b2c8daddSDavid Brownell 	if (spidev->users == 0)
686b2c8daddSDavid Brownell 		kfree(spidev);
687814a8d50SAndrea Paterniani 	mutex_unlock(&device_list_lock);
688814a8d50SAndrea Paterniani 
689814a8d50SAndrea Paterniani 	return 0;
690814a8d50SAndrea Paterniani }
691814a8d50SAndrea Paterniani 
692880cfd43SMaxime Ripard static const struct of_device_id spidev_dt_ids[] = {
6938fad805bSMaxime Ripard 	{ .compatible = "rohm,dh2228fv" },
694880cfd43SMaxime Ripard 	{},
695880cfd43SMaxime Ripard };
696880cfd43SMaxime Ripard 
697880cfd43SMaxime Ripard MODULE_DEVICE_TABLE(of, spidev_dt_ids);
698880cfd43SMaxime Ripard 
699db389b61SMike Frysinger static struct spi_driver spidev_spi_driver = {
700814a8d50SAndrea Paterniani 	.driver = {
701814a8d50SAndrea Paterniani 		.name =		"spidev",
702814a8d50SAndrea Paterniani 		.owner =	THIS_MODULE,
703880cfd43SMaxime Ripard 		.of_match_table = of_match_ptr(spidev_dt_ids),
704814a8d50SAndrea Paterniani 	},
705814a8d50SAndrea Paterniani 	.probe =	spidev_probe,
706fd4a319bSGrant Likely 	.remove =	spidev_remove,
707814a8d50SAndrea Paterniani 
708814a8d50SAndrea Paterniani 	/* NOTE:  suspend/resume methods are not necessary here.
709814a8d50SAndrea Paterniani 	 * We don't do anything except pass the requests to/from
710814a8d50SAndrea Paterniani 	 * the underlying controller.  The refrigerator handles
711814a8d50SAndrea Paterniani 	 * most issues; the controller driver handles the rest.
712814a8d50SAndrea Paterniani 	 */
713814a8d50SAndrea Paterniani };
714814a8d50SAndrea Paterniani 
715814a8d50SAndrea Paterniani /*-------------------------------------------------------------------------*/
716814a8d50SAndrea Paterniani 
717814a8d50SAndrea Paterniani static int __init spidev_init(void)
718814a8d50SAndrea Paterniani {
719814a8d50SAndrea Paterniani 	int status;
720814a8d50SAndrea Paterniani 
721814a8d50SAndrea Paterniani 	/* Claim our 256 reserved device numbers.  Then register a class
722814a8d50SAndrea Paterniani 	 * that will key udev/mdev to add/remove /dev nodes.  Last, register
723814a8d50SAndrea Paterniani 	 * the driver which manages those device numbers.
724814a8d50SAndrea Paterniani 	 */
725814a8d50SAndrea Paterniani 	BUILD_BUG_ON(N_SPI_MINORS > 256);
726814a8d50SAndrea Paterniani 	status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
727814a8d50SAndrea Paterniani 	if (status < 0)
728814a8d50SAndrea Paterniani 		return status;
729814a8d50SAndrea Paterniani 
730b2c8daddSDavid Brownell 	spidev_class = class_create(THIS_MODULE, "spidev");
731b2c8daddSDavid Brownell 	if (IS_ERR(spidev_class)) {
732db389b61SMike Frysinger 		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
733b2c8daddSDavid Brownell 		return PTR_ERR(spidev_class);
734814a8d50SAndrea Paterniani 	}
735814a8d50SAndrea Paterniani 
736db389b61SMike Frysinger 	status = spi_register_driver(&spidev_spi_driver);
737814a8d50SAndrea Paterniani 	if (status < 0) {
738b2c8daddSDavid Brownell 		class_destroy(spidev_class);
739db389b61SMike Frysinger 		unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
740814a8d50SAndrea Paterniani 	}
741814a8d50SAndrea Paterniani 	return status;
742814a8d50SAndrea Paterniani }
743814a8d50SAndrea Paterniani module_init(spidev_init);
744814a8d50SAndrea Paterniani 
745814a8d50SAndrea Paterniani static void __exit spidev_exit(void)
746814a8d50SAndrea Paterniani {
747db389b61SMike Frysinger 	spi_unregister_driver(&spidev_spi_driver);
748b2c8daddSDavid Brownell 	class_destroy(spidev_class);
749db389b61SMike Frysinger 	unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
750814a8d50SAndrea Paterniani }
751814a8d50SAndrea Paterniani module_exit(spidev_exit);
752814a8d50SAndrea Paterniani 
753814a8d50SAndrea Paterniani MODULE_AUTHOR("Andrea Paterniani, <a.paterniani@swapp-eng.it>");
754814a8d50SAndrea Paterniani MODULE_DESCRIPTION("User mode SPI device interface");
755814a8d50SAndrea Paterniani MODULE_LICENSE("GPL");
756e0626e38SAnton Vorontsov MODULE_ALIAS("spi:spidev");
757