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