/* * Copyright (C) 2015 The Android Open Source Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DRV_CLASS_NAME "contexthub" #define DRV_NAME "spich" #define SPICH_BUFFER_SIZE 4096 enum { SPICH_FLAG_TIMESTAMPS_ENABLED = 1, }; struct spich_data { dev_t devno; spinlock_t spi_lock; struct spi_device *spi; struct class *class; struct device *device; struct cdev cdev; unsigned users; struct mutex buf_lock; u8 *buffer; u8 *bufferrx; u32 flags; int sh2ap_irq; struct completion sh2ap_completion; struct gpio gpio_array[4]; struct wake_lock sh2ap_wakelock; struct wake_lock sh2ap_data_wakelock; enum { HUB_ACTIVE, /* driver has seen spich_suspend(), acquired the wakelock, * told the contexthub of our intention to suspend * and returned -EBUSY. */ HUB_SUSPENDING, /* contexthub has acknowledged our intention to suspend, will * no longer send traffic other than that for wakeup-sensors. * we're no longer holding the wakelock. */ HUB_SUSPENDED, /* driver has been resumed for a sufficient duration, * acquired the wakelock and told the contexthub to resume again. */ HUB_WAKING, } hub_state; struct timer_list resume_timer; int force_read; }; #define GPIO_IDX_AP2SH 0 #define GPIO_IDX_SH2AP 1 #define GPIO_IDX_BOOT0 2 #define GPIO_IDX_NRST 3 static int spich_suspend(struct spi_device *spi, pm_message_t state) { struct spich_data *spich = spi_get_drvdata(spi); dev_dbg(&spi->dev, "spich_suspend\n"); if ((spich->flags & SPICH_FLAG_TIMESTAMPS_ENABLED) == 0) { /* We're not in "contexthub" mode. Don't prevent the device * from entering suspend mode. The status of the GPIO pin * (SH2AP) is undefined. */ return 0; } del_timer(&spich->resume_timer); switch (spich->hub_state) { case HUB_ACTIVE: { wake_lock(&spich->sh2ap_wakelock); spich->hub_state = HUB_SUSPENDING; spich->force_read = 1; complete(&spich->sh2ap_completion); return -EBUSY; } case HUB_SUSPENDING: { /* SHOULD NEVER BE HERE, we're holding the wakelock */ break; } case HUB_SUSPENDED: { return 0; } case HUB_WAKING: { /* SHOULD NEVER BE HERE, we're holding the wakelock */ break; } } /* SHOULD NEVER BE HERE */ return 0; } static int spich_resume(struct spi_device *spi) { struct spich_data *spich = spi_get_drvdata(spi); dev_dbg(&spi->dev, "spich_resume\n"); if ((spich->flags & SPICH_FLAG_TIMESTAMPS_ENABLED) == 0) { /* We're not in "contexthub" mode. Don't prevent the device * from entering suspend mode. The status of the GPIO pin * (SH2AP) is undefined. */ return 0; } if (spich->hub_state == HUB_SUSPENDED) { mod_timer(&spich->resume_timer, jiffies + msecs_to_jiffies(1000)); } return 0; } static void spich_resume_timer_expired(unsigned long me) { struct spich_data *spich = (struct spich_data *)me; dev_dbg(&spich->spi->dev, "spich_resume_timer_expired\n"); switch (spich->hub_state) { case HUB_ACTIVE: { // SHOULD NEVER BE HERE break; } case HUB_SUSPENDING: { // SHOULD NEVER BE HERE break; } case HUB_SUSPENDED: { /* We stayed 'resumed' long enough to tell the hub to consider itself * resumed as well. */ wake_lock(&spich->sh2ap_wakelock); spich->hub_state = HUB_WAKING; spich->force_read = 1; complete(&spich->sh2ap_completion); break; } case HUB_WAKING: { // SHOULD NEVER BE HERE break; } } } static void spich_hub_suspended(struct spich_data *spich, int suspended) { /* The hub has now acknowledged being either suspended or resumed, * our driver receives this notification in one of the two transitional * states, HUB_SUSPENDING or HUB_WAKING. In both cases we're currently * holding the wakelock. */ if (suspended) { dev_dbg(&spich->spi->dev, "hub is now suspended\n"); spich->hub_state = HUB_SUSPENDED; mod_timer(&spich->resume_timer, jiffies + msecs_to_jiffies(1000)); } else { dev_dbg(&spich->spi->dev, "hub is now resumed\n"); spich->hub_state = HUB_ACTIVE; } wake_unlock(&spich->sh2ap_wakelock); } static irqreturn_t sh2ap_isr(int irq, void *data) { struct spich_data *spich = data; dev_dbg(&spich->spi->dev, "sh2ap_isr\n"); /* This ISR triggered on a falling edge, so the sh2ap line is now low. * We'll prevent the AP from going to suspend as long as the line is low * to ensure that the client has read all available data. */ if (spich->hub_state == HUB_SUSPENDED) { /* We only acquire the wakelock if the hub has acknowledged * its suspend state, i.e. all the traffic it sends must be * important. */ wake_lock(&spich->sh2ap_data_wakelock); } complete(&spich->sh2ap_completion); return IRQ_HANDLED; } static int spich_init_instance(struct spich_data *spich) { int status = 0; spich->buffer = kmalloc(SPICH_BUFFER_SIZE, GFP_KERNEL); if (!spich->buffer) { dev_err(&spich->spi->dev, "open/ENOMEM\n"); status = -ENOMEM; goto bail; } spich->bufferrx = kmalloc(SPICH_BUFFER_SIZE, GFP_KERNEL); if (!spich->bufferrx) { dev_err(&spich->spi->dev, "open/ENOMEM\n"); status = -ENOMEM; goto bail2; } status = gpio_request_array(spich->gpio_array, ARRAY_SIZE(spich->gpio_array)); if (status != 0) { dev_err(&spich->spi->dev, "open/gpio_request\n"); goto bail3; } spich->sh2ap_irq = gpio_to_irq(spich->gpio_array[GPIO_IDX_SH2AP].gpio); init_completion(&spich->sh2ap_completion); wake_lock_init( &spich->sh2ap_wakelock, WAKE_LOCK_SUSPEND, "sh2ap_wakelock"); wake_lock_init( &spich->sh2ap_data_wakelock, WAKE_LOCK_SUSPEND, "sh2ap_data_wakelock"); status = devm_request_irq(&spich->spi->dev, spich->sh2ap_irq, sh2ap_isr, IRQF_TRIGGER_FALLING, dev_name(&spich->spi->dev), spich); if (status != 0) { dev_err(&spich->spi->dev, "open/devm_request_irq\n"); goto bail4; } status = enable_irq_wake(spich->sh2ap_irq); if (status != 0) { dev_err(&spich->spi->dev, "open/enable_irq_wake FAILED\n"); goto bail5; } setup_timer( &spich->resume_timer, spich_resume_timer_expired, (unsigned long)spich); spich->hub_state = HUB_ACTIVE; spich->force_read = 0; return 0; bail5: devm_free_irq(&spich->spi->dev, spich->sh2ap_irq, spich); bail4: wake_lock_destroy(&spich->sh2ap_data_wakelock); wake_lock_destroy(&spich->sh2ap_wakelock); gpio_free_array(spich->gpio_array, ARRAY_SIZE(spich->gpio_array)); bail3: kfree(spich->bufferrx); spich->bufferrx = NULL; bail2: kfree(spich->buffer); spich->buffer = NULL; bail: return status; } static void spich_destroy_instance(struct spich_data *spich) { del_timer(&spich->resume_timer); devm_free_irq(&spich->spi->dev, spich->sh2ap_irq, spich); wake_lock_destroy(&spich->sh2ap_data_wakelock); wake_lock_destroy(&spich->sh2ap_wakelock); gpio_free_array(spich->gpio_array, ARRAY_SIZE(spich->gpio_array)); kfree(spich->buffer); spich->buffer = NULL; kfree(spich->bufferrx); spich->bufferrx = NULL; } static int spich_open(struct inode *inode, struct file *filp) { struct spich_data *spich; unsigned users; kuid_t kuid = current_uid(); spich = container_of(inode->i_cdev, struct spich_data, cdev); spin_lock_irq(&spich->spi_lock); users = spich->users++; /* Processes running as root (uid 0) can always open the device; * otherwise, non-root processes must wait until all other processes * close the device */ if ((kuid.val != 0) && (users != 0)) { --spich->users; spin_unlock_irq(&spich->spi_lock); return -EBUSY; } spin_unlock_irq(&spich->spi_lock); filp->private_data = spich; nonseekable_open(inode, filp); return 0; } static int spich_release(struct inode *inode, struct file *filp) { struct spich_data *spich; unsigned users; spich = filp->private_data; filp->private_data = NULL; spin_lock_irq(&spich->spi_lock); users = --spich->users; spin_unlock_irq(&spich->spi_lock); if (users == 0) { if (spich->spi == NULL) { spich_destroy_instance(spich); mutex_destroy(&spich->buf_lock); kfree(spich); } } return 0; } static void spich_complete(void *arg) { complete(arg); } static ssize_t spich_sync(struct spich_data *spich, struct spi_message *message) { DECLARE_COMPLETION_ONSTACK(done); int status; message->complete = spich_complete; message->context = &done; gpio_set_value(spich->gpio_array[GPIO_IDX_AP2SH].gpio, 0); /* Allow the context hub time to wake from stop mode. According to the * spec this can take up to 138us, we choose a slightly more * conservative delay. */ udelay(250); spin_lock_irq(&spich->spi_lock); if (spich->spi == NULL) { status = -ESHUTDOWN; } else { status = spi_async(spich->spi, message); } spin_unlock_irq(&spich->spi_lock); if (status == 0) { wait_for_completion(&done); status = message->status; if (status == 0) { status = message->actual_length; } } gpio_set_value(spich->gpio_array[GPIO_IDX_AP2SH].gpio, 1); if (gpio_get_value(spich->gpio_array[GPIO_IDX_SH2AP].gpio) != 0) { wake_unlock(&spich->sh2ap_data_wakelock); } return status; } static void SET_U64(void *_data, uint64_t x) { uint8_t *data = (uint8_t *) _data; data[0] = (x >> 56) & 0xff; data[1] = (x >> 48) & 0xff; data[2] = (x >> 40) & 0xff; data[3] = (x >> 32) & 0xff; data[4] = (x >> 24) & 0xff; data[5] = (x >> 16) & 0xff; data[6] = (x >> 8) & 0xff; data[7] = x & 0xff; } static int spich_message(struct spich_data *spich, struct spi_ioc_transfer *u_xfers, unsigned n_xfers) { struct spi_message msg; struct spi_transfer *k_xfers; struct spi_transfer *k_tmp; struct spi_ioc_transfer *u_tmp; u8 *buf; u8 *bufrx; int status = -EFAULT; unsigned n; unsigned total; struct timespec t; uint64_t now_us; spi_message_init(&msg); k_xfers = kcalloc(n_xfers, sizeof(struct spi_transfer), GFP_KERNEL); if (k_xfers == NULL) { return -ENOMEM; } if (spich->flags & SPICH_FLAG_TIMESTAMPS_ENABLED) { get_monotonic_boottime(&t); now_us = t.tv_sec * 1000000ull + (t.tv_nsec + 500ull) / 1000ull; } buf = spich->buffer; bufrx = spich->bufferrx; total = 0; for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers; n; --n, ++k_tmp, ++u_tmp) { k_tmp->len = u_tmp->len; total += k_tmp->len; if (total > SPICH_BUFFER_SIZE) { status = -EMSGSIZE; goto done; } if (u_tmp->rx_buf) { k_tmp->rx_buf = bufrx; if (!access_ok(VERIFY_WRITE, (u8 __user *) (uintptr_t) u_tmp->rx_buf, u_tmp->len)) { goto done; } } if (u_tmp->tx_buf) { k_tmp->tx_buf = buf; if (copy_from_user(buf, (const u8 __user *)(uintptr_t) u_tmp->tx_buf, u_tmp->len)) { goto done; } if (u_tmp->len >= 10 && (spich->flags & SPICH_FLAG_TIMESTAMPS_ENABLED)) { /* Bit 7 of the cmd byte is used as an indication * of whether or not the hub should considers itself * suspended at the time of the transaction. */ uint8_t modified_cmd = buf[9] & 0x7f; if (spich->hub_state != HUB_ACTIVE && spich->hub_state != HUB_WAKING) { modified_cmd |= 0x80; } buf[9] = modified_cmd; SET_U64(&buf[1], now_us); } } buf += k_tmp->len; bufrx += k_tmp->len; k_tmp->cs_change = ! !u_tmp->cs_change; k_tmp->bits_per_word = u_tmp->bits_per_word; k_tmp->delay_usecs = u_tmp->delay_usecs; k_tmp->speed_hz = u_tmp->speed_hz; spi_message_add_tail(k_tmp, &msg); } status = spich_sync(spich, &msg); if (status < 0) { goto done; } buf = spich->bufferrx; for (n = n_xfers, u_tmp = u_xfers; n; --n, ++u_tmp) { if (u_tmp->rx_buf) { if (__copy_to_user ((u8 __user *) (uintptr_t) u_tmp->rx_buf, buf, u_tmp->len)) { status = -EFAULT; goto done; } } buf += u_tmp->len; } status = total; done: kfree(k_xfers); return status; } #define SPI_MODE_MASK \ (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \ | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \ | SPI_NO_CS | SPI_READY) static long spich_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int err = 0; struct spich_data *spich; struct spi_device *spi; if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) { return -ENOTTY; } if (_IOC_DIR(cmd) & _IOC_READ) { err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); } if (err == 0 && (_IOC_DIR(cmd) & _IOC_WRITE)) { err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); } if (err) { return -EFAULT; } spich = filp->private_data; spin_lock_irq(&spich->spi_lock); spi = spi_dev_get(spich->spi); spin_unlock_irq(&spich->spi_lock); if (spi == NULL) { return -ESHUTDOWN; } mutex_lock(&spich->buf_lock); switch (cmd) { case SPI_IOC_RESET_HUB: { u32 tmp; err = __get_user(tmp, (u8 __user *) arg); if (err != 0) { break; } gpio_set_value(spich->gpio_array[GPIO_IDX_BOOT0].gpio, tmp ? 1 : 0); gpio_set_value(spich->gpio_array[GPIO_IDX_NRST].gpio, 0); mdelay(50); gpio_set_value(spich->gpio_array[GPIO_IDX_NRST].gpio, 1); spich->hub_state = HUB_ACTIVE; wake_unlock(&spich->sh2ap_wakelock); break; } case SPI_IOC_NOTIFY_HUB_SUSPENDED: { u32 tmp; err = __get_user(tmp, (u8 __user *)arg); if (err != 0) { break; } if (spich->hub_state == HUB_SUSPENDING && tmp) { spich_hub_suspended(spich, 1); } else if (spich->hub_state == HUB_WAKING && !tmp) { spich_hub_suspended(spich, 0); } break; } case SPI_IOC_ENABLE_TIMESTAMPS: { u32 tmp; err = __get_user(tmp, (u8 __user *) arg); if (err != 0) { break; } if (tmp) { spich->flags |= SPICH_FLAG_TIMESTAMPS_ENABLED; } else { spich->flags &= ~SPICH_FLAG_TIMESTAMPS_ENABLED; } break; } case SPI_IOC_WR_MODE: { u32 tmp; u8 save; err = __get_user(tmp, (u8 __user *) arg); if (err != 0) { break; } save = spi->mode; if (tmp & ~SPI_MODE_MASK) { err = -EINVAL; break; } tmp |= spi->mode & ~SPI_MODE_MASK; spi->mode = (u8) tmp; err = spi_setup(spi); if (err < 0) { spi->mode = save; } else { dev_dbg(&spi->dev, "spi mode %02x\n", tmp); } break; } case SPI_IOC_WR_LSB_FIRST: { u32 tmp; u8 save; err = __get_user(tmp, (u8 __user *) arg); if (err != 0) { break; } save = spi->mode; if (tmp) { spi->mode |= SPI_LSB_FIRST; } else { spi->mode &= ~SPI_LSB_FIRST; } err = spi_setup(spi); if (err < 0) { spi->mode = save; } else { dev_dbg(&spi->dev, "%csb first\n", tmp ? 'l' : 'm'); } break; } case SPI_IOC_WR_BITS_PER_WORD: { u32 tmp; u8 save; err = __get_user(tmp, (u8 __user *) arg); if (err != 0) { break; } save = spi->bits_per_word; spi->bits_per_word = tmp; err = spi_setup(spi); if (err < 0) { spi->bits_per_word = save; } else { dev_dbg(&spi->dev, "%d bits per word\n", tmp); } break; } case SPI_IOC_WR_MAX_SPEED_HZ: { u32 tmp; u32 save; err = __get_user(tmp, (__u32 __user *) arg); if (err != 0) { break; } save = spi->max_speed_hz; spi->max_speed_hz = tmp; err = spi_setup(spi); if (err < 0) { spi->max_speed_hz = save; } else { dev_dbg(&spi->dev, "%d Hz (max)\n", tmp); } break; } case SPI_IOC_TXRX: { struct spi_ioc_transfer t; if (__copy_from_user(&t, (void __user *)arg, sizeof(t))) { err = -EFAULT; } if (gpio_get_value (spich->gpio_array[GPIO_IDX_SH2AP].gpio) == 1) { wait_for_completion_interruptible (&spich->sh2ap_completion); } if (spich->hub_state == HUB_SUSPENDED) { /* If the userspace is sending some transaction, * then we can tell the hub that we are awake */ wake_lock(&spich->sh2ap_wakelock); del_timer(&spich->resume_timer); spich->hub_state = HUB_WAKING; spich->force_read = 1; } err = spich_message(spich, &t, 1); break; } default: { u32 tmp; unsigned n_ioc; struct spi_ioc_transfer *ioc; if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) || _IOC_DIR(cmd) != _IOC_WRITE) { err = -ENOTTY; break; } tmp = _IOC_SIZE(cmd); if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) { err = -EINVAL; break; } n_ioc = tmp / sizeof(struct spi_ioc_transfer); if (n_ioc == 0) { break; } ioc = kmalloc(tmp, GFP_KERNEL); if (!ioc) { err = -ENOMEM; break; } if (__copy_from_user(ioc, (void __user *)arg, tmp)) { kfree(ioc); err = -EFAULT; } else { err = spich_message(spich, ioc, n_ioc); } kfree(ioc); break; } } mutex_unlock(&spich->buf_lock); spi_dev_put(spi); return err; } #ifdef CONFIG_COMPAT static long spich_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { return spich_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); } #else #define spich_compat_ioctl NULL #endif static ssize_t spich_sync_read(struct spich_data *spich, size_t len) { struct spi_transfer t = { .tx_buf = spich->buffer, .rx_buf = spich->bufferrx, .len = len, }; struct spi_message m; struct timespec ts; uint64_t now_us; ssize_t status; memset(spich->buffer, 0, len); spich->buffer[0] = 0x55; get_monotonic_boottime(&ts); now_us = ts.tv_sec * 1000000ull + (ts.tv_nsec + 500ull) / 1000ull; SET_U64(&spich->buffer[1], now_us); spi_message_init(&m); spi_message_add_tail(&t, &m); status = spich_sync(spich, &m); if (status != 0) { return status; } status = len; return status; } static ssize_t spich_read(struct file *filp, char __user * buf, size_t count, loff_t * f_pos) { struct spich_data *spich; ssize_t status; if (count > SPICH_BUFFER_SIZE) { return -EMSGSIZE; } spich = filp->private_data; if (gpio_get_value(spich->gpio_array[GPIO_IDX_SH2AP].gpio) == 1) { wait_for_completion_interruptible(&spich->sh2ap_completion); } mutex_lock(&spich->buf_lock); status = spich_sync_read(spich, count); if (status > 0) { unsigned long missing = copy_to_user(buf, spich->bufferrx, status); if (missing == status) { status = -EFAULT; } else { status -= missing; } } mutex_unlock(&spich->buf_lock); return status; } static unsigned int spich_poll(struct file *filp, struct poll_table_struct *wait) { struct spich_data *spich; unsigned int mask = 0; unsigned users; kuid_t kuid = current_uid(); spich = filp->private_data; if (gpio_get_value(spich->gpio_array[GPIO_IDX_SH2AP].gpio) == 1) { poll_wait(filp, &spich->sh2ap_completion.wait, wait); } /* If this a non-root process and there is another (root) user, * then only the root user gets access to SPI traffic */ spin_lock_irq(&spich->spi_lock); users = spich->users; if ((kuid.val != 0) && (users > 1)) { spin_unlock_irq(&spich->spi_lock); return 0; } spin_unlock_irq(&spich->spi_lock); if (gpio_get_value(spich->gpio_array[GPIO_IDX_SH2AP].gpio) == 0 || spich->force_read) { mask |= POLLIN | POLLRDNORM; spich->force_read = 0; } return mask; } static const struct file_operations spich_fops = { .owner = THIS_MODULE, .unlocked_ioctl = spich_ioctl, .compat_ioctl = spich_compat_ioctl, .open = spich_open, .release = spich_release, .llseek = no_llseek, .read = spich_read, .poll = spich_poll, }; static int spich_probe(struct spi_device *spi) { struct spich_data *spich; int error = 0; int i; spich = kzalloc(sizeof(struct spich_data), GFP_KERNEL); if (!spich) { dev_err(&spi->dev, "memory allocation error!.\n"); return -ENOMEM; } spich->spi = spi; spin_lock_init(&spich->spi_lock); mutex_init(&spich->buf_lock); spich->gpio_array[GPIO_IDX_AP2SH].flags = GPIOF_OUT_INIT_HIGH; spich->gpio_array[GPIO_IDX_AP2SH].label = "contexthub,ap2sh"; spich->gpio_array[GPIO_IDX_SH2AP].flags = GPIOF_DIR_IN; spich->gpio_array[GPIO_IDX_SH2AP].label = "contexthub,sh2ap"; spich->gpio_array[GPIO_IDX_BOOT0].flags = GPIOF_OUT_INIT_LOW; spich->gpio_array[GPIO_IDX_BOOT0].label = "contexthub,boot0"; spich->gpio_array[GPIO_IDX_NRST].flags = GPIOF_OUT_INIT_HIGH; spich->gpio_array[GPIO_IDX_NRST].label = "contexthub,nrst"; spich->class = class_create(THIS_MODULE, DRV_CLASS_NAME); if (IS_ERR(spich->class)) { dev_err(&spich->spi->dev, "failed to create class.\n"); error = PTR_ERR(spich->class); goto err_class_create; } error = alloc_chrdev_region(&spich->devno, 0, 1, DRV_NAME); if (error < 0) { dev_err(&spich->spi->dev, "alloc_chrdev_region failed.\n"); goto err_alloc_chrdev; } spich->device = device_create(spich->class, NULL, spich->devno, NULL, "%s", DRV_NAME); if (IS_ERR(spich->device)) { dev_err(&spich->spi->dev, "device_create failed.\n"); error = PTR_ERR(spich->device); goto err_device_create; } cdev_init(&spich->cdev, &spich_fops); spich->cdev.owner = THIS_MODULE; error = cdev_add(&spich->cdev, spich->devno, 1); if (error) { dev_err(&spich->spi->dev, "cdev_add failed.\n"); goto err_device_create; } for (i = 0; i < ARRAY_SIZE(spich->gpio_array); i++) { int gpio; gpio = of_get_named_gpio(spich->spi->dev.of_node, spich->gpio_array[i].label, 0); if (gpio < 0) { dev_err(&spich->spi->dev, "failed to find %s property in DT\n", spich->gpio_array[i].label); goto err_device_create; } spich->gpio_array[i].gpio = gpio; dev_info(&spich->spi->dev, "%s: %s=%u\n", __func__, spich->gpio_array[i].label, spich->gpio_array[i].gpio); } device_init_wakeup(&spi->dev, 1 /* wakeup */); error = spich_init_instance(spich); if (error) { dev_err(&spich->spi->dev, "spich_init_instance failed.\n"); goto err_spich_init_instance; } spi_set_drvdata(spi, spich); return 0; err_spich_init_instance: cdev_del(&spich->cdev); err_device_create: device_destroy(spich->class, spich->devno); err_alloc_chrdev: class_destroy(spich->class); err_class_create: kfree(spich); return error; } static int spich_remove(struct spi_device *spi) { struct spich_data *spich = spi_get_drvdata(spi); unsigned users; device_init_wakeup(&spi->dev, 0 /* wakeup */); cdev_del(&spich->cdev); device_destroy(spich->class, spich->devno); class_destroy(spich->class); spin_lock_irq(&spich->spi_lock); spich->spi = NULL; users = spich->users; spi_set_drvdata(spi, NULL); spin_unlock_irq(&spich->spi_lock); if (users == 0) { spich_destroy_instance(spich); mutex_destroy(&spich->buf_lock); kfree(spich); } return 0; } static const struct of_device_id spich_dt_ids[] = { {.compatible = "contexthub," DRV_NAME}, {}, }; MODULE_DEVICE_TABLE(of, spich_dt_ids); static struct spi_driver spich_spi_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, .of_match_table = of_match_ptr(spich_dt_ids), }, .probe = spich_probe, .remove = spich_remove, .suspend = spich_suspend, .resume = spich_resume, }; static int __init spich_init(void) { pr_info(DRV_NAME ": %s\n", __func__); if (spi_register_driver(&spich_spi_driver)) { pr_err(DRV_NAME ": failed to spi_register_driver\n"); return -EINVAL; } return 0; } static void __exit spich_exit(void) { pr_info(DRV_NAME ": %s\n", __func__); spi_unregister_driver(&spich_spi_driver); } module_init(spich_init); module_exit(spich_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Google"); MODULE_DESCRIPTION("User mode SPI context hub interface");