13d0407baSopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 23d0407baSopenharmony_ci/* 33d0407baSopenharmony_ci * Simple synchronous userspace interface to SPI devices 43d0407baSopenharmony_ci * 53d0407baSopenharmony_ci * Copyright (C) 2006 SWAPP 63d0407baSopenharmony_ci * Andrea Paterniani <a.paterniani@swapp-eng.it> 73d0407baSopenharmony_ci * Copyright (C) 2007 David Brownell (simplification, cleanup) 83d0407baSopenharmony_ci */ 93d0407baSopenharmony_ci 103d0407baSopenharmony_ci#include <linux/init.h> 113d0407baSopenharmony_ci#include <linux/module.h> 123d0407baSopenharmony_ci#include <linux/ioctl.h> 133d0407baSopenharmony_ci#include <linux/fs.h> 143d0407baSopenharmony_ci#include <linux/device.h> 153d0407baSopenharmony_ci#include <linux/err.h> 163d0407baSopenharmony_ci#include <linux/list.h> 173d0407baSopenharmony_ci#include <linux/errno.h> 183d0407baSopenharmony_ci#include <linux/mutex.h> 193d0407baSopenharmony_ci#include <linux/slab.h> 203d0407baSopenharmony_ci#include <linux/compat.h> 213d0407baSopenharmony_ci#include <linux/of.h> 223d0407baSopenharmony_ci#include <linux/of_device.h> 233d0407baSopenharmony_ci#include <linux/acpi.h> 243d0407baSopenharmony_ci 253d0407baSopenharmony_ci#include <linux/spi/spi.h> 263d0407baSopenharmony_ci#include <linux/spi/spidev.h> 273d0407baSopenharmony_ci 283d0407baSopenharmony_ci#include <linux/uaccess.h> 293d0407baSopenharmony_ci 303d0407baSopenharmony_ci/* 313d0407baSopenharmony_ci * This supports access to SPI devices using normal userspace I/O calls. 323d0407baSopenharmony_ci * Note that while traditional UNIX/POSIX I/O semantics are half duplex, 333d0407baSopenharmony_ci * and often mask message boundaries, full SPI support requires full duplex 343d0407baSopenharmony_ci * transfers. There are several kinds of internal message boundaries to 353d0407baSopenharmony_ci * handle chipselect management and other protocol options. 363d0407baSopenharmony_ci * 373d0407baSopenharmony_ci * SPI has a character major number assigned. We allocate minor numbers 383d0407baSopenharmony_ci * dynamically using a bitmask. You must use hotplug tools, such as udev 393d0407baSopenharmony_ci * (or mdev with busybox) to create and destroy the /dev/spidevB.C device 403d0407baSopenharmony_ci * nodes, since there is no fixed association of minor numbers with any 413d0407baSopenharmony_ci * particular SPI bus or device. 423d0407baSopenharmony_ci */ 433d0407baSopenharmony_ci#define SPIDEV_MAJOR 153 /* assigned */ 443d0407baSopenharmony_ci#define N_SPI_MINORS 32 /* ... up to 256 */ 453d0407baSopenharmony_ci 463d0407baSopenharmony_cistatic DECLARE_BITMAP(minors, N_SPI_MINORS); 473d0407baSopenharmony_ci 483d0407baSopenharmony_ci/* Bit masks for spi_device.mode management. Note that incorrect 493d0407baSopenharmony_ci * settings for some settings can cause *lots* of trouble for other 503d0407baSopenharmony_ci * devices on a shared bus: 513d0407baSopenharmony_ci * 523d0407baSopenharmony_ci * - CS_HIGH ... this device will be active when it shouldn't be 533d0407baSopenharmony_ci * - 3WIRE ... when active, it won't behave as it should 543d0407baSopenharmony_ci * - NO_CS ... there will be no explicit message boundaries; this 553d0407baSopenharmony_ci * is completely incompatible with the shared bus model 563d0407baSopenharmony_ci * - READY ... transfers may proceed when they shouldn't. 573d0407baSopenharmony_ci * 583d0407baSopenharmony_ci * REVISIT should changing those flags be privileged? 593d0407baSopenharmony_ci */ 603d0407baSopenharmony_ci#define SPI_MODE_MASK \ 613d0407baSopenharmony_ci (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP | SPI_NO_CS | SPI_READY | SPI_TX_DUAL | \ 623d0407baSopenharmony_ci SPI_TX_QUAD | SPI_TX_OCTAL | SPI_RX_DUAL | SPI_RX_QUAD | SPI_RX_OCTAL) 633d0407baSopenharmony_ci 643d0407baSopenharmony_cistruct spidev_data { 653d0407baSopenharmony_ci dev_t devt; 663d0407baSopenharmony_ci spinlock_t spi_lock; 673d0407baSopenharmony_ci struct spi_device *spi; 683d0407baSopenharmony_ci struct list_head device_entry; 693d0407baSopenharmony_ci 703d0407baSopenharmony_ci /* TX/RX buffers are NULL unless this device is open (users > 0) */ 713d0407baSopenharmony_ci struct mutex buf_lock; 723d0407baSopenharmony_ci unsigned users; 733d0407baSopenharmony_ci u8 *tx_buffer; 743d0407baSopenharmony_ci u8 *rx_buffer; 753d0407baSopenharmony_ci u32 speed_hz; 763d0407baSopenharmony_ci}; 773d0407baSopenharmony_ci 783d0407baSopenharmony_cistatic LIST_HEAD(device_list); 793d0407baSopenharmony_cistatic DEFINE_MUTEX(device_list_lock); 803d0407baSopenharmony_ci 813d0407baSopenharmony_cistatic unsigned bufsiz = 4096; 823d0407baSopenharmony_cimodule_param(bufsiz, uint, S_IRUGO); 833d0407baSopenharmony_ciMODULE_PARM_DESC(bufsiz, "data bytes in biggest supported SPI message"); 843d0407baSopenharmony_ci 853d0407baSopenharmony_ci/*-------------------------------------------------------------------------*/ 863d0407baSopenharmony_ci 873d0407baSopenharmony_cistatic ssize_t spidev_sync(struct spidev_data *spidev, struct spi_message *message) 883d0407baSopenharmony_ci{ 893d0407baSopenharmony_ci int status; 903d0407baSopenharmony_ci struct spi_device *spi; 913d0407baSopenharmony_ci 923d0407baSopenharmony_ci spin_lock_irq(&spidev->spi_lock); 933d0407baSopenharmony_ci spi = spidev->spi; 943d0407baSopenharmony_ci spin_unlock_irq(&spidev->spi_lock); 953d0407baSopenharmony_ci 963d0407baSopenharmony_ci if (spi == NULL) { 973d0407baSopenharmony_ci status = -ESHUTDOWN; 983d0407baSopenharmony_ci } else { 993d0407baSopenharmony_ci status = spi_sync(spi, message); 1003d0407baSopenharmony_ci } 1013d0407baSopenharmony_ci 1023d0407baSopenharmony_ci if (status == 0) { 1033d0407baSopenharmony_ci status = message->actual_length; 1043d0407baSopenharmony_ci } 1053d0407baSopenharmony_ci 1063d0407baSopenharmony_ci return status; 1073d0407baSopenharmony_ci} 1083d0407baSopenharmony_ci 1093d0407baSopenharmony_cistatic inline ssize_t spidev_sync_write(struct spidev_data *spidev, size_t len) 1103d0407baSopenharmony_ci{ 1113d0407baSopenharmony_ci struct spi_transfer t = { 1123d0407baSopenharmony_ci .tx_buf = spidev->tx_buffer, 1133d0407baSopenharmony_ci .len = len, 1143d0407baSopenharmony_ci .speed_hz = spidev->speed_hz, 1153d0407baSopenharmony_ci }; 1163d0407baSopenharmony_ci struct spi_message m; 1173d0407baSopenharmony_ci 1183d0407baSopenharmony_ci spi_message_init(&m); 1193d0407baSopenharmony_ci spi_message_add_tail(&t, &m); 1203d0407baSopenharmony_ci return spidev_sync(spidev, &m); 1213d0407baSopenharmony_ci} 1223d0407baSopenharmony_ci 1233d0407baSopenharmony_cistatic inline ssize_t spidev_sync_read(struct spidev_data *spidev, size_t len) 1243d0407baSopenharmony_ci{ 1253d0407baSopenharmony_ci struct spi_transfer t = { 1263d0407baSopenharmony_ci .rx_buf = spidev->rx_buffer, 1273d0407baSopenharmony_ci .len = len, 1283d0407baSopenharmony_ci .speed_hz = spidev->speed_hz, 1293d0407baSopenharmony_ci }; 1303d0407baSopenharmony_ci struct spi_message m; 1313d0407baSopenharmony_ci 1323d0407baSopenharmony_ci spi_message_init(&m); 1333d0407baSopenharmony_ci spi_message_add_tail(&t, &m); 1343d0407baSopenharmony_ci return spidev_sync(spidev, &m); 1353d0407baSopenharmony_ci} 1363d0407baSopenharmony_ci 1373d0407baSopenharmony_ci/*-------------------------------------------------------------------------*/ 1383d0407baSopenharmony_ci 1393d0407baSopenharmony_ci/* Read-only message with current device setup */ 1403d0407baSopenharmony_cistatic ssize_t spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) 1413d0407baSopenharmony_ci{ 1423d0407baSopenharmony_ci struct spidev_data *spidev; 1433d0407baSopenharmony_ci ssize_t status; 1443d0407baSopenharmony_ci 1453d0407baSopenharmony_ci /* chipselect only toggles at start or end of operation */ 1463d0407baSopenharmony_ci if (count > bufsiz) { 1473d0407baSopenharmony_ci return -EMSGSIZE; 1483d0407baSopenharmony_ci } 1493d0407baSopenharmony_ci 1503d0407baSopenharmony_ci spidev = filp->private_data; 1513d0407baSopenharmony_ci 1523d0407baSopenharmony_ci mutex_lock(&spidev->buf_lock); 1533d0407baSopenharmony_ci status = spidev_sync_read(spidev, count); 1543d0407baSopenharmony_ci if (status > 0) { 1553d0407baSopenharmony_ci unsigned long missing; 1563d0407baSopenharmony_ci 1573d0407baSopenharmony_ci missing = copy_to_user(buf, spidev->rx_buffer, status); 1583d0407baSopenharmony_ci if (missing == status) { 1593d0407baSopenharmony_ci status = -EFAULT; 1603d0407baSopenharmony_ci } else { 1613d0407baSopenharmony_ci status = status - missing; 1623d0407baSopenharmony_ci } 1633d0407baSopenharmony_ci } 1643d0407baSopenharmony_ci mutex_unlock(&spidev->buf_lock); 1653d0407baSopenharmony_ci 1663d0407baSopenharmony_ci return status; 1673d0407baSopenharmony_ci} 1683d0407baSopenharmony_ci 1693d0407baSopenharmony_ci/* Write-only message with current device setup */ 1703d0407baSopenharmony_cistatic ssize_t spidev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) 1713d0407baSopenharmony_ci{ 1723d0407baSopenharmony_ci struct spidev_data *spidev; 1733d0407baSopenharmony_ci ssize_t status; 1743d0407baSopenharmony_ci unsigned long missing; 1753d0407baSopenharmony_ci 1763d0407baSopenharmony_ci /* chipselect only toggles at start or end of operation */ 1773d0407baSopenharmony_ci if (count > bufsiz) { 1783d0407baSopenharmony_ci return -EMSGSIZE; 1793d0407baSopenharmony_ci } 1803d0407baSopenharmony_ci 1813d0407baSopenharmony_ci spidev = filp->private_data; 1823d0407baSopenharmony_ci 1833d0407baSopenharmony_ci mutex_lock(&spidev->buf_lock); 1843d0407baSopenharmony_ci missing = copy_from_user(spidev->tx_buffer, buf, count); 1853d0407baSopenharmony_ci if (missing == 0) { 1863d0407baSopenharmony_ci status = spidev_sync_write(spidev, count); 1873d0407baSopenharmony_ci } else { 1883d0407baSopenharmony_ci status = -EFAULT; 1893d0407baSopenharmony_ci } 1903d0407baSopenharmony_ci mutex_unlock(&spidev->buf_lock); 1913d0407baSopenharmony_ci 1923d0407baSopenharmony_ci return status; 1933d0407baSopenharmony_ci} 1943d0407baSopenharmony_ci 1953d0407baSopenharmony_cistatic int spidev_message(struct spidev_data *spidev, struct spi_ioc_transfer *u_xfers, unsigned n_xfers) 1963d0407baSopenharmony_ci{ 1973d0407baSopenharmony_ci struct spi_message msg; 1983d0407baSopenharmony_ci struct spi_transfer *k_xfers; 1993d0407baSopenharmony_ci struct spi_transfer *k_tmp; 2003d0407baSopenharmony_ci struct spi_ioc_transfer *u_tmp; 2013d0407baSopenharmony_ci unsigned n, total, tx_total, rx_total; 2023d0407baSopenharmony_ci u8 *tx_buf, *rx_buf; 2033d0407baSopenharmony_ci int status = -EFAULT; 2043d0407baSopenharmony_ci 2053d0407baSopenharmony_ci spi_message_init(&msg); 2063d0407baSopenharmony_ci k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL); 2073d0407baSopenharmony_ci if (k_xfers == NULL) { 2083d0407baSopenharmony_ci return -ENOMEM; 2093d0407baSopenharmony_ci } 2103d0407baSopenharmony_ci 2113d0407baSopenharmony_ci /* Construct spi_message, copying any tx data to bounce buffer. 2123d0407baSopenharmony_ci * We walk the array of user-provided transfers, using each one 2133d0407baSopenharmony_ci * to initialize a kernel version of the same transfer. 2143d0407baSopenharmony_ci */ 2153d0407baSopenharmony_ci tx_buf = spidev->tx_buffer; 2163d0407baSopenharmony_ci rx_buf = spidev->rx_buffer; 2173d0407baSopenharmony_ci total = 0; 2183d0407baSopenharmony_ci tx_total = 0; 2193d0407baSopenharmony_ci rx_total = 0; 2203d0407baSopenharmony_ci for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; n; n--, k_tmp++, u_tmp++) { 2213d0407baSopenharmony_ci /* Ensure that also following allocations from rx_buf/tx_buf will meet 2223d0407baSopenharmony_ci * DMA alignment requirements. 2233d0407baSopenharmony_ci */ 2243d0407baSopenharmony_ci unsigned int len_aligned = ALIGN(u_tmp->len, ARCH_KMALLOC_MINALIGN); 2253d0407baSopenharmony_ci 2263d0407baSopenharmony_ci k_tmp->len = u_tmp->len; 2273d0407baSopenharmony_ci 2283d0407baSopenharmony_ci total += k_tmp->len; 2293d0407baSopenharmony_ci /* Since the function returns the total length of transfers 2303d0407baSopenharmony_ci * on success, restrict the total to positive int values to 2313d0407baSopenharmony_ci * avoid the return value looking like an error. Also check 2323d0407baSopenharmony_ci * each transfer length to avoid arithmetic overflow. 2333d0407baSopenharmony_ci */ 2343d0407baSopenharmony_ci if (total > INT_MAX || k_tmp->len > INT_MAX) { 2353d0407baSopenharmony_ci status = -EMSGSIZE; 2363d0407baSopenharmony_ci goto done; 2373d0407baSopenharmony_ci } 2383d0407baSopenharmony_ci 2393d0407baSopenharmony_ci if (u_tmp->rx_buf) { 2403d0407baSopenharmony_ci /* this transfer needs space in RX bounce buffer */ 2413d0407baSopenharmony_ci rx_total += len_aligned; 2423d0407baSopenharmony_ci if (rx_total > bufsiz) { 2433d0407baSopenharmony_ci status = -EMSGSIZE; 2443d0407baSopenharmony_ci goto done; 2453d0407baSopenharmony_ci } 2463d0407baSopenharmony_ci k_tmp->rx_buf = rx_buf; 2473d0407baSopenharmony_ci rx_buf += len_aligned; 2483d0407baSopenharmony_ci } 2493d0407baSopenharmony_ci if (u_tmp->tx_buf) { 2503d0407baSopenharmony_ci /* this transfer needs space in TX bounce buffer */ 2513d0407baSopenharmony_ci tx_total += len_aligned; 2523d0407baSopenharmony_ci if (tx_total > bufsiz) { 2533d0407baSopenharmony_ci status = -EMSGSIZE; 2543d0407baSopenharmony_ci goto done; 2553d0407baSopenharmony_ci } 2563d0407baSopenharmony_ci k_tmp->tx_buf = tx_buf; 2573d0407baSopenharmony_ci if (copy_from_user(tx_buf, (const u8 __user *)(uintptr_t)u_tmp->tx_buf, u_tmp->len)) { 2583d0407baSopenharmony_ci goto done; 2593d0407baSopenharmony_ci } 2603d0407baSopenharmony_ci tx_buf += len_aligned; 2613d0407baSopenharmony_ci } 2623d0407baSopenharmony_ci 2633d0407baSopenharmony_ci k_tmp->cs_change = !!u_tmp->cs_change; 2643d0407baSopenharmony_ci k_tmp->tx_nbits = u_tmp->tx_nbits; 2653d0407baSopenharmony_ci k_tmp->rx_nbits = u_tmp->rx_nbits; 2663d0407baSopenharmony_ci k_tmp->bits_per_word = u_tmp->bits_per_word; 2673d0407baSopenharmony_ci k_tmp->delay.value = u_tmp->delay_usecs; 2683d0407baSopenharmony_ci k_tmp->delay.unit = SPI_DELAY_UNIT_USECS; 2693d0407baSopenharmony_ci k_tmp->speed_hz = u_tmp->speed_hz; 2703d0407baSopenharmony_ci k_tmp->word_delay.value = u_tmp->word_delay_usecs; 2713d0407baSopenharmony_ci k_tmp->word_delay.unit = SPI_DELAY_UNIT_USECS; 2723d0407baSopenharmony_ci if (!k_tmp->speed_hz) { 2733d0407baSopenharmony_ci k_tmp->speed_hz = spidev->speed_hz; 2743d0407baSopenharmony_ci } 2753d0407baSopenharmony_ci#ifdef VERBOSE 2763d0407baSopenharmony_ci dev_dbg(&spidev->spi->dev, " xfer len %u %s%s%s%dbits %u usec %u usec %uHz\n", k_tmp->len, 2773d0407baSopenharmony_ci k_tmp->rx_buf ? "rx " : "", k_tmp->tx_buf ? "tx " : "", k_tmp->cs_change ? "cs " : "", 2783d0407baSopenharmony_ci k_tmp->bits_per_word ?: spidev->spi->bits_per_word, k_tmp->delay.value, k_tmp->word_delay.value, 2793d0407baSopenharmony_ci k_tmp->speed_hz ?: spidev->spi->max_speed_hz); 2803d0407baSopenharmony_ci#endif 2813d0407baSopenharmony_ci spi_message_add_tail(k_tmp, &msg); 2823d0407baSopenharmony_ci } 2833d0407baSopenharmony_ci 2843d0407baSopenharmony_ci status = spidev_sync(spidev, &msg); 2853d0407baSopenharmony_ci if (status < 0) { 2863d0407baSopenharmony_ci goto done; 2873d0407baSopenharmony_ci } 2883d0407baSopenharmony_ci 2893d0407baSopenharmony_ci /* copy any rx data out of bounce buffer */ 2903d0407baSopenharmony_ci for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; n; n--, k_tmp++, u_tmp++) { 2913d0407baSopenharmony_ci if (u_tmp->rx_buf) { 2923d0407baSopenharmony_ci if (copy_to_user((u8 __user *)(uintptr_t)u_tmp->rx_buf, k_tmp->rx_buf, u_tmp->len)) { 2933d0407baSopenharmony_ci status = -EFAULT; 2943d0407baSopenharmony_ci goto done; 2953d0407baSopenharmony_ci } 2963d0407baSopenharmony_ci } 2973d0407baSopenharmony_ci } 2983d0407baSopenharmony_ci status = total; 2993d0407baSopenharmony_ci 3003d0407baSopenharmony_cidone: 3013d0407baSopenharmony_ci kfree(k_xfers); 3023d0407baSopenharmony_ci return status; 3033d0407baSopenharmony_ci} 3043d0407baSopenharmony_ci 3053d0407baSopenharmony_cistatic struct spi_ioc_transfer *spidev_get_ioc_message(unsigned int cmd, struct spi_ioc_transfer __user *u_ioc, 3063d0407baSopenharmony_ci unsigned *n_ioc) 3073d0407baSopenharmony_ci{ 3083d0407baSopenharmony_ci u32 tmp; 3093d0407baSopenharmony_ci 3103d0407baSopenharmony_ci /* Check type, command number and direction */ 3113d0407baSopenharmony_ci if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC || _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) || _IOC_DIR(cmd) != _IOC_WRITE) { 3123d0407baSopenharmony_ci return ERR_PTR(-ENOTTY); 3133d0407baSopenharmony_ci } 3143d0407baSopenharmony_ci 3153d0407baSopenharmony_ci tmp = _IOC_SIZE(cmd); 3163d0407baSopenharmony_ci if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) { 3173d0407baSopenharmony_ci return ERR_PTR(-EINVAL); 3183d0407baSopenharmony_ci } 3193d0407baSopenharmony_ci *n_ioc = tmp / sizeof(struct spi_ioc_transfer); 3203d0407baSopenharmony_ci if (*n_ioc == 0) { 3213d0407baSopenharmony_ci return NULL; 3223d0407baSopenharmony_ci } 3233d0407baSopenharmony_ci 3243d0407baSopenharmony_ci /* copy into scratch area */ 3253d0407baSopenharmony_ci return memdup_user(u_ioc, tmp); 3263d0407baSopenharmony_ci} 3273d0407baSopenharmony_ci 3283d0407baSopenharmony_cistatic long spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 3293d0407baSopenharmony_ci{ 3303d0407baSopenharmony_ci int retval = 0; 3313d0407baSopenharmony_ci struct spidev_data *spidev; 3323d0407baSopenharmony_ci struct spi_device *spi; 3333d0407baSopenharmony_ci u32 tmp; 3343d0407baSopenharmony_ci unsigned n_ioc; 3353d0407baSopenharmony_ci struct spi_ioc_transfer *ioc; 3363d0407baSopenharmony_ci 3373d0407baSopenharmony_ci /* Check type and command number */ 3383d0407baSopenharmony_ci if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) { 3393d0407baSopenharmony_ci return -ENOTTY; 3403d0407baSopenharmony_ci } 3413d0407baSopenharmony_ci 3423d0407baSopenharmony_ci /* guard against device removal before, or while, 3433d0407baSopenharmony_ci * we issue this ioctl. 3443d0407baSopenharmony_ci */ 3453d0407baSopenharmony_ci spidev = filp->private_data; 3463d0407baSopenharmony_ci spin_lock_irq(&spidev->spi_lock); 3473d0407baSopenharmony_ci spi = spi_dev_get(spidev->spi); 3483d0407baSopenharmony_ci spin_unlock_irq(&spidev->spi_lock); 3493d0407baSopenharmony_ci 3503d0407baSopenharmony_ci if (spi == NULL) { 3513d0407baSopenharmony_ci return -ESHUTDOWN; 3523d0407baSopenharmony_ci } 3533d0407baSopenharmony_ci 3543d0407baSopenharmony_ci /* use the buffer lock here for triple duty: 3553d0407baSopenharmony_ci * - prevent I/O (from us) so calling spi_setup() is safe; 3563d0407baSopenharmony_ci * - prevent concurrent SPI_IOC_WR_* from morphing 3573d0407baSopenharmony_ci * data fields while SPI_IOC_RD_* reads them; 3583d0407baSopenharmony_ci * - SPI_IOC_MESSAGE needs the buffer locked "normally". 3593d0407baSopenharmony_ci */ 3603d0407baSopenharmony_ci mutex_lock(&spidev->buf_lock); 3613d0407baSopenharmony_ci 3623d0407baSopenharmony_ci switch (cmd) { 3633d0407baSopenharmony_ci /* read requests */ 3643d0407baSopenharmony_ci case SPI_IOC_RD_MODE: 3653d0407baSopenharmony_ci retval = put_user(spi->mode & SPI_MODE_MASK, (__u8 __user *)arg); 3663d0407baSopenharmony_ci break; 3673d0407baSopenharmony_ci case SPI_IOC_RD_MODE32: 3683d0407baSopenharmony_ci retval = put_user(spi->mode & SPI_MODE_MASK, (__u32 __user *)arg); 3693d0407baSopenharmony_ci break; 3703d0407baSopenharmony_ci case SPI_IOC_RD_LSB_FIRST: 3713d0407baSopenharmony_ci retval = put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, (__u8 __user *)arg); 3723d0407baSopenharmony_ci break; 3733d0407baSopenharmony_ci case SPI_IOC_RD_BITS_PER_WORD: 3743d0407baSopenharmony_ci retval = put_user(spi->bits_per_word, (__u8 __user *)arg); 3753d0407baSopenharmony_ci break; 3763d0407baSopenharmony_ci case SPI_IOC_RD_MAX_SPEED_HZ: 3773d0407baSopenharmony_ci retval = put_user(spidev->speed_hz, (__u32 __user *)arg); 3783d0407baSopenharmony_ci break; 3793d0407baSopenharmony_ci 3803d0407baSopenharmony_ci /* write requests */ 3813d0407baSopenharmony_ci case SPI_IOC_WR_MODE: 3823d0407baSopenharmony_ci case SPI_IOC_WR_MODE32: 3833d0407baSopenharmony_ci if (cmd == SPI_IOC_WR_MODE) { 3843d0407baSopenharmony_ci retval = get_user(tmp, (u8 __user *)arg); 3853d0407baSopenharmony_ci } else { 3863d0407baSopenharmony_ci retval = get_user(tmp, (u32 __user *)arg); 3873d0407baSopenharmony_ci } 3883d0407baSopenharmony_ci if (retval == 0) { 3893d0407baSopenharmony_ci struct spi_controller *ctlr = spi->controller; 3903d0407baSopenharmony_ci u32 save = spi->mode; 3913d0407baSopenharmony_ci 3923d0407baSopenharmony_ci if (tmp & ~SPI_MODE_MASK) { 3933d0407baSopenharmony_ci retval = -EINVAL; 3943d0407baSopenharmony_ci break; 3953d0407baSopenharmony_ci } 3963d0407baSopenharmony_ci 3973d0407baSopenharmony_ci if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods && ctlr->cs_gpiods[spi->chip_select]) { 3983d0407baSopenharmony_ci tmp |= SPI_CS_HIGH; 3993d0407baSopenharmony_ci } 4003d0407baSopenharmony_ci 4013d0407baSopenharmony_ci tmp |= spi->mode & ~SPI_MODE_MASK; 4023d0407baSopenharmony_ci spi->mode = (u16)tmp; 4033d0407baSopenharmony_ci retval = spi_setup(spi); 4043d0407baSopenharmony_ci if (retval < 0) { 4053d0407baSopenharmony_ci spi->mode = save; 4063d0407baSopenharmony_ci } else { 4073d0407baSopenharmony_ci dev_dbg(&spi->dev, "spi mode %x\n", tmp); 4083d0407baSopenharmony_ci } 4093d0407baSopenharmony_ci } 4103d0407baSopenharmony_ci break; 4113d0407baSopenharmony_ci case SPI_IOC_WR_LSB_FIRST: 4123d0407baSopenharmony_ci retval = get_user(tmp, (__u8 __user *)arg); 4133d0407baSopenharmony_ci if (retval == 0) { 4143d0407baSopenharmony_ci u32 save = spi->mode; 4153d0407baSopenharmony_ci 4163d0407baSopenharmony_ci if (tmp) { 4173d0407baSopenharmony_ci spi->mode |= SPI_LSB_FIRST; 4183d0407baSopenharmony_ci } else { 4193d0407baSopenharmony_ci spi->mode &= ~SPI_LSB_FIRST; 4203d0407baSopenharmony_ci } 4213d0407baSopenharmony_ci retval = spi_setup(spi); 4223d0407baSopenharmony_ci if (retval < 0) { 4233d0407baSopenharmony_ci spi->mode = save; 4243d0407baSopenharmony_ci } else { 4253d0407baSopenharmony_ci dev_dbg(&spi->dev, "%csb first\n", tmp ? 'l' : 'm'); 4263d0407baSopenharmony_ci } 4273d0407baSopenharmony_ci } 4283d0407baSopenharmony_ci break; 4293d0407baSopenharmony_ci case SPI_IOC_WR_BITS_PER_WORD: 4303d0407baSopenharmony_ci retval = get_user(tmp, (__u8 __user *)arg); 4313d0407baSopenharmony_ci if (retval == 0) { 4323d0407baSopenharmony_ci u8 save = spi->bits_per_word; 4333d0407baSopenharmony_ci 4343d0407baSopenharmony_ci spi->bits_per_word = tmp; 4353d0407baSopenharmony_ci retval = spi_setup(spi); 4363d0407baSopenharmony_ci if (retval < 0) { 4373d0407baSopenharmony_ci spi->bits_per_word = save; 4383d0407baSopenharmony_ci } else { 4393d0407baSopenharmony_ci dev_dbg(&spi->dev, "%d bits per word\n", tmp); 4403d0407baSopenharmony_ci } 4413d0407baSopenharmony_ci } 4423d0407baSopenharmony_ci break; 4433d0407baSopenharmony_ci case SPI_IOC_WR_MAX_SPEED_HZ: 4443d0407baSopenharmony_ci retval = get_user(tmp, (__u32 __user *)arg); 4453d0407baSopenharmony_ci if (retval == 0) { 4463d0407baSopenharmony_ci u32 save = spi->max_speed_hz; 4473d0407baSopenharmony_ci 4483d0407baSopenharmony_ci spi->max_speed_hz = tmp; 4493d0407baSopenharmony_ci retval = spi_setup(spi); 4503d0407baSopenharmony_ci if (retval == 0) { 4513d0407baSopenharmony_ci spidev->speed_hz = tmp; 4523d0407baSopenharmony_ci dev_dbg(&spi->dev, "%d Hz (max)\n", spidev->speed_hz); 4533d0407baSopenharmony_ci } 4543d0407baSopenharmony_ci spi->max_speed_hz = save; 4553d0407baSopenharmony_ci } 4563d0407baSopenharmony_ci break; 4573d0407baSopenharmony_ci 4583d0407baSopenharmony_ci default: 4593d0407baSopenharmony_ci /* segmented and/or full-duplex I/O request */ 4603d0407baSopenharmony_ci /* Check message and copy into scratch area */ 4613d0407baSopenharmony_ci ioc = spidev_get_ioc_message(cmd, (struct spi_ioc_transfer __user *)arg, &n_ioc); 4623d0407baSopenharmony_ci if (IS_ERR(ioc)) { 4633d0407baSopenharmony_ci retval = PTR_ERR(ioc); 4643d0407baSopenharmony_ci break; 4653d0407baSopenharmony_ci } 4663d0407baSopenharmony_ci if (!ioc) { 4673d0407baSopenharmony_ci break; /* n_ioc is also 0 */ 4683d0407baSopenharmony_ci } 4693d0407baSopenharmony_ci 4703d0407baSopenharmony_ci /* translate to spi_message, execute */ 4713d0407baSopenharmony_ci retval = spidev_message(spidev, ioc, n_ioc); 4723d0407baSopenharmony_ci kfree(ioc); 4733d0407baSopenharmony_ci break; 4743d0407baSopenharmony_ci } 4753d0407baSopenharmony_ci 4763d0407baSopenharmony_ci mutex_unlock(&spidev->buf_lock); 4773d0407baSopenharmony_ci spi_dev_put(spi); 4783d0407baSopenharmony_ci return retval; 4793d0407baSopenharmony_ci} 4803d0407baSopenharmony_ci 4813d0407baSopenharmony_ci#ifdef CONFIG_COMPAT 4823d0407baSopenharmony_cistatic long spidev_compat_ioc_message(struct file *filp, unsigned int cmd, unsigned long arg) 4833d0407baSopenharmony_ci{ 4843d0407baSopenharmony_ci struct spi_ioc_transfer __user *u_ioc; 4853d0407baSopenharmony_ci int retval = 0; 4863d0407baSopenharmony_ci struct spidev_data *spidev; 4873d0407baSopenharmony_ci struct spi_device *spi; 4883d0407baSopenharmony_ci unsigned n_ioc, n; 4893d0407baSopenharmony_ci struct spi_ioc_transfer *ioc; 4903d0407baSopenharmony_ci 4913d0407baSopenharmony_ci u_ioc = (struct spi_ioc_transfer __user *)compat_ptr(arg); 4923d0407baSopenharmony_ci 4933d0407baSopenharmony_ci /* guard against device removal before, or while, 4943d0407baSopenharmony_ci * we issue this ioctl. 4953d0407baSopenharmony_ci */ 4963d0407baSopenharmony_ci spidev = filp->private_data; 4973d0407baSopenharmony_ci spin_lock_irq(&spidev->spi_lock); 4983d0407baSopenharmony_ci spi = spi_dev_get(spidev->spi); 4993d0407baSopenharmony_ci spin_unlock_irq(&spidev->spi_lock); 5003d0407baSopenharmony_ci 5013d0407baSopenharmony_ci if (spi == NULL) { 5023d0407baSopenharmony_ci return -ESHUTDOWN; 5033d0407baSopenharmony_ci } 5043d0407baSopenharmony_ci 5053d0407baSopenharmony_ci /* SPI_IOC_MESSAGE needs the buffer locked "normally" */ 5063d0407baSopenharmony_ci mutex_lock(&spidev->buf_lock); 5073d0407baSopenharmony_ci 5083d0407baSopenharmony_ci /* Check message and copy into scratch area */ 5093d0407baSopenharmony_ci ioc = spidev_get_ioc_message(cmd, u_ioc, &n_ioc); 5103d0407baSopenharmony_ci if (IS_ERR(ioc)) { 5113d0407baSopenharmony_ci retval = PTR_ERR(ioc); 5123d0407baSopenharmony_ci goto done; 5133d0407baSopenharmony_ci } 5143d0407baSopenharmony_ci if (!ioc) { 5153d0407baSopenharmony_ci goto done; /* n_ioc is also 0 */ 5163d0407baSopenharmony_ci } 5173d0407baSopenharmony_ci 5183d0407baSopenharmony_ci /* Convert buffer pointers */ 5193d0407baSopenharmony_ci for (n = 0; n < n_ioc; n++) { 5203d0407baSopenharmony_ci ioc[n].rx_buf = (uintptr_t)compat_ptr(ioc[n].rx_buf); 5213d0407baSopenharmony_ci ioc[n].tx_buf = (uintptr_t)compat_ptr(ioc[n].tx_buf); 5223d0407baSopenharmony_ci } 5233d0407baSopenharmony_ci 5243d0407baSopenharmony_ci /* translate to spi_message, execute */ 5253d0407baSopenharmony_ci retval = spidev_message(spidev, ioc, n_ioc); 5263d0407baSopenharmony_ci kfree(ioc); 5273d0407baSopenharmony_ci 5283d0407baSopenharmony_cidone: 5293d0407baSopenharmony_ci mutex_unlock(&spidev->buf_lock); 5303d0407baSopenharmony_ci spi_dev_put(spi); 5313d0407baSopenharmony_ci return retval; 5323d0407baSopenharmony_ci} 5333d0407baSopenharmony_ci 5343d0407baSopenharmony_cistatic long spidev_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 5353d0407baSopenharmony_ci{ 5363d0407baSopenharmony_ci if (_IOC_TYPE(cmd) == SPI_IOC_MAGIC && _IOC_NR(cmd) == _IOC_NR(SPI_IOC_MESSAGE(0)) && _IOC_DIR(cmd) == _IOC_WRITE) { 5373d0407baSopenharmony_ci return spidev_compat_ioc_message(filp, cmd, arg); 5383d0407baSopenharmony_ci } 5393d0407baSopenharmony_ci 5403d0407baSopenharmony_ci return spidev_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); 5413d0407baSopenharmony_ci} 5423d0407baSopenharmony_ci#else 5433d0407baSopenharmony_ci#define spidev_compat_ioctl NULL 5443d0407baSopenharmony_ci#endif /* CONFIG_COMPAT */ 5453d0407baSopenharmony_ci 5463d0407baSopenharmony_cistatic int spidev_open(struct inode *inode, struct file *filp) 5473d0407baSopenharmony_ci{ 5483d0407baSopenharmony_ci struct spidev_data *spidev; 5493d0407baSopenharmony_ci int status = -ENXIO; 5503d0407baSopenharmony_ci 5513d0407baSopenharmony_ci mutex_lock(&device_list_lock); 5523d0407baSopenharmony_ci 5533d0407baSopenharmony_ci list_for_each_entry(spidev, &device_list, device_entry) 5543d0407baSopenharmony_ci { 5553d0407baSopenharmony_ci if (spidev->devt == inode->i_rdev) { 5563d0407baSopenharmony_ci status = 0; 5573d0407baSopenharmony_ci break; 5583d0407baSopenharmony_ci } 5593d0407baSopenharmony_ci } 5603d0407baSopenharmony_ci 5613d0407baSopenharmony_ci if (status) { 5623d0407baSopenharmony_ci pr_debug("spidev: nothing for minor %d\n", iminor(inode)); 5633d0407baSopenharmony_ci goto err_find_dev; 5643d0407baSopenharmony_ci } 5653d0407baSopenharmony_ci 5663d0407baSopenharmony_ci if (!spidev->tx_buffer) { 5673d0407baSopenharmony_ci spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL); 5683d0407baSopenharmony_ci if (!spidev->tx_buffer) { 5693d0407baSopenharmony_ci dev_dbg(&spidev->spi->dev, "open/ENOMEM\n"); 5703d0407baSopenharmony_ci status = -ENOMEM; 5713d0407baSopenharmony_ci goto err_find_dev; 5723d0407baSopenharmony_ci } 5733d0407baSopenharmony_ci } 5743d0407baSopenharmony_ci 5753d0407baSopenharmony_ci if (!spidev->rx_buffer) { 5763d0407baSopenharmony_ci spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL); 5773d0407baSopenharmony_ci if (!spidev->rx_buffer) { 5783d0407baSopenharmony_ci dev_dbg(&spidev->spi->dev, "open/ENOMEM\n"); 5793d0407baSopenharmony_ci status = -ENOMEM; 5803d0407baSopenharmony_ci goto err_alloc_rx_buf; 5813d0407baSopenharmony_ci } 5823d0407baSopenharmony_ci } 5833d0407baSopenharmony_ci 5843d0407baSopenharmony_ci spidev->users++; 5853d0407baSopenharmony_ci filp->private_data = spidev; 5863d0407baSopenharmony_ci stream_open(inode, filp); 5873d0407baSopenharmony_ci 5883d0407baSopenharmony_ci mutex_unlock(&device_list_lock); 5893d0407baSopenharmony_ci return 0; 5903d0407baSopenharmony_ci 5913d0407baSopenharmony_cierr_alloc_rx_buf: 5923d0407baSopenharmony_ci kfree(spidev->tx_buffer); 5933d0407baSopenharmony_ci spidev->tx_buffer = NULL; 5943d0407baSopenharmony_cierr_find_dev: 5953d0407baSopenharmony_ci mutex_unlock(&device_list_lock); 5963d0407baSopenharmony_ci return status; 5973d0407baSopenharmony_ci} 5983d0407baSopenharmony_ci 5993d0407baSopenharmony_cistatic int spidev_release(struct inode *inode, struct file *filp) 6003d0407baSopenharmony_ci{ 6013d0407baSopenharmony_ci struct spidev_data *spidev; 6023d0407baSopenharmony_ci int dofree; 6033d0407baSopenharmony_ci 6043d0407baSopenharmony_ci mutex_lock(&device_list_lock); 6053d0407baSopenharmony_ci spidev = filp->private_data; 6063d0407baSopenharmony_ci filp->private_data = NULL; 6073d0407baSopenharmony_ci 6083d0407baSopenharmony_ci spin_lock_irq(&spidev->spi_lock); 6093d0407baSopenharmony_ci /* ... after we unbound from the underlying device? */ 6103d0407baSopenharmony_ci dofree = (spidev->spi == NULL); 6113d0407baSopenharmony_ci spin_unlock_irq(&spidev->spi_lock); 6123d0407baSopenharmony_ci 6133d0407baSopenharmony_ci /* last close? */ 6143d0407baSopenharmony_ci spidev->users--; 6153d0407baSopenharmony_ci if (!spidev->users) { 6163d0407baSopenharmony_ci kfree(spidev->tx_buffer); 6173d0407baSopenharmony_ci spidev->tx_buffer = NULL; 6183d0407baSopenharmony_ci 6193d0407baSopenharmony_ci kfree(spidev->rx_buffer); 6203d0407baSopenharmony_ci spidev->rx_buffer = NULL; 6213d0407baSopenharmony_ci 6223d0407baSopenharmony_ci if (dofree) { 6233d0407baSopenharmony_ci kfree(spidev); 6243d0407baSopenharmony_ci } else { 6253d0407baSopenharmony_ci spidev->speed_hz = spidev->spi->max_speed_hz; 6263d0407baSopenharmony_ci } 6273d0407baSopenharmony_ci } 6283d0407baSopenharmony_ci#ifdef CONFIG_SPI_SLAVE 6293d0407baSopenharmony_ci if (!dofree) { 6303d0407baSopenharmony_ci spi_slave_abort(spidev->spi); 6313d0407baSopenharmony_ci } 6323d0407baSopenharmony_ci#endif 6333d0407baSopenharmony_ci mutex_unlock(&device_list_lock); 6343d0407baSopenharmony_ci 6353d0407baSopenharmony_ci return 0; 6363d0407baSopenharmony_ci} 6373d0407baSopenharmony_ci 6383d0407baSopenharmony_cistatic const struct file_operations spidev_fops = { 6393d0407baSopenharmony_ci .owner = THIS_MODULE, 6403d0407baSopenharmony_ci /* REVISIT switch to aio primitives, so that userspace 6413d0407baSopenharmony_ci * gets more complete API coverage. It'll simplify things 6423d0407baSopenharmony_ci * too, except for the locking. 6433d0407baSopenharmony_ci */ 6443d0407baSopenharmony_ci .write = spidev_write, 6453d0407baSopenharmony_ci .read = spidev_read, 6463d0407baSopenharmony_ci .unlocked_ioctl = spidev_ioctl, 6473d0407baSopenharmony_ci .compat_ioctl = spidev_compat_ioctl, 6483d0407baSopenharmony_ci .open = spidev_open, 6493d0407baSopenharmony_ci .release = spidev_release, 6503d0407baSopenharmony_ci .llseek = no_llseek, 6513d0407baSopenharmony_ci}; 6523d0407baSopenharmony_ci 6533d0407baSopenharmony_ci/*-------------------------------------------------------------------------*/ 6543d0407baSopenharmony_ci 6553d0407baSopenharmony_ci/* The main reason to have this class is to make mdev/udev create the 6563d0407baSopenharmony_ci * /dev/spidevB.C character device nodes exposing our userspace API. 6573d0407baSopenharmony_ci * It also simplifies memory management. 6583d0407baSopenharmony_ci */ 6593d0407baSopenharmony_ci 6603d0407baSopenharmony_cistatic struct class *spidev_class; 6613d0407baSopenharmony_ci 6623d0407baSopenharmony_ci#ifdef CONFIG_OF 6633d0407baSopenharmony_cistatic const struct of_device_id spidev_dt_ids[] = { 6643d0407baSopenharmony_ci {.compatible = "rohm,dh2228fv"}, 6653d0407baSopenharmony_ci {.compatible = "lineartechnology,ltc2488"}, 6663d0407baSopenharmony_ci {.compatible = "ge,achc"}, 6673d0407baSopenharmony_ci {.compatible = "semtech,sx1301"}, 6683d0407baSopenharmony_ci {.compatible = "lwn,bk4"}, 6693d0407baSopenharmony_ci {.compatible = "dh,dhcom-board"}, 6703d0407baSopenharmony_ci {.compatible = "menlo,m53cpld"}, 6713d0407baSopenharmony_ci {.compatible = "rockchip,spidev"}, 6723d0407baSopenharmony_ci {}, 6733d0407baSopenharmony_ci}; 6743d0407baSopenharmony_ciMODULE_DEVICE_TABLE(of, spidev_dt_ids); 6753d0407baSopenharmony_ci#endif 6763d0407baSopenharmony_ci 6773d0407baSopenharmony_ci#ifdef CONFIG_ACPI 6783d0407baSopenharmony_ci 6793d0407baSopenharmony_ci/* Dummy SPI devices not to be used in production systems */ 6803d0407baSopenharmony_ci#define SPIDEV_ACPI_DUMMY 1 6813d0407baSopenharmony_ci 6823d0407baSopenharmony_cistatic const struct acpi_device_id spidev_acpi_ids[] = { 6833d0407baSopenharmony_ci /* 6843d0407baSopenharmony_ci * The ACPI SPT000* devices are only meant for development and 6853d0407baSopenharmony_ci * testing. Systems used in production should have a proper ACPI 6863d0407baSopenharmony_ci * description of the connected peripheral and they should also use 6873d0407baSopenharmony_ci * a proper driver instead of poking directly to the SPI bus. 6883d0407baSopenharmony_ci */ 6893d0407baSopenharmony_ci {"SPT0001", SPIDEV_ACPI_DUMMY}, 6903d0407baSopenharmony_ci {"SPT0002", SPIDEV_ACPI_DUMMY}, 6913d0407baSopenharmony_ci {"SPT0003", SPIDEV_ACPI_DUMMY}, 6923d0407baSopenharmony_ci {}, 6933d0407baSopenharmony_ci}; 6943d0407baSopenharmony_ciMODULE_DEVICE_TABLE(acpi, spidev_acpi_ids); 6953d0407baSopenharmony_ci 6963d0407baSopenharmony_cistatic void spidev_probe_acpi(struct spi_device *spi) 6973d0407baSopenharmony_ci{ 6983d0407baSopenharmony_ci const struct acpi_device_id *id; 6993d0407baSopenharmony_ci 7003d0407baSopenharmony_ci if (!has_acpi_companion(&spi->dev)) { 7013d0407baSopenharmony_ci return; 7023d0407baSopenharmony_ci } 7033d0407baSopenharmony_ci 7043d0407baSopenharmony_ci id = acpi_match_device(spidev_acpi_ids, &spi->dev); 7053d0407baSopenharmony_ci if (WARN_ON(!id)) { 7063d0407baSopenharmony_ci return; 7073d0407baSopenharmony_ci } 7083d0407baSopenharmony_ci 7093d0407baSopenharmony_ci if (id->driver_data == SPIDEV_ACPI_DUMMY) { 7103d0407baSopenharmony_ci dev_warn(&spi->dev, "do not use this driver in production systems!\n"); 7113d0407baSopenharmony_ci } 7123d0407baSopenharmony_ci} 7133d0407baSopenharmony_ci#else 7143d0407baSopenharmony_cistatic inline void spidev_probe_acpi(struct spi_device *spi) 7153d0407baSopenharmony_ci{ 7163d0407baSopenharmony_ci} 7173d0407baSopenharmony_ci#endif 7183d0407baSopenharmony_ci 7193d0407baSopenharmony_ci/*-------------------------------------------------------------------------*/ 7203d0407baSopenharmony_ci 7213d0407baSopenharmony_cistatic int spidev_probe(struct spi_device *spi) 7223d0407baSopenharmony_ci{ 7233d0407baSopenharmony_ci struct spidev_data *spidev; 7243d0407baSopenharmony_ci int status; 7253d0407baSopenharmony_ci unsigned long minor; 7263d0407baSopenharmony_ci 7273d0407baSopenharmony_ci /* 7283d0407baSopenharmony_ci * spidev should never be referenced in DT without a specific 7293d0407baSopenharmony_ci * compatible string, it is a Linux implementation thing 7303d0407baSopenharmony_ci * rather than a description of the hardware. 7313d0407baSopenharmony_ci */ 7323d0407baSopenharmony_ci WARN(spi->dev.of_node && of_device_is_compatible(spi->dev.of_node, "spidev"), 7333d0407baSopenharmony_ci "%pOF: buggy DT: spidev listed directly in DT\n", spi->dev.of_node); 7343d0407baSopenharmony_ci 7353d0407baSopenharmony_ci spidev_probe_acpi(spi); 7363d0407baSopenharmony_ci 7373d0407baSopenharmony_ci /* Allocate driver data */ 7383d0407baSopenharmony_ci spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); 7393d0407baSopenharmony_ci if (!spidev) { 7403d0407baSopenharmony_ci return -ENOMEM; 7413d0407baSopenharmony_ci } 7423d0407baSopenharmony_ci 7433d0407baSopenharmony_ci /* Initialize the driver data */ 7443d0407baSopenharmony_ci spidev->spi = spi; 7453d0407baSopenharmony_ci spin_lock_init(&spidev->spi_lock); 7463d0407baSopenharmony_ci mutex_init(&spidev->buf_lock); 7473d0407baSopenharmony_ci 7483d0407baSopenharmony_ci INIT_LIST_HEAD(&spidev->device_entry); 7493d0407baSopenharmony_ci 7503d0407baSopenharmony_ci /* If we can allocate a minor number, hook up this device. 7513d0407baSopenharmony_ci * Reusing minors is fine so long as udev or mdev is working. 7523d0407baSopenharmony_ci */ 7533d0407baSopenharmony_ci mutex_lock(&device_list_lock); 7543d0407baSopenharmony_ci minor = find_first_zero_bit(minors, N_SPI_MINORS); 7553d0407baSopenharmony_ci if (minor < N_SPI_MINORS) { 7563d0407baSopenharmony_ci struct device *dev; 7573d0407baSopenharmony_ci 7583d0407baSopenharmony_ci spidev->devt = MKDEV(SPIDEV_MAJOR, minor); 7593d0407baSopenharmony_ci dev = device_create(spidev_class, &spi->dev, spidev->devt, spidev, "spidev%d.%d", spi->master->bus_num, 7603d0407baSopenharmony_ci spi->chip_select); 7613d0407baSopenharmony_ci status = PTR_ERR_OR_ZERO(dev); 7623d0407baSopenharmony_ci } else { 7633d0407baSopenharmony_ci dev_dbg(&spi->dev, "no minor number available!\n"); 7643d0407baSopenharmony_ci status = -ENODEV; 7653d0407baSopenharmony_ci } 7663d0407baSopenharmony_ci if (status == 0) { 7673d0407baSopenharmony_ci set_bit(minor, minors); 7683d0407baSopenharmony_ci list_add(&spidev->device_entry, &device_list); 7693d0407baSopenharmony_ci } 7703d0407baSopenharmony_ci mutex_unlock(&device_list_lock); 7713d0407baSopenharmony_ci 7723d0407baSopenharmony_ci spidev->speed_hz = spi->max_speed_hz; 7733d0407baSopenharmony_ci 7743d0407baSopenharmony_ci if (status == 0) { 7753d0407baSopenharmony_ci spi_set_drvdata(spi, spidev); 7763d0407baSopenharmony_ci } else { 7773d0407baSopenharmony_ci kfree(spidev); 7783d0407baSopenharmony_ci } 7793d0407baSopenharmony_ci 7803d0407baSopenharmony_ci return status; 7813d0407baSopenharmony_ci} 7823d0407baSopenharmony_ci 7833d0407baSopenharmony_cistatic int spidev_remove(struct spi_device *spi) 7843d0407baSopenharmony_ci{ 7853d0407baSopenharmony_ci struct spidev_data *spidev = spi_get_drvdata(spi); 7863d0407baSopenharmony_ci 7873d0407baSopenharmony_ci /* prevent new opens */ 7883d0407baSopenharmony_ci mutex_lock(&device_list_lock); 7893d0407baSopenharmony_ci /* make sure ops on existing fds can abort cleanly */ 7903d0407baSopenharmony_ci spin_lock_irq(&spidev->spi_lock); 7913d0407baSopenharmony_ci spidev->spi = NULL; 7923d0407baSopenharmony_ci spin_unlock_irq(&spidev->spi_lock); 7933d0407baSopenharmony_ci 7943d0407baSopenharmony_ci list_del(&spidev->device_entry); 7953d0407baSopenharmony_ci device_destroy(spidev_class, spidev->devt); 7963d0407baSopenharmony_ci clear_bit(MINOR(spidev->devt), minors); 7973d0407baSopenharmony_ci if (spidev->users == 0) { 7983d0407baSopenharmony_ci kfree(spidev); 7993d0407baSopenharmony_ci } 8003d0407baSopenharmony_ci mutex_unlock(&device_list_lock); 8013d0407baSopenharmony_ci 8023d0407baSopenharmony_ci return 0; 8033d0407baSopenharmony_ci} 8043d0407baSopenharmony_ci 8053d0407baSopenharmony_cistatic struct spi_driver spidev_spi_driver = { 8063d0407baSopenharmony_ci .driver = 8073d0407baSopenharmony_ci { 8083d0407baSopenharmony_ci .name = "spidev", 8093d0407baSopenharmony_ci .of_match_table = of_match_ptr(spidev_dt_ids), 8103d0407baSopenharmony_ci .acpi_match_table = ACPI_PTR(spidev_acpi_ids), 8113d0407baSopenharmony_ci }, 8123d0407baSopenharmony_ci .probe = spidev_probe, 8133d0407baSopenharmony_ci .remove = spidev_remove, 8143d0407baSopenharmony_ci 8153d0407baSopenharmony_ci /* NOTE: suspend/resume methods are not necessary here. 8163d0407baSopenharmony_ci * We don't do anything except pass the requests to/from 8173d0407baSopenharmony_ci * the underlying controller. The refrigerator handles 8183d0407baSopenharmony_ci * most issues; the controller driver handles the rest. 8193d0407baSopenharmony_ci */ 8203d0407baSopenharmony_ci}; 8213d0407baSopenharmony_ci 8223d0407baSopenharmony_ci/*-------------------------------------------------------------------------*/ 8233d0407baSopenharmony_ci 8243d0407baSopenharmony_cistatic int __init spidev_init(void) 8253d0407baSopenharmony_ci{ 8263d0407baSopenharmony_ci int status; 8273d0407baSopenharmony_ci 8283d0407baSopenharmony_ci /* Claim our 256 reserved device numbers. Then register a class 8293d0407baSopenharmony_ci * that will key udev/mdev to add/remove /dev nodes. Last, register 8303d0407baSopenharmony_ci * the driver which manages those device numbers. 8313d0407baSopenharmony_ci */ 8323d0407baSopenharmony_ci BUILD_BUG_ON(N_SPI_MINORS > 256); 8333d0407baSopenharmony_ci status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); 8343d0407baSopenharmony_ci if (status < 0) { 8353d0407baSopenharmony_ci return status; 8363d0407baSopenharmony_ci } 8373d0407baSopenharmony_ci 8383d0407baSopenharmony_ci spidev_class = class_create(THIS_MODULE, "spidev"); 8393d0407baSopenharmony_ci if (IS_ERR(spidev_class)) { 8403d0407baSopenharmony_ci unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); 8413d0407baSopenharmony_ci return PTR_ERR(spidev_class); 8423d0407baSopenharmony_ci } 8433d0407baSopenharmony_ci 8443d0407baSopenharmony_ci status = spi_register_driver(&spidev_spi_driver); 8453d0407baSopenharmony_ci if (status < 0) { 8463d0407baSopenharmony_ci class_destroy(spidev_class); 8473d0407baSopenharmony_ci unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); 8483d0407baSopenharmony_ci } 8493d0407baSopenharmony_ci return status; 8503d0407baSopenharmony_ci} 8513d0407baSopenharmony_cimodule_init(spidev_init); 8523d0407baSopenharmony_ci 8533d0407baSopenharmony_cistatic void __exit spidev_exit(void) 8543d0407baSopenharmony_ci{ 8553d0407baSopenharmony_ci spi_unregister_driver(&spidev_spi_driver); 8563d0407baSopenharmony_ci class_destroy(spidev_class); 8573d0407baSopenharmony_ci unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name); 8583d0407baSopenharmony_ci} 8593d0407baSopenharmony_cimodule_exit(spidev_exit); 8603d0407baSopenharmony_ci 8613d0407baSopenharmony_ciMODULE_AUTHOR("Andrea Paterniani, <a.paterniani@swapp-eng.it>"); 8623d0407baSopenharmony_ciMODULE_DESCRIPTION("User mode SPI device interface"); 8633d0407baSopenharmony_ciMODULE_LICENSE("GPL"); 8643d0407baSopenharmony_ciMODULE_ALIAS("spi:spidev"); 865