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