| /* |
| * 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 <linux/cdev.h> |
| #include <linux/compat.h> |
| #include <linux/completion.h> |
| #include <linux/cred.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/err.h> |
| #include <linux/errno.h> |
| #include <linux/fs.h> |
| #include <linux/gpio.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/ioctl.h> |
| #include <linux/list.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/of.h> |
| #include <linux/of_device.h> |
| #include <linux/of_gpio.h> |
| #include <linux/poll.h> |
| #include <linux/slab.h> |
| #include <linux/spi/spi-contexthub.h> |
| #include <linux/spi/spi.h> |
| #include <linux/wakelock.h> |
| |
| #include <asm/poll.h> |
| #include <asm/uaccess.h> |
| |
| #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; |
| unsigned uid = 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 ((uid != 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; |
| unsigned uid = 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 ((uid != 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"); |