| /* |
| * Copyright (C) 2016 Google, Inc. |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/kthread.h> |
| #include <linux/slab.h> |
| #include <linux/iio/iio.h> |
| #include <linux/firmware.h> |
| #include <linux/fs.h> |
| #include <linux/cdev.h> |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| #include <linux/list.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_platform.h> |
| #include <linux/platform_device.h> |
| #include <linux/poll.h> |
| #include <linux/regmap.h> |
| #include <linux/spinlock.h> |
| #include <linux/semaphore.h> |
| #include <linux/sched.h> |
| #include <linux/sched/rt.h> |
| #include <linux/sched/signal.h> |
| #include <linux/sched/types.h> |
| #include <linux/time.h> |
| #include <linux/uaccess.h> |
| #include <linux/vmalloc.h> |
| #include <uapi/linux/sched/types.h> |
| |
| #include "nanohub.h" |
| #include "nanohub_exports.h" |
| #include "main.h" |
| #include "comms.h" |
| #include "spi.h" |
| |
| #ifdef CONFIG_NANOHUB_BL_ST |
| #include "bl_st.h" |
| #endif |
| |
| #ifdef CONFIG_NANOHUB_BL_NXP |
| #include "bl_nxp.h" |
| #endif |
| |
| #ifdef CONFIG_NANOHUB_DISPLAY |
| #include "display.h" |
| #endif |
| |
| #define READ_QUEUE_DEPTH 20 |
| #define APP_FROM_HOST_EVENTID 0x000000F8 |
| #define FIRST_SENSOR_EVENTID 0x00000200 |
| #define LAST_SENSOR_EVENTID 0x000002FF |
| #define APP_TO_HOST_EVENTID 0x00000401 |
| #define OS_LOG_EVENTID 0x3B474F4C |
| #define WAKEUP_INTERRUPT 1 |
| #define WAKEUP_TIMEOUT_MS 1000 |
| #define SUSPEND_TIMEOUT_MS 100 |
| #define RETRY_INT_WIDTH_US 50 |
| #define KTHREAD_ERR_TIME_NS (60LL * NSEC_PER_SEC) |
| #define KTHREAD_ERR_CNT 70 |
| #define KTHREAD_WARN_CNT 10 |
| #define WAKEUP_ERR_TIME_NS (60LL * NSEC_PER_SEC) |
| #define WAKEUP_ERR_CNT 4 |
| #define BL_MAX_SPEED_HZ 1000000 |
| |
| #define GPIO05_INT_SET_TYPE 0x00008C11 |
| #define GPIO05_INT_POLARITY_HIGH 0x00008C12 |
| #define GPIO05_INT_EN_SET 0x00008C15 |
| #define GPIO05_INT_EN_CLR 0x00008C16 |
| #define GPIO05_INT_MID_SEL 0x00008C1A |
| #define GPIO05_OUTPUT_CTRL 0x00008C44 |
| |
| /** |
| * struct gpio_config - this is a binding between platform data and driver data |
| * @label: for diagnostics |
| * @flags: to pass to gpio_request_one() |
| * @options: one or more of GPIO_OPT_* flags, below |
| * @pdata_off: offset of u32 field in platform data with gpio # |
| * @data_off: offset of int field in driver data with irq # (optional) |
| */ |
| struct gpio_config { |
| const char *label; |
| u16 flags; |
| u16 options; |
| u16 pdata_off; |
| u16 data_off; |
| }; |
| |
| struct device_config { |
| const char *name; |
| const uint8_t channels; |
| const bool is_kernel; |
| }; |
| |
| #define GPIO_OPT_HAS_IRQ 0x0001 |
| #define GPIO_OPT_OPTIONAL 0x8000 |
| |
| #define PLAT_GPIO_DEF(name, _flags) \ |
| .pdata_off = offsetof(struct nanohub_platform_data, name ## _gpio), \ |
| .label = "nanohub_" #name, \ |
| .flags = _flags \ |
| |
| #define PLAT_GPIO_DEF_IRQ(name, _flags, _opts) \ |
| PLAT_GPIO_DEF(name, _flags), \ |
| .data_off = offsetof(struct nanohub_data, name), \ |
| .options = GPIO_OPT_HAS_IRQ | (_opts) \ |
| |
| static int nanohub_open(struct inode *, struct file *); |
| static ssize_t nanohub_read(struct file *, char *, size_t, loff_t *); |
| static ssize_t nanohub_write(struct file *, const char *, size_t, loff_t *); |
| static unsigned int nanohub_poll(struct file *, poll_table *); |
| static int nanohub_release(struct inode *, struct file *); |
| static int nanohub_hw_reset(struct nanohub_data *data, int boot_mode); |
| static int nanohub_pmic_irq_config(struct regmap *pmic_regmap); |
| |
| // Kernel client support. |
| static struct nanohub_data *priv_nanohub_data; |
| EXPORT_SYMBOL(nanohub_send_message); |
| EXPORT_SYMBOL(nanohub_register_listener); |
| EXPORT_SYMBOL(nanohub_unregister_listener); |
| EXPORT_SYMBOL(nanohub_query_display_state); |
| |
| static struct class *sensor_class; |
| static int major; |
| |
| static const struct gpio_config gconf[] = { |
| { PLAT_GPIO_DEF(wakeup, GPIOF_OUT_INIT_HIGH) }, |
| { PLAT_GPIO_DEF(nreset, GPIOF_OUT_INIT_HIGH) }, |
| { PLAT_GPIO_DEF(boot0, GPIOF_OUT_INIT_HIGH) }, |
| { PLAT_GPIO_DEF(boot2, GPIOF_OUT_INIT_HIGH) }, |
| #ifdef CONFIG_NANOHUB_DISPLAY |
| { PLAT_GPIO_DEF(display_select, GPIOF_OUT_INIT_HIGH) }, |
| #endif |
| { PLAT_GPIO_DEF_IRQ(irq1, GPIOF_DIR_IN, 0) }, |
| { PLAT_GPIO_DEF_IRQ(irq2, GPIOF_DIR_IN, GPIO_OPT_OPTIONAL) }, |
| }; |
| |
| static const struct iio_info nanohub_iio_info = { |
| }; |
| |
| static const struct file_operations nanohub_fileops = { |
| .owner = THIS_MODULE, |
| .open = nanohub_open, |
| .read = nanohub_read, |
| .write = nanohub_write, |
| .poll = nanohub_poll, |
| .release = nanohub_release, |
| }; |
| |
| enum { |
| ST_RESET, |
| ST_IDLE, |
| ST_ERROR, |
| ST_RUNNING |
| }; |
| |
| static inline bool gpio_is_optional(const struct gpio_config *_cfg) |
| { |
| return _cfg->options & GPIO_OPT_OPTIONAL; |
| } |
| |
| static inline bool gpio_has_irq(const struct gpio_config *_cfg) |
| { |
| return _cfg->options & GPIO_OPT_HAS_IRQ; |
| } |
| |
| static inline bool nanohub_has_priority_lock_locked(struct nanohub_data *data) |
| { |
| return atomic_read(&data->wakeup_lock_cnt) > |
| atomic_read(&data->wakeup_cnt); |
| } |
| |
| static inline void nanohub_notify_thread(struct nanohub_data *data) |
| { |
| atomic_set(&data->kthread_run, 1); |
| /* wake_up implementation works as memory barrier */ |
| wake_up_interruptible_sync(&data->kthread_wait); |
| } |
| |
| static inline void nanohub_io_init(struct nanohub_io *io, |
| struct nanohub_data *data, |
| struct device *dev, |
| int id, |
| bool is_kernel) |
| { |
| init_waitqueue_head(&io->buf_wait); |
| INIT_LIST_HEAD(&io->buf_list); |
| io->data = data; |
| io->dev = dev; |
| io->id = id; |
| io->is_kernel = is_kernel; |
| atomic_set(&io->busy, 0); |
| } |
| |
| static inline bool nanohub_io_has_buf(struct nanohub_io *io) |
| { |
| return !list_empty(&io->buf_list); |
| } |
| |
| static struct nanohub_buf *nanohub_io_get_buf(struct nanohub_io *io, |
| bool wait) |
| { |
| struct nanohub_buf *buf = NULL; |
| int ret; |
| |
| spin_lock(&io->buf_wait.lock); |
| if (wait) { |
| ret = wait_event_interruptible_locked(io->buf_wait, |
| nanohub_io_has_buf(io)); |
| if (ret < 0) { |
| spin_unlock(&io->buf_wait.lock); |
| return ERR_PTR(ret); |
| } |
| } |
| |
| if (nanohub_io_has_buf(io)) { |
| buf = list_first_entry(&io->buf_list, struct nanohub_buf, list); |
| list_del(&buf->list); |
| } |
| spin_unlock(&io->buf_wait.lock); |
| |
| return buf; |
| } |
| |
| static void nanohub_io_put_buf(struct nanohub_io *io, |
| struct nanohub_buf *buf) |
| { |
| bool was_empty; |
| |
| spin_lock(&io->buf_wait.lock); |
| was_empty = !nanohub_io_has_buf(io); |
| list_add_tail(&buf->list, &io->buf_list); |
| spin_unlock(&io->buf_wait.lock); |
| |
| if (was_empty) { |
| if (&io->data->free_pool == io) |
| nanohub_notify_thread(io->data); |
| else |
| wake_up_interruptible(&io->buf_wait); |
| } |
| } |
| |
| static inline int plat_gpio_get(struct nanohub_data *data, |
| const struct gpio_config *_cfg) |
| { |
| const struct nanohub_platform_data *pdata = data->pdata; |
| |
| return *(u32 *)(((char *)pdata) + (_cfg)->pdata_off); |
| } |
| |
| static inline void nanohub_set_irq_data(struct nanohub_data *data, |
| const struct gpio_config *_cfg, int val) |
| { |
| int *data_addr = ((int *)(((char *)data) + _cfg->data_off)); |
| |
| if ((void *)data_addr > (void *)data && |
| (void *)data_addr < (void *)(data + 1)) |
| *data_addr = val; |
| else |
| WARN(1, "No data binding defined for %s", _cfg->label); |
| } |
| |
| static inline void mcu_wakeup_gpio_set_value(struct nanohub_data *data, |
| int val) |
| { |
| const struct nanohub_platform_data *pdata = data->pdata; |
| |
| gpio_set_value(pdata->wakeup_gpio, val); |
| } |
| |
| static inline void mcu_wakeup_gpio_get_locked(struct nanohub_data *data, |
| int priority_lock) |
| { |
| atomic_inc(&data->wakeup_lock_cnt); |
| if (!priority_lock && atomic_inc_return(&data->wakeup_cnt) == 1 && |
| !nanohub_has_priority_lock_locked(data)) |
| mcu_wakeup_gpio_set_value(data, 0); |
| } |
| |
| static inline bool mcu_wakeup_gpio_put_locked(struct nanohub_data *data, |
| int priority_lock) |
| { |
| bool gpio_done = priority_lock ? |
| atomic_read(&data->wakeup_cnt) == 0 : |
| atomic_dec_and_test(&data->wakeup_cnt); |
| bool done = atomic_dec_and_test(&data->wakeup_lock_cnt); |
| |
| if (!nanohub_has_priority_lock_locked(data)) |
| mcu_wakeup_gpio_set_value(data, gpio_done ? 1 : 0); |
| |
| return done; |
| } |
| |
| static inline bool mcu_wakeup_gpio_is_locked(struct nanohub_data *data) |
| { |
| return atomic_read(&data->wakeup_lock_cnt) != 0; |
| } |
| |
| static inline void nanohub_handle_irq1(struct nanohub_data *data) |
| { |
| bool locked; |
| |
| spin_lock(&data->wakeup_wait.lock); |
| locked = mcu_wakeup_gpio_is_locked(data); |
| spin_unlock(&data->wakeup_wait.lock); |
| if (!locked) |
| nanohub_notify_thread(data); |
| else |
| wake_up_interruptible_sync(&data->wakeup_wait); |
| } |
| |
| static inline void nanohub_handle_irq2(struct nanohub_data *data) |
| { |
| nanohub_notify_thread(data); |
| } |
| |
| static inline bool mcu_wakeup_try_lock(struct nanohub_data *data, int key) |
| { |
| /* implementation contains memory barrier */ |
| return atomic_cmpxchg(&data->wakeup_acquired, 0, key) == 0; |
| } |
| |
| static inline void mcu_wakeup_unlock(struct nanohub_data *data, int key) |
| { |
| WARN(atomic_cmpxchg(&data->wakeup_acquired, key, 0) != key, |
| "%s: failed to unlock with key %d; current state: %d", |
| __func__, key, atomic_read(&data->wakeup_acquired)); |
| } |
| |
| static inline void nanohub_set_state(struct nanohub_data *data, int state) |
| { |
| atomic_set(&data->thread_state, state); |
| smp_mb__after_atomic(); /* updated thread state is now visible */ |
| } |
| |
| static inline int nanohub_get_state(struct nanohub_data *data) |
| { |
| smp_mb__before_atomic(); /* wait for all updates to finish */ |
| return atomic_read(&data->thread_state); |
| } |
| |
| static inline void nanohub_clear_err_cnt(struct nanohub_data *data) |
| { |
| data->kthread_err_cnt = data->wakeup_err_cnt = 0; |
| } |
| |
| static long do_wait_intr_timeout(wait_queue_head_t *wq, |
| wait_queue_entry_t *wait, long tmo) |
| { |
| long ret; |
| |
| if (likely(list_empty(&wait->entry))) |
| __add_wait_queue_entry_tail(wq, wait); |
| |
| set_current_state(TASK_INTERRUPTIBLE); |
| if (signal_pending(current)) |
| return -ERESTARTSYS; |
| |
| spin_unlock(&wq->lock); |
| ret = schedule_timeout(tmo); |
| spin_lock(&wq->lock); |
| return ret; |
| } |
| |
| /* the following fragment is based on wait_event_* code from wait.h */ |
| #define wait_event_interruptible_timeout_locked(wq, cond, tmo) \ |
| ({ \ |
| long __ret = (tmo); \ |
| DEFINE_WAIT(__wait); \ |
| if (!(cond)) { \ |
| do { \ |
| __ret = do_wait_intr_timeout(&(wq), &__wait, __ret); \ |
| if (!__ret) { \ |
| if ((cond)) \ |
| __ret = 1; \ |
| break; \ |
| } else if (__ret < 0) \ |
| break; \ |
| } while (!(cond)); \ |
| __remove_wait_queue(&(wq), &__wait); \ |
| __set_current_state(TASK_RUNNING); \ |
| } else if (__ret == 0) { \ |
| __ret = 1; \ |
| } \ |
| __ret; \ |
| }) |
| |
| int request_wakeup_ex(struct nanohub_data *data, long timeout_ms, |
| int key, int lock_mode) |
| { |
| long timeout; |
| bool priority_lock = lock_mode > LOCK_MODE_NORMAL; |
| struct device *dev = data->io[ID_NANOHUB_COMMS].dev; |
| ktime_t ktime_delta; |
| ktime_t wakeup_ktime; |
| |
| spin_lock(&data->wakeup_wait.lock); |
| mcu_wakeup_gpio_get_locked(data, priority_lock); |
| timeout = (timeout_ms != MAX_SCHEDULE_TIMEOUT) ? |
| msecs_to_jiffies(timeout_ms) : |
| MAX_SCHEDULE_TIMEOUT; |
| |
| if (!priority_lock && !data->wakeup_err_cnt) |
| wakeup_ktime = ktime_get_boottime(); |
| timeout = wait_event_interruptible_timeout_locked( |
| data->wakeup_wait, |
| ((priority_lock || nanohub_irq1_fired(data)) && |
| mcu_wakeup_try_lock(data, key)), |
| timeout |
| ); |
| |
| if (timeout <= 0) { |
| if (!timeout && !priority_lock) { |
| if (!data->wakeup_err_cnt) |
| data->wakeup_err_ktime = wakeup_ktime; |
| ktime_delta = ktime_sub(ktime_get_boottime(), |
| data->wakeup_err_ktime); |
| data->wakeup_err_cnt++; |
| if (ktime_to_ns(ktime_delta) > WAKEUP_ERR_TIME_NS |
| && data->wakeup_err_cnt > WAKEUP_ERR_CNT) { |
| mcu_wakeup_gpio_put_locked(data, priority_lock); |
| spin_unlock(&data->wakeup_wait.lock); |
| dev_info(dev, "wakeup: consistent error\n"); |
| // dev_info(dev, |
| // "wakeup: hard reset due to consistent error\n"); |
| // ret = nanohub_hw_reset(data); |
| // if (ret) { |
| // dev_info(dev, |
| // "%s: failed to reset nanohub: ret=%d\n", |
| // __func__, ret); |
| // } |
| return -ETIME; |
| } |
| } |
| mcu_wakeup_gpio_put_locked(data, priority_lock); |
| |
| if (timeout == 0) |
| timeout = -ETIME; |
| } else { |
| data->wakeup_err_cnt = 0; |
| timeout = 0; |
| } |
| spin_unlock(&data->wakeup_wait.lock); |
| |
| return timeout; |
| } |
| |
| void release_wakeup_ex(struct nanohub_data *data, int key, int lock_mode) |
| { |
| bool done; |
| bool priority_lock = lock_mode > LOCK_MODE_NORMAL; |
| |
| spin_lock(&data->wakeup_wait.lock); |
| done = mcu_wakeup_gpio_put_locked(data, priority_lock); |
| mcu_wakeup_unlock(data, key); |
| spin_unlock(&data->wakeup_wait.lock); |
| |
| if (!done) |
| wake_up_interruptible_sync(&data->wakeup_wait); |
| else if (nanohub_irq1_fired(data) || nanohub_irq2_fired(data)) |
| nanohub_notify_thread(data); |
| } |
| |
| int nanohub_wait_for_interrupt(struct nanohub_data *data) |
| { |
| int ret = -EFAULT; |
| |
| /* release the wakeup line, and wait for nanohub to send |
| * us an interrupt indicating the transaction completed. |
| */ |
| spin_lock(&data->wakeup_wait.lock); |
| if (mcu_wakeup_gpio_is_locked(data)) { |
| mcu_wakeup_gpio_set_value(data, 1); |
| ret = wait_event_interruptible_locked(data->wakeup_wait, |
| nanohub_irq1_fired(data)); |
| mcu_wakeup_gpio_set_value(data, 0); |
| } |
| spin_unlock(&data->wakeup_wait.lock); |
| |
| return ret; |
| } |
| |
| int nanohub_wakeup_eom(struct nanohub_data *data, bool repeat) |
| { |
| int ret = -EFAULT; |
| |
| spin_lock(&data->wakeup_wait.lock); |
| if (mcu_wakeup_gpio_is_locked(data)) { |
| mcu_wakeup_gpio_set_value(data, 1); |
| if (repeat) { |
| udelay(RETRY_INT_WIDTH_US); |
| mcu_wakeup_gpio_set_value(data, 0); |
| } |
| ret = 0; |
| } |
| spin_unlock(&data->wakeup_wait.lock); |
| |
| return ret; |
| } |
| |
| static void __nanohub_interrupt_cfg(struct nanohub_data *data, |
| u8 interrupt, bool mask) |
| { |
| int ret; |
| uint8_t mask_ret; |
| int cnt = 10; |
| struct device *dev = data->io[ID_NANOHUB_COMMS].dev; |
| int cmd = mask ? CMD_COMMS_MASK_INTR : CMD_COMMS_UNMASK_INTR; |
| |
| // interrupt config is not supported in r11, keeping for later use |
| if (true) |
| return; |
| |
| do { |
| ret = request_wakeup_timeout(data, WAKEUP_TIMEOUT_MS); |
| if (ret) { |
| dev_err(dev, |
| "%s: interrupt %d %smask failed: ret=%d\n", |
| __func__, interrupt, mask ? "" : "un", ret); |
| return; |
| } |
| |
| ret = |
| nanohub_comms_tx_rx_retrans(data, cmd, ID_NANOHUB_COMMS, |
| &interrupt, sizeof(interrupt), |
| NULL, &mask_ret, |
| sizeof(mask_ret), false, 10, 0); |
| release_wakeup(data); |
| dev_dbg(dev, |
| "%smasking interrupt %d, ret=%d, mask_ret=%d\n", |
| mask ? "" : "un", |
| interrupt, ret, mask_ret); |
| } while ((ret != 1 || mask_ret != 1) && --cnt > 0); |
| } |
| |
| static inline void nanohub_mask_interrupt(struct nanohub_data *data, |
| u8 interrupt) |
| { |
| __nanohub_interrupt_cfg(data, interrupt, true); |
| return; |
| } |
| |
| static inline void nanohub_unmask_interrupt(struct nanohub_data *data, |
| u8 interrupt) |
| { |
| __nanohub_interrupt_cfg(data, interrupt, false); |
| return; |
| } |
| |
| static ssize_t nanohub_wakeup_query(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct nanohub_platform_data *pdata = data->pdata; |
| |
| nanohub_clear_err_cnt(data); |
| if (nanohub_irq1_fired(data) || nanohub_irq2_fired(data)) |
| wake_up_interruptible(&data->wakeup_wait); |
| |
| return scnprintf(buf, PAGE_SIZE, "WAKEUP: %d INT1: %d INT2: %d\n", |
| gpio_get_value(pdata->wakeup_gpio), |
| gpio_get_value(pdata->irq1_gpio), |
| data->irq2 ? gpio_get_value(pdata->irq2_gpio) : -1); |
| } |
| |
| static ssize_t nanohub_pin_nreset_get(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct nanohub_platform_data *pdata = data->pdata; |
| return scnprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(pdata->nreset_gpio)); |
| } |
| |
| static ssize_t nanohub_pin_boot0_get(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct nanohub_platform_data *pdata = data->pdata; |
| return scnprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(pdata->boot0_gpio)); |
| } |
| |
| static ssize_t nanohub_pin_boot2_get(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct nanohub_platform_data *pdata = data->pdata; |
| return scnprintf(buf, PAGE_SIZE, "%d\n", gpio_get_value(pdata->boot2_gpio)); |
| } |
| |
| static ssize_t nanohub_pin_nreset_set(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct nanohub_platform_data *pdata = data->pdata; |
| if (count >= 1 && buf[0] == '0') { |
| gpio_set_value(pdata->nreset_gpio, 0); |
| } else if (count >= 1 && buf[0] == '1') { |
| gpio_set_value(pdata->nreset_gpio, 1); |
| } |
| return count; |
| } |
| |
| static ssize_t nanohub_pin_boot0_set(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct nanohub_platform_data *pdata = data->pdata; |
| if (count >= 1 && buf[0] == '0') { |
| gpio_set_value(pdata->boot0_gpio, 0); |
| } else if (count >= 1 && buf[0] == '1') { |
| gpio_set_value(pdata->boot0_gpio, 1); |
| } |
| return count; |
| } |
| |
| static ssize_t nanohub_pin_boot2_set(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct nanohub_platform_data *pdata = data->pdata; |
| if (count >= 1 && buf[0] == '0') { |
| gpio_set_value(pdata->boot2_gpio, 0); |
| } else if (count >= 1 && buf[0] == '1') { |
| gpio_set_value(pdata->boot2_gpio, 1); |
| } |
| return count; |
| } |
| |
| static ssize_t nanohub_app_info(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| struct { |
| uint64_t appId; |
| uint32_t appVer; |
| uint32_t appSize; |
| } __packed buffer; |
| uint32_t i = 0; |
| int ret; |
| ssize_t len = 0; |
| |
| do { |
| if (request_wakeup(data)) |
| return -ERESTARTSYS; |
| |
| if (nanohub_comms_tx_rx_retrans |
| (data, CMD_COMMS_QUERY_APP_INFO, ID_NANOHUB_COMMS, |
| (uint8_t *)&i, sizeof(i), NULL, (u8 *)&buffer, |
| sizeof(buffer), false, 10, 10) == sizeof(buffer)) { |
| ret = |
| scnprintf(buf + len, PAGE_SIZE - len, |
| "app: %d id: %016llx ver: %08x size: %08x\n", |
| i, buffer.appId, buffer.appVer, |
| buffer.appSize); |
| if (ret > 0) { |
| len += ret; |
| i++; |
| } |
| } else { |
| ret = -1; |
| } |
| |
| release_wakeup(data); |
| } while (ret > 0); |
| |
| return len; |
| } |
| |
| static ssize_t nanohub_firmware_query(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| uint16_t buffer[6]; |
| |
| if (request_wakeup(data)) |
| return -ERESTARTSYS; |
| |
| if (nanohub_comms_tx_rx_retrans |
| (data, CMD_COMMS_GET_OS_HW_VERSIONS, ID_NANOHUB_COMMS, NULL, |
| 0, NULL, (uint8_t *)&buffer, sizeof(buffer), false, 10, |
| 10) == sizeof(buffer)) { |
| release_wakeup(data); |
| return scnprintf(buf, PAGE_SIZE, |
| "hw type: %04x hw ver: %04x bl ver: %04x os ver: %04x variant ver: %08x\n", |
| buffer[0], buffer[1], buffer[2], buffer[3], |
| buffer[5] << 16 | buffer[4]); |
| } else { |
| release_wakeup(data); |
| return 0; |
| } |
| } |
| |
| static ssize_t nanohub_time_sync(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| struct nanohub_time_sync { |
| uint64_t ap_time_ns; |
| uint64_t mcu_time_ns; |
| } __packed time_sync_result; |
| int ret; |
| |
| if (request_wakeup(data)) |
| return -ERESTARTSYS; |
| |
| ret = nanohub_comms_rx_retrans_boottime |
| (data, CMD_COMMS_TIME_SYNC, ID_NANOHUB_COMMS, |
| NULL, (uint8_t *)&time_sync_result, sizeof(time_sync_result), |
| 10, 10); |
| |
| release_wakeup(data); |
| if (ret == sizeof(time_sync_result)) { |
| return scnprintf(buf, PAGE_SIZE, |
| "ap time: %llu mcu time: %llu\n", |
| time_sync_result.ap_time_ns, time_sync_result.mcu_time_ns); |
| } else { |
| return scnprintf(buf, PAGE_SIZE, "time sync error: %d\n", ret); |
| } |
| } |
| |
| static ssize_t nanohub_mcu_wake_lock(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| uint8_t lock; |
| uint8_t result; |
| int ret; |
| |
| if (count >= 1 && buf[0] == '0') { |
| lock = 0; |
| } else if (count >= 1 && buf[0] == '1') { |
| lock = 1; |
| } else { |
| return count; |
| } |
| |
| if (request_wakeup(data)) |
| return -ERESTARTSYS; |
| |
| ret = nanohub_comms_tx_rx_retrans( |
| data, CMD_COMMS_MCU_WAKE_LOCK, ID_NANOHUB_COMMS, |
| (uint8_t *)&lock, sizeof(lock), NULL, |
| (uint8_t *)&result, sizeof(result), false, 10, 10); |
| |
| release_wakeup(data); |
| return count; |
| } |
| |
| static inline int nanohub_wakeup_lock(struct nanohub_data *data, int mode) |
| { |
| int ret; |
| |
| if (data->irq2) |
| disable_irq(data->irq2); |
| else |
| nanohub_mask_interrupt(data, 2); |
| |
| ret = request_wakeup_ex(data, |
| mode == LOCK_MODE_SUSPEND_RESUME ? |
| SUSPEND_TIMEOUT_MS : WAKEUP_TIMEOUT_MS, |
| KEY_WAKEUP_LOCK, mode); |
| if (ret < 0) { |
| if (data->irq2) |
| enable_irq(data->irq2); |
| else |
| nanohub_unmask_interrupt(data, 2); |
| return ret; |
| } |
| |
| #if defined(CONFIG_NANOHUB_BL_ST) || defined(CONFIG_NANOHUB_BL_NXP) |
| if (mode == LOCK_MODE_IO || mode == LOCK_MODE_IO_BL) |
| ret = nanohub_bl_open(data); |
| if (ret < 0) { |
| release_wakeup_ex(data, KEY_WAKEUP_LOCK, mode); |
| return ret; |
| } |
| #endif |
| if (mode != LOCK_MODE_SUSPEND_RESUME) |
| disable_irq(data->irq1); |
| |
| atomic_set(&data->lock_mode, mode); |
| mcu_wakeup_gpio_set_value(data, mode != LOCK_MODE_IO_BL); |
| |
| return 0; |
| } |
| |
| /* returns lock mode used to perform this lock */ |
| static inline int nanohub_wakeup_unlock(struct nanohub_data *data) |
| { |
| int mode = atomic_read(&data->lock_mode); |
| |
| atomic_set(&data->lock_mode, LOCK_MODE_NONE); |
| if (mode != LOCK_MODE_SUSPEND_RESUME) |
| enable_irq(data->irq1); |
| #if defined(CONFIG_NANOHUB_BL_ST) || defined(CONFIG_NANOHUB_BL_NXP) |
| if (mode == LOCK_MODE_IO || mode == LOCK_MODE_IO_BL) |
| nanohub_bl_close(data); |
| #endif |
| if (data->irq2) |
| enable_irq(data->irq2); |
| release_wakeup_ex(data, KEY_WAKEUP_LOCK, mode); |
| if (!data->irq2) |
| nanohub_unmask_interrupt(data, 2); |
| nanohub_notify_thread(data); |
| |
| return mode; |
| } |
| |
| // boot_mode represents the ISP mode for the NXP MCU. |
| static void __nanohub_hw_reset(struct nanohub_data *data, int boot_mode) |
| { |
| const struct nanohub_platform_data *pdata = data->pdata; |
| int isp0 = boot_mode & 0x1; |
| int isp1 = (boot_mode & 0x2) >> 1; |
| int isp2 = (boot_mode & 0x4) >> 2; |
| int ret; |
| |
| // Set the ISP mode |
| if (isp1 != 1) { |
| // ISP1 is not connected and is pulled high in ISP mode. |
| pr_err("nanohub_hw_reset: invalid boot mode (%d), ISP pin1 must be high\n", |
| boot_mode); |
| return; |
| } |
| pr_info("nanohub_hw_reset: boot_mode=%d\n", boot_mode); |
| gpio_set_value(pdata->boot0_gpio, isp0); |
| gpio_set_value(pdata->boot2_gpio, isp2); |
| |
| // Disable AFE_TX_SUP prior to MCU boot |
| ret = regmap_write(pdata->pmic_regmap, pdata->afe_control_reg, 0); |
| if (ret != 0) |
| pr_warn("nanohub: failed to disable afe_tx_sup ret=%x\n", ret); |
| |
| // Disable BOB EXT CTRL1 prior to MCU boot |
| ret = regmap_write(pdata->pmic_regmap, pdata->bob_ext_ctrl1_control_reg, 0); |
| if (ret != 0) |
| pr_warn("nanohub: failed to disable bob ext ctrl1 ret=%x\n", ret); |
| |
| // Disable 32KHz clock prior to MCU boot |
| ret = regmap_write(pdata->pmic_regmap, pdata->clk32_control_reg, 0); |
| if (ret != 0) |
| pr_warn("nanohub: failed to disable 32KHz clock ret=%x\n", ret); |
| |
| // Reset MCU |
| gpio_set_value(pdata->nreset_gpio, 0); |
| usleep_range(1000, 2000); |
| |
| // Reset MCU vddcore voltage after reset, but prior to MCU boot |
| ret = regmap_write(pdata->pmic_regmap, pdata->vddcore_control_reg, |
| pdata->vddcore_voltage / 1000 & 0xff); |
| if (ret == 0) |
| ret = regmap_write(pdata->pmic_regmap, |
| pdata->vddcore_control_reg + 1, |
| pdata->vddcore_voltage / 1000 >> 8); |
| if (ret != 0) |
| pr_warn("nanohub: failed to reset vddcore ret=%x\n", ret); |
| usleep_range(1000, 2000); |
| |
| // Force S5A PGOOD high prior to MCU boot (Work-around for QCOM Case #06525336) |
| ret = regmap_write(pdata->pmic_regmap, pdata->s5a_pgood_state_reg, 0x80); |
| if (ret != 0) |
| pr_warn("nanohub: failed to set S5A PGOOD state ret=%x\n", ret); |
| |
| gpio_set_value(pdata->nreset_gpio, 1); |
| usleep_range(10000, 20000); |
| nanohub_clear_err_cnt(data); |
| } |
| |
| static int nanohub_hw_reset(struct nanohub_data *data, int boot_mode) |
| { |
| int ret; |
| ret = nanohub_wakeup_lock(data, LOCK_MODE_RESET); |
| |
| if (!ret) { |
| __nanohub_hw_reset(data, boot_mode); |
| nanohub_wakeup_unlock(data); |
| } |
| |
| return ret; |
| } |
| |
| // Configure PM_GPIO_05 to function as PMIC_IRQ |
| static int nanohub_pmic_irq_config(struct regmap *pmic_regmap) |
| { |
| int ret; |
| // Command seqeuence provided by QCOM in Case #06209787 |
| // GPIO05 kernel write permission enabled by gpar/529000 |
| ret = regmap_write(pmic_regmap, GPIO05_INT_SET_TYPE, 0x1); |
| ret |= regmap_write(pmic_regmap, GPIO05_INT_POLARITY_HIGH, 0x1); |
| ret |= regmap_write(pmic_regmap, GPIO05_INT_EN_SET, 0x1); |
| ret |= regmap_write(pmic_regmap, GPIO05_INT_EN_CLR, 0x1); |
| ret |= regmap_write(pmic_regmap, GPIO05_OUTPUT_CTRL, 0x82); |
| if (ret) { |
| pr_warn("nanohob: a PMIC_IRQ write operation failed, ret=%x\n", ret); |
| } |
| |
| return ret; |
| } |
| |
| static ssize_t nanohub_try_hw_reset(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| #define BOOT_MODE_ARG_SIZE 10 |
| char boot_mode_buf[BOOT_MODE_ARG_SIZE + 1]; |
| int boot_mode; |
| int ret; |
| |
| // Read the boot mode argument. (ascii, possibly unterminated) |
| if (count == 0 || count > BOOT_MODE_ARG_SIZE) { |
| pr_err("nanohub_try_hw_reset: invalid input\n"); |
| return -EINVAL; |
| } |
| memcpy(boot_mode_buf, buf, count); |
| boot_mode_buf[count] = 0; |
| if (kstrtoint(boot_mode_buf, 0, &boot_mode) != 0) { |
| pr_err("nanohub_try_hw_reset: invalid boot mode %s\n", |
| boot_mode_buf); |
| return -EINVAL; |
| } |
| |
| ret = nanohub_hw_reset(data, boot_mode); |
| |
| return ret < 0 ? ret : count; |
| } |
| |
| #ifdef CONFIG_NANOHUB_BL_ST |
| static ssize_t nanohub_erase_shared(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| uint8_t status = CMD_ACK; |
| int ret; |
| |
| ret = nanohub_wakeup_lock(data, LOCK_MODE_IO); |
| if (ret < 0) |
| return ret; |
| |
| __nanohub_hw_reset(data, 1); |
| |
| status = nanohub_bl_erase_shared(data); |
| dev_info(dev, "nanohub_bl_erase_shared: status=%02x\n", |
| status); |
| |
| __nanohub_hw_reset(data, 0); |
| nanohub_wakeup_unlock(data); |
| |
| return ret < 0 ? ret : count; |
| } |
| |
| static ssize_t nanohub_erase_shared_bl(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| uint8_t status = CMD_ACK; |
| int ret; |
| |
| ret = nanohub_wakeup_lock(data, LOCK_MODE_IO_BL); |
| if (ret < 0) |
| return ret; |
| |
| __nanohub_hw_reset(data, -1); |
| |
| status = nanohub_bl_erase_shared_bl(data); |
| dev_info(dev, "%s: status=%02x\n", __func__, status); |
| |
| __nanohub_hw_reset(data, 0); |
| nanohub_wakeup_unlock(data); |
| |
| return ret < 0 ? ret : count; |
| } |
| |
| static ssize_t nanohub_download_bl(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct nanohub_platform_data *pdata = data->pdata; |
| const struct firmware *fw_entry; |
| int ret; |
| uint8_t status = CMD_ACK; |
| |
| ret = nanohub_wakeup_lock(data, LOCK_MODE_IO); |
| if (ret < 0) |
| return ret; |
| |
| __nanohub_hw_reset(data, 1); |
| |
| ret = request_firmware(&fw_entry, "nanohub.full.bin", dev); |
| if (ret) { |
| dev_err(dev, "%s: err=%d\n", __func__, ret); |
| } else { |
| status = nanohub_bl_download(data, pdata->bl_addr, |
| fw_entry->data, fw_entry->size); |
| dev_info(dev, "%s: status=%02x\n", __func__, status); |
| release_firmware(fw_entry); |
| } |
| |
| __nanohub_hw_reset(data, 0); |
| nanohub_wakeup_unlock(data); |
| |
| return ret < 0 ? ret : count; |
| } |
| #endif |
| |
| static ssize_t nanohub_download_kernel(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct firmware *fw_entry; |
| int ret; |
| |
| ret = request_firmware(&fw_entry, "nanohub.update.bin", dev); |
| if (ret) { |
| dev_err(dev, "nanohub_download_kernel: err=%d\n", ret); |
| return -EIO; |
| } else { |
| ret = |
| nanohub_comms_kernel_download(data, fw_entry->data, |
| fw_entry->size); |
| |
| release_firmware(fw_entry); |
| |
| return count; |
| } |
| |
| } |
| |
| #ifdef CONFIG_NANOHUB_BL_ST |
| static ssize_t nanohub_download_kernel_bl(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct firmware *fw_entry; |
| int ret; |
| uint8_t status = CMD_ACK; |
| |
| ret = request_firmware(&fw_entry, "nanohub.kernel.signed", dev); |
| if (ret) { |
| dev_err(dev, "%s: err=%d\n", __func__, ret); |
| } else { |
| ret = nanohub_wakeup_lock(data, LOCK_MODE_IO_BL); |
| if (ret < 0) |
| return ret; |
| |
| __nanohub_hw_reset(data, -1); |
| |
| status = nanohub_bl_erase_shared_bl(data); |
| dev_info(dev, "%s: (erase) status=%02x\n", __func__, status); |
| if (status == CMD_ACK) { |
| status = nanohub_bl_write_memory(data, 0x50000000, |
| fw_entry->size, |
| fw_entry->data); |
| mcu_wakeup_gpio_set_value(data, 1); |
| dev_info(dev, "%s: (write) status=%02x\n", __func__, status); |
| if (status == CMD_ACK) { |
| status = nanohub_bl_update_finished(data); |
| dev_info(dev, "%s: (finish) status=%02x\n", __func__, status); |
| } |
| } else { |
| mcu_wakeup_gpio_set_value(data, 1); |
| } |
| |
| __nanohub_hw_reset(data, 0); |
| nanohub_wakeup_unlock(data); |
| |
| release_firmware(fw_entry); |
| } |
| |
| return ret < 0 ? ret : count; |
| } |
| #endif |
| |
| #ifdef CONFIG_NANOHUB_BL_NXP |
| |
| static ssize_t nanohub_firmware_name_query(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct nanohub_platform_data *pdata = data->pdata; |
| |
| if (pdata->firmware_name) { |
| strcpy(buf, pdata->firmware_name); |
| return strlen(pdata->firmware_name); |
| } |
| return 0; |
| } |
| |
| static ssize_t nanohub_download_firmware_request(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| struct firmware_request *fw_request = &data->firmware_request; |
| char *fw_name = fw_request->fw_name; |
| uint32_t* fw_image_offset = &fw_request->fw_image_offset; |
| int i, ret; |
| |
| if (atomic_cmpxchg(&fw_request->state, FW_IDLE, FW_PENDING) != |
| FW_IDLE) { |
| ret = -EBUSY; |
| goto out; |
| } |
| |
| if (count == 0 || count >= FW_NAME_SIZE) { |
| ret = -ENAMETOOLONG; |
| goto out; |
| } |
| |
| // Handle \n or unterminated name. |
| memcpy(fw_name, buf, count); |
| if (fw_name[count - 1] == '\n') |
| fw_name[count - 1] = 0; |
| fw_name[count] = 0; |
| |
| // Parse firmware name then firmware image offset which are expected |
| // to be separated by a space. Offset is optional. |
| *fw_image_offset = 0; |
| for (i = 0; i < strlen(fw_name); i++) { |
| if (fw_name[i] == ' ') { |
| ret = kstrtou32(fw_name + i + 1, 10, fw_image_offset); |
| fw_name[i] = 0; |
| break; |
| } |
| } |
| |
| atomic_set(&fw_request->state, FW_REQUESTED); |
| nanohub_notify_thread(data); |
| wait_for_completion(&fw_request->fw_complete); |
| ret = fw_request->result; |
| |
| out: |
| if (ret < 0) |
| pr_err("nanohub: firmware request failed: %s err=%d\n", fw_name, |
| ret); |
| |
| atomic_set(&fw_request->state, FW_IDLE); |
| return ret < 0 ? ret : count; |
| } |
| |
| static void nanohub_download_firmware(struct device *dev) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| struct firmware_request *fw_request = &data->firmware_request; |
| const struct firmware *fw_entry; |
| int ret; |
| |
| pr_info("nanohub: firmware download: %s\n", fw_request->fw_name); |
| pr_info("nanohub: firmware image offset: %lu\n", |
| fw_request->fw_image_offset); |
| |
| atomic_set(&fw_request->state, FW_IN_PROGRESS); |
| ret = request_firmware(&fw_entry, fw_request->fw_name, dev); |
| if (ret < 0) |
| goto out; |
| |
| ret = nanohub_wakeup_lock(data, LOCK_MODE_IO_BL); |
| if (ret < 0) |
| goto release_firmware; |
| |
| // Disable irq1 as its GPIO is used by the bootloader. |
| if (data->irq1) |
| disable_irq(data->irq1); |
| |
| // FW download uses SERIAL_ISP mode 110. |
| __nanohub_hw_reset(data, 0b110); |
| |
| if (fw_request->fw_image_offset != 0) { |
| ret = nanohub_bl_download_firmware_buffer( |
| data, fw_entry->data + fw_request->fw_image_offset, |
| fw_entry->size - fw_request->fw_image_offset); |
| } else { |
| ret = nanohub_bl_download_firmware(data, fw_entry->data, |
| fw_entry->size); |
| } |
| |
| mcu_wakeup_gpio_set_value(data, 1); |
| if (data->irq1) |
| enable_irq(data->irq1); |
| nanohub_wakeup_unlock(data); |
| |
| release_firmware: |
| release_firmware(fw_entry); |
| out: |
| pr_info("nanohub: firmware download complete: %s err=%d\n", |
| fw_request->fw_name, ret); |
| fw_request->result = ret; |
| atomic_set(&fw_request->state, FW_COMPLETE); |
| complete(&fw_request->fw_complete); |
| } |
| |
| #endif |
| |
| static ssize_t nanohub_download_app(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| const struct firmware *fw_entry; |
| char buffer[70]; |
| int i, ret, ret1, ret2, file_len = 0, appid_len = 0, ver_len = 0; |
| const char *appid = NULL, *ver = NULL; |
| unsigned long version; |
| uint64_t id; |
| uint32_t cur_version; |
| bool update = true; |
| |
| for (i = 0; i < count; i++) { |
| if (buf[i] == ' ') { |
| if (i + 1 == count) { |
| break; |
| } else { |
| if (appid == NULL) |
| appid = buf + i + 1; |
| else if (ver == NULL) |
| ver = buf + i + 1; |
| else |
| break; |
| } |
| } else if (buf[i] == '\n' || buf[i] == '\r') { |
| break; |
| } else { |
| if (ver) |
| ver_len++; |
| else if (appid) |
| appid_len++; |
| else |
| file_len++; |
| } |
| } |
| |
| if (file_len > 64 || appid_len > 16 || ver_len > 8 || file_len < 1) |
| return -EIO; |
| |
| memcpy(buffer, buf, file_len); |
| memcpy(buffer + file_len, ".napp", 5); |
| buffer[file_len + 5] = '\0'; |
| |
| ret = request_firmware(&fw_entry, buffer, dev); |
| if (ret) { |
| dev_err(dev, "nanohub_download_app(%s): err=%d\n", |
| buffer, ret); |
| return -EIO; |
| } |
| if (appid_len > 0 && ver_len > 0) { |
| memcpy(buffer, appid, appid_len); |
| buffer[appid_len] = '\0'; |
| |
| ret1 = kstrtoull(buffer, 16, &id); |
| |
| memcpy(buffer, ver, ver_len); |
| buffer[ver_len] = '\0'; |
| |
| ret2 = kstrtoul(buffer, 16, &version); |
| |
| if (ret1 == 0 && ret2 == 0) { |
| if (request_wakeup(data)) |
| return -ERESTARTSYS; |
| if (nanohub_comms_tx_rx_retrans |
| (data, CMD_COMMS_GET_APP_VERSIONS, ID_NANOHUB_COMMS, |
| (uint8_t *)&id, sizeof(id), NULL, |
| (uint8_t *)&cur_version, |
| sizeof(cur_version), false, 10, |
| 10) == sizeof(cur_version)) { |
| if (cur_version == version) |
| update = false; |
| } |
| release_wakeup(data); |
| } |
| } |
| |
| if (update) |
| ret = |
| nanohub_comms_app_download(data, fw_entry->data, |
| fw_entry->size); |
| |
| release_firmware(fw_entry); |
| |
| return count; |
| } |
| |
| #ifdef CONFIG_NANOHUB_BL_ST |
| static ssize_t nanohub_lock_bl(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| int ret; |
| uint8_t status = CMD_ACK; |
| |
| ret = nanohub_wakeup_lock(data, LOCK_MODE_IO); |
| if (ret < 0) |
| return ret; |
| |
| __nanohub_hw_reset(data, 1); |
| |
| gpio_set_value(data->pdata->boot0_gpio, 0); |
| /* this command reboots itself */ |
| status = nanohub_bl_lock(data); |
| dev_info(dev, "%s: status=%02x\n", __func__, status); |
| msleep(350); |
| |
| nanohub_wakeup_unlock(data); |
| |
| return ret < 0 ? ret : count; |
| } |
| |
| static ssize_t nanohub_unlock_bl(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| int ret; |
| uint8_t status = CMD_ACK; |
| |
| ret = nanohub_wakeup_lock(data, LOCK_MODE_IO); |
| if (ret < 0) |
| return ret; |
| |
| __nanohub_hw_reset(data, 1); |
| |
| gpio_set_value(data->pdata->boot0_gpio, 0); |
| /* this command reboots itself (erasing the flash) */ |
| status = nanohub_bl_unlock(data); |
| dev_info(dev, "%s: status=%02x\n", __func__, status); |
| msleep(20); |
| |
| nanohub_wakeup_unlock(data); |
| |
| return ret < 0 ? ret : count; |
| } |
| #endif |
| |
| static ssize_t nanohub_wakeup_event_msec_get(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| return scnprintf(buf, PAGE_SIZE, "%d\n", data->wakeup_event_msec); |
| } |
| |
| static ssize_t nanohub_wakeup_event_msec_set(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| u32 value; |
| |
| if (kstrtou32(buf, 0, &value)) |
| return -EINVAL; |
| |
| data->wakeup_event_msec = value; |
| return count; |
| } |
| |
| static struct device_attribute attributes[] = { |
| __ATTR(wakeup, 0440, nanohub_wakeup_query, NULL), |
| __ATTR(app_info, 0440, nanohub_app_info, NULL), |
| __ATTR(firmware_version, 0440, nanohub_firmware_query, NULL), |
| __ATTR(time_sync, 0440, nanohub_time_sync, NULL), |
| __ATTR(wake_lock, 0220, NULL, nanohub_mcu_wake_lock), |
| #ifdef CONFIG_NANOHUB_BL_ST |
| __ATTR(download_bl, 0220, NULL, nanohub_download_bl), |
| #endif |
| __ATTR(download_kernel, 0220, NULL, nanohub_download_kernel), |
| #ifdef CONFIG_NANOHUB_BL_ST |
| __ATTR(download_kernel_bl, 0220, NULL, nanohub_download_kernel_bl), |
| #endif |
| __ATTR(download_app, 0220, NULL, nanohub_download_app), |
| #ifdef CONFIG_NANOHUB_BL_ST |
| __ATTR(erase_shared, 0220, NULL, nanohub_erase_shared), |
| __ATTR(erase_shared_bl, 0220, NULL, nanohub_erase_shared_bl), |
| #endif |
| __ATTR(hw_reset, 0220, NULL, nanohub_try_hw_reset), |
| __ATTR(nreset, 0660, nanohub_pin_nreset_get, nanohub_pin_nreset_set), |
| __ATTR(boot0, 0660, nanohub_pin_boot0_get, nanohub_pin_boot0_set), |
| __ATTR(boot2, 0660, nanohub_pin_boot2_get, nanohub_pin_boot2_set), |
| #ifdef CONFIG_NANOHUB_DISPLAY |
| __ATTR(display_select, 0660, nanohub_pin_display_select_get, |
| nanohub_pin_display_select_set), |
| __ATTR(display_state, 0440, nanohub_get_display_state, NULL), |
| #endif |
| #ifdef CONFIG_NANOHUB_BL_ST |
| __ATTR(lock, 0220, NULL, nanohub_lock_bl), |
| __ATTR(unlock, 0220, NULL, nanohub_unlock_bl), |
| #endif |
| #ifdef CONFIG_NANOHUB_BL_NXP |
| __ATTR(firmware_name, 0440, nanohub_firmware_name_query, NULL), |
| __ATTR(download_firmware, 0220, NULL, nanohub_download_firmware_request), |
| #endif |
| __ATTR(wakeup_event_msec, 0660, nanohub_wakeup_event_msec_get, |
| nanohub_wakeup_event_msec_set), |
| }; |
| |
| static inline int nanohub_create_sensor(struct nanohub_data *data) |
| { |
| int i, ret; |
| struct device *dev = data->io[ID_NANOHUB_COMMS].dev; |
| |
| for (i = 0, ret = 0; i < ARRAY_SIZE(attributes); i++) { |
| ret = device_create_file(dev, &attributes[i]); |
| if (ret) { |
| dev_err(dev, |
| "create sysfs attr %d [%s] failed; err=%d\n", |
| i, attributes[i].attr.name, ret); |
| goto fail_attr; |
| } |
| } |
| |
| ret = sysfs_create_link(&dev->kobj, |
| &data->iio_dev->dev.kobj, "iio"); |
| if (ret) { |
| dev_err(dev, |
| "sysfs_create_link failed; err=%d\n", ret); |
| goto fail_attr; |
| } |
| goto done; |
| |
| fail_attr: |
| for (i--; i >= 0; i--) |
| device_remove_file(dev, &attributes[i]); |
| done: |
| return ret; |
| } |
| |
| static int nanohub_create_devices(struct nanohub_data *data) |
| { |
| int i, j, k, ret; |
| static const struct device_config device_configs[] = { |
| { "nanohub_comms", 1, false }, |
| { "nanohub", ID_NANOHUB_CLIENT_NUM_IDS, false }, |
| { "nanohub_audio", 1, true }, |
| { "nanohub_display", 1, false }, |
| { "nanohub_render", 1, false }, |
| { "nanohub_debug_log", 1, false }, |
| { "nanohub_metrics", 1, false }, |
| { "nanohub_console", 1, false }, |
| { "nanohub_rpc0", 1, false }, |
| { "nanohub_rpc1", 1, false }, |
| { "nanohub_brightness", 1, false }, |
| { "nanohub_touch", 1, true }, |
| { "nanohub_display_kernel", 1, true }, |
| }; |
| static_assert(ARRAY_SIZE(device_configs) == ID_NANOHUB_MAX - ID_NANOHUB_CLIENT_NUM_IDS + 1); |
| |
| for (i = 0, j = 0; j < ID_NANOHUB_MAX - ID_NANOHUB_CLIENT_NUM_IDS + 1; ++j) { |
| struct device *dev = NULL; |
| if (!device_configs[j].is_kernel) { |
| dev = device_create(sensor_class, NULL, MKDEV(major, i), data, |
| device_configs[j].name); |
| if (IS_ERR(dev)) { |
| ret = PTR_ERR(dev); |
| pr_err("nanohub: device_create failed for %s; err=%d\n", |
| device_configs[j].name, ret); |
| goto fail_dev; |
| } |
| } |
| |
| for (k = 0; k < device_configs[j].channels; ++i, ++k) { |
| struct nanohub_io *io = &data->io[i]; |
| |
| nanohub_io_init(io, data, dev, i, device_configs[j].is_kernel); |
| } |
| } |
| |
| ret = nanohub_create_sensor(data); |
| if (!ret) |
| goto done; |
| |
| fail_dev: |
| for (--i; i >= 0; --i) |
| device_destroy(sensor_class, MKDEV(major, i)); |
| done: |
| return ret; |
| } |
| |
| static int nanohub_match_devt(struct device *dev, const void *data) |
| { |
| const dev_t *devt = data; |
| |
| return dev->devt == *devt; |
| } |
| |
| static int nanohub_open(struct inode *inode, struct file *file) |
| { |
| dev_t devt = inode->i_rdev; |
| struct device *dev; |
| int i; |
| |
| dev = class_find_device(sensor_class, NULL, &devt, nanohub_match_devt); |
| if (dev) { |
| struct nanohub_data *data = dev_get_drvdata(dev); |
| if (MINOR(devt) == ID_NANOHUB_CLIENT) { |
| for (i = 0; i<ID_NANOHUB_CLIENT_NUM_IDS; ++i) { |
| struct nanohub_io *io = |
| &data->io[ID_NANOHUB_CLIENT + i]; |
| if (atomic_cmpxchg(&io->busy, 0, 1) == 0) { |
| file->private_data = io; |
| nonseekable_open(inode, file); |
| return 0; |
| } |
| } |
| return -EBUSY; |
| } else { |
| file->private_data = &data->io[MINOR(devt)]; |
| nonseekable_open(inode, file); |
| return 0; |
| } |
| } |
| |
| return -ENODEV; |
| } |
| |
| static ssize_t nanohub_read(struct file *file, char *buffer, size_t length, |
| loff_t *offset) |
| { |
| struct nanohub_io *io = file->private_data; |
| struct nanohub_data *data = io->data; |
| struct nanohub_buf *buf; |
| int ret; |
| |
| if (!nanohub_io_has_buf(io) && (file->f_flags & O_NONBLOCK)) |
| return -EAGAIN; |
| |
| buf = nanohub_io_get_buf(io, true); |
| if (IS_ERR_OR_NULL(buf)) |
| return PTR_ERR(buf); |
| |
| ret = copy_to_user(buffer, buf->buffer, buf->length); |
| if (ret != 0) |
| ret = -EFAULT; |
| else |
| ret = buf->length; |
| |
| nanohub_io_put_buf(&data->free_pool, buf); |
| |
| return ret; |
| } |
| |
| static ssize_t nanohub_write(struct file *file, const char *buffer, |
| size_t length, loff_t *offset) |
| { |
| struct nanohub_io *io = file->private_data; |
| struct nanohub_data *data = io->data; |
| int ret; |
| |
| ret = request_wakeup_timeout(data, WAKEUP_TIMEOUT_MS); |
| if (ret) |
| return ret; |
| |
| ret = nanohub_comms_write(data, io->id, buffer, length, true); |
| |
| release_wakeup(data); |
| |
| return ret; |
| } |
| |
| ssize_t nanohub_send_message(int channel_id, const char *buffer, size_t length) |
| { |
| struct nanohub_data *data = priv_nanohub_data; |
| int ret; |
| |
| if (!data) { |
| pr_warn("%s: nanohub is not ready for kernel access\n", __func__); |
| return -EAGAIN; |
| } |
| |
| ret = request_wakeup_timeout(data, WAKEUP_TIMEOUT_MS); |
| if (ret) |
| return ret; |
| |
| ret = nanohub_comms_write(data, channel_id, buffer, length, false); |
| |
| release_wakeup(data); |
| |
| return ret; |
| } |
| |
| void nanohub_register_listener(int channel_id, |
| void (*on_message_received)(const char *buffer, |
| size_t length)) |
| { |
| struct nanohub_data *data = priv_nanohub_data; |
| struct nanohub_io *io; |
| if (!data) { |
| pr_warn("%s: nanohub is not ready for kernel access\n", __func__); |
| return; |
| } |
| io = &data->io[channel_id]; |
| io->on_message_received = on_message_received; |
| } |
| |
| void nanohub_unregister_listener(int channel_id) |
| { |
| struct nanohub_data *data = priv_nanohub_data; |
| struct nanohub_io *io; |
| if (!data) { |
| pr_warn("%s: nanohub is not ready for kernel access\n", __func__); |
| return; |
| } |
| io = &data->io[channel_id]; |
| io->on_message_received = NULL; |
| } |
| |
| int nanohub_query_display_state(void) |
| { |
| #ifdef CONFIG_NANOHUB_DISPLAY |
| return nanohub_query_display_state_internal(priv_nanohub_data); |
| #else |
| return -ENOSYS; |
| #endif |
| } |
| |
| static unsigned int nanohub_poll(struct file *file, poll_table *wait) |
| { |
| struct nanohub_io *io = file->private_data; |
| unsigned int mask = POLLOUT | POLLWRNORM; |
| |
| poll_wait(file, &io->buf_wait, wait); |
| |
| if (nanohub_io_has_buf(io)) |
| mask |= POLLIN | POLLRDNORM; |
| |
| return mask; |
| } |
| |
| static int nanohub_release(struct inode *inode, struct file *file) |
| { |
| dev_t devt = inode->i_rdev; |
| if (MINOR(devt) == ID_NANOHUB_CLIENT) { |
| int drop_cnt = 0; |
| struct nanohub_buf *buf; |
| struct nanohub_io *io = file->private_data; |
| struct nanohub_data *data = io->data; |
| |
| /* discard packets on release */ |
| atomic_set(&io->busy, 2); |
| while ((buf = nanohub_io_get_buf(io, false)) != NULL) { |
| nanohub_io_put_buf(&data->free_pool, buf); |
| drop_cnt++; |
| } |
| if (drop_cnt > 0) { |
| pr_info("nanohub: Discarded %d buffers on release", |
| drop_cnt); |
| } |
| |
| atomic_set(&io->busy, 0); |
| } |
| file->private_data = NULL; |
| return 0; |
| } |
| |
| static void nanohub_destroy_devices(struct nanohub_data *data) |
| { |
| int i; |
| struct device *dev = data->io[ID_NANOHUB_COMMS].dev; |
| |
| sysfs_remove_link(&dev->kobj, "iio"); |
| for (i = 0; i < ARRAY_SIZE(attributes); i++) |
| device_remove_file(dev, &attributes[i]); |
| for (i = 0; i < ID_NANOHUB_MAX; ++i) |
| device_destroy(sensor_class, MKDEV(major, i)); |
| } |
| |
| static irqreturn_t nanohub_irq1(int irq, void *dev_id) |
| { |
| struct nanohub_data *data = (struct nanohub_data *)dev_id; |
| |
| nanohub_handle_irq1(data); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static irqreturn_t nanohub_irq2(int irq, void *dev_id) |
| { |
| struct nanohub_data *data = (struct nanohub_data *)dev_id; |
| |
| nanohub_handle_irq2(data); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static bool nanohub_os_log(char *buffer, int len) |
| { |
| if (le32_to_cpu((((uint32_t *)buffer)[0]) & 0x7FFFFFFF) == |
| OS_LOG_EVENTID) { |
| char *mtype, *mdata = &buffer[5]; |
| |
| buffer[len] = 0x00; |
| |
| switch (buffer[4]) { |
| case 'E': |
| mtype = KERN_ERR; |
| break; |
| case 'W': |
| mtype = KERN_WARNING; |
| break; |
| case 'I': |
| mtype = KERN_INFO; |
| break; |
| case 'D': |
| mtype = KERN_DEBUG; |
| break; |
| default: |
| mtype = KERN_DEFAULT; |
| mdata--; |
| break; |
| } |
| printk("%snanohub: %s", mtype, mdata); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| static void nanohub_process_buffer(struct nanohub_data *data, uint8_t id, |
| struct nanohub_buf **buf, int ret) |
| { |
| bool wakeup = false; |
| struct nanohub_io *io; |
| |
| data->kthread_err_cnt = 0; |
| if (id == ID_NANOHUB_COMMS && (ret < 4 || nanohub_os_log((*buf)->buffer, ret))) { |
| release_wakeup(data); |
| return; |
| } else if (id >= ID_NANOHUB_MAX) { |
| pr_err("nanohub_process_buffer: id %d >= max %d\n", id, ID_NANOHUB_MAX); |
| release_wakeup(data); |
| return; |
| } |
| |
| (*buf)->length = ret; |
| wakeup = true; |
| io = &data->io[id]; |
| |
| if (id == ID_NANOHUB_CLIENT && atomic_read(&io->busy) != 1) { |
| pr_info("nanohub: dropping packet for /dev/nanohub/%d\n", id); |
| } else if (!io->is_kernel) { |
| nanohub_io_put_buf(io, *buf); |
| *buf = NULL; |
| } |
| |
| if (wakeup) |
| __pm_wakeup_event(&data->wakesrc_read, data->wakeup_event_msec); |
| release_wakeup(data); |
| if (io->is_kernel && io->on_message_received) { |
| io->on_message_received((*buf)->buffer, (*buf)->length); |
| } |
| } |
| |
| static int nanohub_kthread(void *arg) |
| { |
| struct nanohub_data *data = (struct nanohub_data *)arg; |
| struct nanohub_buf *buf = NULL; |
| int ret; |
| ktime_t ktime_delta; |
| uint32_t clear_interrupts[8] = { 0x00000006 }; |
| uint8_t rx_id; |
| struct device *dev = data->io[ID_NANOHUB_COMMS].dev; |
| static const struct sched_param param = { |
| .sched_priority = (MAX_RT_PRIO/2)-1, |
| }; |
| |
| data->kthread_err_cnt = 0; |
| sched_setscheduler(current, SCHED_FIFO, ¶m); |
| nanohub_set_state(data, ST_RESET); |
| |
| while (!kthread_should_stop()) { |
| switch (nanohub_get_state(data)) { |
| case ST_RESET: |
| nanohub_reset(data); |
| nanohub_wakeup_unlock(data); |
| nanohub_set_state(data, ST_IDLE); |
| break; |
| case ST_IDLE: |
| wait_event_interruptible(data->kthread_wait, |
| atomic_read(&data->kthread_run) |
| ); |
| nanohub_set_state(data, ST_RUNNING); |
| break; |
| case ST_ERROR: |
| ktime_delta = ktime_sub(ktime_get_boottime(), |
| data->kthread_err_ktime); |
| if (ktime_to_ns(ktime_delta) > KTHREAD_ERR_TIME_NS |
| && data->kthread_err_cnt > KTHREAD_ERR_CNT) { |
| dev_info(dev, "kthread: consistent error\n"); |
| // dev_info(dev, |
| // "kthread: hard reset due to consistent error\n"); |
| // ret = nanohub_hw_reset(data); // |
| // if (ret) { |
| // dev_info(dev, |
| // "%s: failed to reset nanohub: ret=%d\n", |
| // __func__, ret); |
| // } |
| } |
| msleep_interruptible(WAKEUP_TIMEOUT_MS); |
| nanohub_set_state(data, ST_RUNNING); |
| break; |
| case ST_RUNNING: |
| break; |
| } |
| atomic_set(&data->kthread_run, 0); |
| |
| #ifdef CONFIG_NANOHUB_BL_NXP |
| if (atomic_read(&data->firmware_request.state) == FW_REQUESTED) { |
| nanohub_download_firmware(dev); |
| nanohub_set_state(data, ST_IDLE); |
| continue; |
| } |
| #endif |
| |
| if (!buf) |
| buf = nanohub_io_get_buf(&data->free_pool, |
| false); |
| if (buf) { |
| ret = request_wakeup_timeout(data, WAKEUP_TIMEOUT_MS); |
| if (ret) { |
| dev_info(dev, |
| "%s: request_wakeup_timeout: ret=%d\n", |
| __func__, ret); |
| continue; |
| } |
| |
| ret = nanohub_comms_rx_retrans_boottime( |
| data, CMD_COMMS_READ, ID_NANOHUB_COMMS, &rx_id, |
| buf->buffer, sizeof(buf->buffer), 10, 0); |
| |
| if (ret > 0) { |
| nanohub_process_buffer(data, rx_id, &buf, ret); |
| if (!nanohub_irq1_fired(data) && |
| !nanohub_irq2_fired(data)) { |
| nanohub_set_state(data, ST_IDLE); |
| continue; |
| } |
| } else if (ret == 0) { |
| /* queue empty, go to sleep */ |
| data->kthread_err_cnt = 0; |
| data->interrupts[0] &= ~0x00000006; |
| release_wakeup(data); |
| nanohub_set_state(data, ST_IDLE); |
| continue; |
| } else { |
| release_wakeup(data); |
| if (data->kthread_err_cnt == 0) |
| data->kthread_err_ktime = |
| ktime_get_boottime(); |
| |
| data->kthread_err_cnt++; |
| if (data->kthread_err_cnt >= KTHREAD_WARN_CNT) { |
| dev_err(dev, |
| "%s: kthread_err_cnt=%d\n", |
| __func__, |
| data->kthread_err_cnt); |
| nanohub_set_state(data, ST_ERROR); |
| continue; |
| } |
| } |
| } else { |
| if (!nanohub_irq1_fired(data) && |
| !nanohub_irq2_fired(data)) { |
| nanohub_set_state(data, ST_IDLE); |
| continue; |
| } |
| pr_warn_ratelimited("nanohub: buffers exhausted"); |
| /* pending interrupt, but no room to read data - |
| * clear interrupts */ |
| if (request_wakeup(data)) |
| continue; |
| nanohub_comms_tx_rx_retrans(data, |
| CMD_COMMS_CLR_GET_INTR, |
| ID_NANOHUB_COMMS, |
| (uint8_t *) |
| clear_interrupts, |
| sizeof(clear_interrupts), |
| NULL, |
| (uint8_t *) data-> |
| interrupts, |
| sizeof(data->interrupts), |
| false, 10, 0); |
| release_wakeup(data); |
| nanohub_set_state(data, ST_IDLE); |
| } |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_OF |
| static struct nanohub_platform_data *nanohub_parse_dt(struct device *dev) |
| { |
| struct nanohub_platform_data *pdata; |
| struct device_node *dt = dev->of_node; |
| struct device_node *spmi_node; |
| struct platform_device *spmi_dev; |
| #ifdef CONFIG_NANOHUB_BL_ST |
| const uint32_t *tmp; |
| struct property *prop; |
| uint32_t u, i; |
| #endif |
| int ret; |
| u32 val = 0; |
| |
| if (!dt) |
| return ERR_PTR(-ENODEV); |
| |
| pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); |
| if (!pdata) |
| return ERR_PTR(-ENOMEM); |
| |
| ret = pdata->irq1_gpio = |
| of_get_named_gpio(dt, "sensorhub,irq1-gpio", 0); |
| if (ret < 0) { |
| pr_err("nanohub: missing sensorhub,irq1-gpio in device tree\n"); |
| goto free_pdata; |
| } |
| |
| /* optional (strongly recommended) */ |
| pdata->irq2_gpio = of_get_named_gpio(dt, "sensorhub,irq2-gpio", 0); |
| |
| ret = pdata->wakeup_gpio = |
| of_get_named_gpio(dt, "sensorhub,wakeup-gpio", 0); |
| if (ret < 0) { |
| pr_err |
| ("nanohub: missing sensorhub,wakeup-gpio in device tree\n"); |
| goto free_pdata; |
| } |
| |
| ret = pdata->nreset_gpio = |
| of_get_named_gpio(dt, "sensorhub,nreset-gpio", 0); |
| if (ret < 0) { |
| pr_err |
| ("nanohub: missing sensorhub,nreset-gpio in device tree\n"); |
| goto free_pdata; |
| } |
| |
| ret = pdata->boot0_gpio = |
| of_get_named_gpio(dt, "sensorhub,boot0-gpio", 0); |
| if (ret < 0) { |
| pr_err("nanohub: missing sensorhub,boot0-gpio in device tree\n"); |
| goto free_pdata; |
| } |
| |
| ret = pdata->boot2_gpio = |
| of_get_named_gpio(dt, "sensorhub,boot2-gpio", 0); |
| if (ret < 0) { |
| pr_err("nanohub: missing sensorhub,boot2-gpio in device tree\n"); |
| goto free_pdata; |
| } |
| |
| #ifdef CONFIG_NANOHUB_DISPLAY |
| /* optional (display select gpio) */ |
| pdata->display_select_gpio = |
| of_get_named_gpio(dt, "sensorhub,display-select-gpio", 0); |
| #endif |
| |
| /* optional (spi) */ |
| pdata->spi_cs_gpio = of_get_named_gpio(dt, "sensorhub,spi-cs-gpio", 0); |
| |
| /* required spmi controller info */ |
| spmi_node = of_parse_phandle(dt, "sensorhub,spmi", 0); |
| if (!spmi_node) { |
| pr_err("nanohub: unable to find sensorhub,spmi node\n"); |
| ret = -EINVAL; |
| goto free_pdata; |
| } |
| spmi_dev = of_find_device_by_node(spmi_node); |
| of_node_put(spmi_node); |
| if (!spmi_dev) { |
| pr_err("nanohub: unable to find sensorhub,spmi device\n"); |
| ret = -EINVAL; |
| goto free_pdata; |
| } |
| pdata->pmic_regmap = dev_get_regmap(spmi_dev->dev.parent, NULL); |
| if (!pdata->pmic_regmap) { |
| pr_err("nanohub: unable to get spmi controller regmap\n"); |
| ret = -EINVAL; |
| goto free_pdata; |
| } |
| |
| /* required afe_tx_sup control register */ |
| ret = of_property_read_u32(dt, "sensorhub,afe_control", |
| &pdata->afe_control_reg); |
| if (ret < 0) { |
| pr_err("nanohub: missing sensorhub,afe_control in device tree\n"); |
| goto free_pdata; |
| } |
| |
| /* required vddcore control register and voltage */ |
| ret = of_property_read_u32(dt, "sensorhub,vddcore_control", |
| &pdata->vddcore_control_reg); |
| if (ret < 0) { |
| pr_err("nanohub: missing sensorhub,vddcore_control in device tree\n"); |
| goto free_pdata; |
| } |
| ret = of_property_read_u32(dt, "sensorhub,vddcore_voltage", |
| &pdata->vddcore_voltage); |
| if (ret < 0) { |
| pr_err("nanohub: missing sensorhub,vddcore_voltage in device tree\n"); |
| goto free_pdata; |
| } |
| |
| /* required MCU 32KHz clock control register */ |
| ret = of_property_read_u32(dt, "sensorhub,clk32_control", |
| &pdata->clk32_control_reg); |
| if (ret < 0) { |
| pr_err("nanohub: missing sensorhub,clk32_control in device tree\n"); |
| goto free_pdata; |
| } |
| |
| /* required bob ext ctrl1 control register */ |
| ret = of_property_read_u32(dt, "sensorhub,bob_ext_ctrl1_control", |
| &pdata->bob_ext_ctrl1_control_reg); |
| if (ret < 0) { |
| pr_err("nanohub: missing sensorhub,bob_ext_ctrl1_control in device tree\n"); |
| goto free_pdata; |
| } |
| |
| /* required S5A PGOOD State control register */ |
| ret = of_property_read_u32(dt, "sensorhub,s5a_pgood_state_control", |
| &pdata->s5a_pgood_state_reg); |
| if (ret < 0) { |
| pr_err("nanohub: missing sensorhub,s5a_pgood_state_control in device tree\n"); |
| goto free_pdata; |
| } |
| |
| ret = of_property_read_u32(dt, "sensorhub,pmic_irq_enable", &val); |
| if (!ret && val) { |
| nanohub_pmic_irq_config(pdata->pmic_regmap); |
| } |
| |
| #ifdef CONFIG_NANOHUB_BL_ST |
| /* optional (bl-max-frequency) */ |
| pdata->bl_max_speed_hz = BL_MAX_SPEED_HZ; |
| ret = of_property_read_u32(dt, "sensorhub,bl-max-frequency", &u); |
| if (!ret) |
| pdata->bl_max_speed_hz = u; |
| |
| /* optional (stm32f bootloader) */ |
| of_property_read_u32(dt, "sensorhub,bl-addr", &pdata->bl_addr); |
| |
| /* optional (stm32f bootloader) */ |
| tmp = of_get_property(dt, "sensorhub,num-flash-banks", NULL); |
| if (tmp) { |
| pdata->num_flash_banks = be32_to_cpup(tmp); |
| pdata->flash_banks = |
| devm_kzalloc(dev, |
| sizeof(struct nanohub_flash_bank) * |
| pdata->num_flash_banks, GFP_KERNEL); |
| if (!pdata->flash_banks) |
| goto no_mem; |
| |
| /* TODO: investigate replacing with of_property_read_u32_array |
| */ |
| i = 0; |
| of_property_for_each_u32(dt, "sensorhub,flash-banks", prop, tmp, |
| u) { |
| if (i / 3 >= pdata->num_flash_banks) |
| break; |
| switch (i % 3) { |
| case 0: |
| pdata->flash_banks[i / 3].bank = u; |
| break; |
| case 1: |
| pdata->flash_banks[i / 3].address = u; |
| break; |
| case 2: |
| pdata->flash_banks[i / 3].length = u; |
| break; |
| } |
| i++; |
| } |
| } |
| |
| /* optional (stm32f bootloader) */ |
| tmp = of_get_property(dt, "sensorhub,num-shared-flash-banks", NULL); |
| if (tmp) { |
| pdata->num_shared_flash_banks = be32_to_cpup(tmp); |
| pdata->shared_flash_banks = |
| devm_kzalloc(dev, |
| sizeof(struct nanohub_flash_bank) * |
| pdata->num_shared_flash_banks, GFP_KERNEL); |
| if (!pdata->shared_flash_banks) |
| goto no_mem_shared; |
| |
| /* TODO: investigate replacing with of_property_read_u32_array |
| */ |
| i = 0; |
| of_property_for_each_u32(dt, "sensorhub,shared-flash-banks", |
| prop, tmp, u) { |
| if (i / 3 >= pdata->num_shared_flash_banks) |
| break; |
| switch (i % 3) { |
| case 0: |
| pdata->shared_flash_banks[i / 3].bank = u; |
| break; |
| case 1: |
| pdata->shared_flash_banks[i / 3].address = u; |
| break; |
| case 2: |
| pdata->shared_flash_banks[i / 3].length = u; |
| break; |
| } |
| i++; |
| } |
| } |
| #endif |
| |
| #ifdef CONFIG_NANOHUB_BL_NXP |
| pdata->firmware_name = of_get_property(dt, "sensorhub,firmware", NULL); |
| if (!pdata->firmware_name) { |
| pr_err("nanohub: missing sensorhub,firmware in device tree\n"); |
| ret = -EINVAL; |
| goto free_pdata; |
| } |
| #endif |
| |
| return pdata; |
| |
| #ifdef CONFIG_NANOHUB_BL_ST |
| no_mem_shared: |
| devm_kfree(dev, pdata->flash_banks); |
| no_mem: |
| ret = -ENOMEM; |
| #endif |
| free_pdata: |
| devm_kfree(dev, pdata); |
| return ERR_PTR(ret); |
| } |
| #else |
| static struct nanohub_data *nanohub_parse_dt(struct device *dev) |
| { |
| struct nanohub_platform_data *pdata; |
| |
| pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); |
| if (!pdata) |
| return ERR_PTR(-ENOMEM); |
| |
| pdata->bl_max_speed_hz = BL_MAX_SPEED_HZ; |
| return pdata; |
| } |
| #endif |
| |
| static int nanohub_request_irqs(struct nanohub_data *data) |
| { |
| int ret; |
| |
| ret = request_threaded_irq(data->irq1, NULL, nanohub_irq1, |
| IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
| "nanohub-irq1", data); |
| if (ret < 0) |
| data->irq1 = 0; |
| else |
| disable_irq(data->irq1); |
| if (data->irq2 <= 0 || ret < 0) { |
| data->irq2 = 0; |
| return ret; |
| } |
| |
| ret = request_threaded_irq(data->irq2, NULL, nanohub_irq2, |
| IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
| "nanohub-irq2", data); |
| if (ret < 0) { |
| data->irq2 = 0; |
| WARN(1, "failed to request optional IRQ %d; err=%d", |
| data->irq2, ret); |
| } else { |
| disable_irq(data->irq2); |
| } |
| |
| /* if 2d request fails, hide this; it is optional IRQ, |
| * and failure should not interrupt driver init sequence. |
| */ |
| return 0; |
| } |
| |
| static int nanohub_request_gpios(struct nanohub_data *data) |
| { |
| int i, ret = 0; |
| |
| for (i = 0; i < ARRAY_SIZE(gconf); ++i) { |
| const struct gpio_config *cfg = &gconf[i]; |
| unsigned int gpio = plat_gpio_get(data, cfg); |
| const char *label; |
| bool optional = gpio_is_optional(cfg); |
| |
| ret = 0; /* clear errors on optional pins, if any */ |
| |
| if (!gpio_is_valid(gpio) && optional) |
| continue; |
| |
| label = cfg->label; |
| ret = gpio_request_one(gpio, cfg->flags, label); |
| if (ret && !optional) { |
| pr_err("nanohub: gpio %d[%s] request failed;err=%d\n", |
| gpio, label, ret); |
| break; |
| } |
| if (gpio_has_irq(cfg)) { |
| int irq = gpio_to_irq(gpio); |
| if (irq > 0) { |
| nanohub_set_irq_data(data, cfg, irq); |
| } else if (!optional) { |
| ret = -EINVAL; |
| pr_err("nanohub: no irq; gpio %d[%s];err=%d\n", |
| gpio, label, irq); |
| break; |
| } |
| } |
| } |
| if (i < ARRAY_SIZE(gconf)) { |
| for (--i; i >= 0; --i) |
| gpio_free(plat_gpio_get(data, &gconf[i])); |
| } |
| |
| return ret; |
| } |
| |
| static void nanohub_release_gpios_irqs(struct nanohub_data *data) |
| { |
| const struct nanohub_platform_data *pdata = data->pdata; |
| |
| if (data->irq2) |
| free_irq(data->irq2, data); |
| if (data->irq1) |
| free_irq(data->irq1, data); |
| if (gpio_is_valid(pdata->irq2_gpio)) |
| gpio_free(pdata->irq2_gpio); |
| gpio_free(pdata->irq1_gpio); |
| //gpio_set_value(pdata->nreset_gpio, 0); |
| gpio_free(pdata->nreset_gpio); |
| mcu_wakeup_gpio_set_value(data, 1); |
| gpio_free(pdata->wakeup_gpio); |
| gpio_set_value(pdata->boot0_gpio, 1); |
| gpio_free(pdata->boot0_gpio); |
| gpio_free(pdata->boot2_gpio); |
| #ifdef CONFIG_NANOHUB_DISPLAY |
| gpio_free(pdata->display_select_gpio); |
| #endif |
| } |
| |
| struct iio_dev *nanohub_probe(struct device *dev, struct iio_dev *iio_dev) |
| { |
| int ret, i; |
| const struct nanohub_platform_data *pdata; |
| struct nanohub_data *data; |
| struct nanohub_buf *buf; |
| bool own_iio_dev = !iio_dev; |
| |
| pdata = dev_get_platdata(dev); |
| if (!pdata) { |
| pdata = nanohub_parse_dt(dev); |
| if (IS_ERR(pdata)) |
| return ERR_PTR(PTR_ERR(pdata)); |
| } |
| |
| if (own_iio_dev) { |
| iio_dev = iio_device_alloc(dev, sizeof(struct nanohub_data)); |
| if (!iio_dev) |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| iio_dev->name = "nanohub"; |
| iio_dev->dev.parent = dev; |
| iio_dev->info = &nanohub_iio_info; |
| iio_dev->channels = NULL; |
| iio_dev->num_channels = 0; |
| |
| data = iio_priv(iio_dev); |
| data->iio_dev = iio_dev; |
| data->pdata = pdata; |
| |
| data->wakeup_event_msec = 100; /* Default 100 msec timed wakelock per event */ |
| |
| init_waitqueue_head(&data->kthread_wait); |
| |
| nanohub_io_init(&data->free_pool, data, dev, 0, false); |
| |
| buf = vmalloc(sizeof(*buf) * READ_QUEUE_DEPTH); |
| data->vbuf = buf; |
| if (!buf) { |
| ret = -ENOMEM; |
| goto fail_vma; |
| } |
| |
| for (i = 0; i < READ_QUEUE_DEPTH; i++) |
| nanohub_io_put_buf(&data->free_pool, &buf[i]); |
| atomic_set(&data->kthread_run, 0); |
| data->wakesrc_read.name = "nanohub_wake"; |
| wakeup_source_add(&data->wakesrc_read); |
| |
| /* hold lock until reset completes */ |
| atomic_set(&data->lock_mode, LOCK_MODE_RESET); |
| atomic_set(&data->wakeup_cnt, 0); |
| atomic_set(&data->wakeup_lock_cnt, 1); |
| atomic_set(&data->wakeup_acquired, KEY_WAKEUP_LOCK); |
| init_waitqueue_head(&data->wakeup_wait); |
| |
| ret = nanohub_request_gpios(data); |
| if (ret) |
| goto fail_gpio; |
| |
| ret = nanohub_request_irqs(data); |
| if (ret) |
| goto fail_irq; |
| |
| ret = iio_device_register(iio_dev); |
| if (ret) { |
| pr_err("nanohub: iio_device_register failed\n"); |
| goto fail_irq; |
| } |
| |
| ret = nanohub_create_devices(data); |
| if (ret) |
| goto fail_dev; |
| |
| #ifdef CONFIG_NANOHUB_BL_NXP |
| atomic_set(&data->firmware_request.state, FW_IDLE); |
| init_completion(&data->firmware_request.fw_complete); |
| #endif |
| |
| data->thread = kthread_create(nanohub_kthread, data, "nanohub"); |
| if (!IS_ERR(data->thread)) { |
| priv_nanohub_data = data; |
| return iio_dev; |
| } |
| |
| fail_dev: |
| iio_device_unregister(iio_dev); |
| fail_irq: |
| nanohub_release_gpios_irqs(data); |
| fail_gpio: |
| wakeup_source_remove(&data->wakesrc_read); |
| vfree(buf); |
| fail_vma: |
| if (own_iio_dev) |
| iio_device_free(iio_dev); |
| |
| return ERR_PTR(ret); |
| } |
| |
| void nanohub_start(struct nanohub_data *data) |
| { |
| wake_up_process(data->thread); |
| } |
| |
| int nanohub_reset(struct nanohub_data *data) |
| { |
| const struct nanohub_platform_data *pdata = data->pdata; |
| |
| gpio_set_value(pdata->nreset_gpio, 1); |
| usleep_range(650000, 700000); |
| return 0; |
| } |
| |
| int nanohub_remove(struct iio_dev *iio_dev) |
| { |
| struct nanohub_data *data = iio_priv(iio_dev); |
| |
| priv_nanohub_data = NULL; |
| nanohub_notify_thread(data); |
| kthread_stop(data->thread); |
| |
| nanohub_destroy_devices(data); |
| iio_device_unregister(iio_dev); |
| nanohub_release_gpios_irqs(data); |
| wakeup_source_remove(&data->wakesrc_read); |
| vfree(data->vbuf); |
| iio_device_free(iio_dev); |
| |
| return 0; |
| } |
| |
| int nanohub_suspend(struct iio_dev *iio_dev) |
| { |
| struct nanohub_data *data = iio_priv(iio_dev); |
| const struct nanohub_platform_data *pdata = data->pdata; |
| int ret; |
| |
| ret = nanohub_wakeup_lock(data, LOCK_MODE_SUSPEND_RESUME); |
| if (!ret) { |
| int cnt; |
| const int max_cnt = 10; |
| |
| for (cnt = 0; cnt < max_cnt; ++cnt) { |
| if (!nanohub_irq1_fired(data)) |
| break; |
| usleep_range(10, 15); |
| } |
| if (cnt < max_cnt) { |
| dev_dbg(&iio_dev->dev, "%s: cnt=%d\n", __func__, cnt); |
| enable_irq_wake(data->irq1); |
| gpio_set_value(pdata->boot0_gpio, 1); |
| return 0; |
| } |
| ret = -EBUSY; |
| dev_info(&iio_dev->dev, |
| "%s: failed to suspend: IRQ1=%d, state=%d\n", |
| __func__, nanohub_irq1_fired(data), |
| nanohub_get_state(data)); |
| nanohub_wakeup_unlock(data); |
| } else { |
| dev_info(&iio_dev->dev, "%s: could not take wakeup lock\n", |
| __func__); |
| } |
| |
| return ret; |
| } |
| |
| int nanohub_suspend_noirq(struct iio_dev *iio_dev) |
| { |
| struct nanohub_data *data = iio_priv(iio_dev); |
| int ret = 0; |
| |
| if (nanohub_irq1_fired(data)) { |
| ret = -EBUSY; |
| } |
| return ret; |
| } |
| |
| int nanohub_resume(struct iio_dev *iio_dev) |
| { |
| struct nanohub_data *data = iio_priv(iio_dev); |
| const struct nanohub_platform_data *pdata = data->pdata; |
| |
| disable_irq_wake(data->irq1); |
| gpio_set_value(pdata->boot0_gpio, 0); |
| nanohub_wakeup_unlock(data); |
| |
| return 0; |
| } |
| |
| static int __init nanohub_init(void) |
| { |
| int ret = 0; |
| |
| sensor_class = class_create(THIS_MODULE, "nanohub"); |
| if (IS_ERR(sensor_class)) { |
| ret = PTR_ERR(sensor_class); |
| pr_err("nanohub: class_create failed; err=%d\n", ret); |
| } |
| if (!ret) |
| major = __register_chrdev(0, 0, ID_NANOHUB_MAX, "nanohub", |
| &nanohub_fileops); |
| |
| if (major < 0) { |
| ret = major; |
| major = 0; |
| pr_err("nanohub: can't register; err=%d\n", ret); |
| } |
| |
| #ifdef CONFIG_NANOHUB_SPI |
| if (ret == 0) |
| ret = nanohub_spi_init(); |
| #endif |
| pr_info("nanohub: loaded; ret=%d\n", ret); |
| return ret; |
| } |
| |
| static void __exit nanohub_cleanup(void) |
| { |
| #ifdef CONFIG_NANOHUB_SPI |
| nanohub_spi_cleanup(); |
| #endif |
| __unregister_chrdev(major, 0, ID_NANOHUB_MAX, "nanohub"); |
| class_destroy(sensor_class); |
| major = 0; |
| sensor_class = 0; |
| } |
| |
| module_init(nanohub_init); |
| module_exit(nanohub_cleanup); |
| |
| MODULE_AUTHOR("Ben Fennema"); |
| MODULE_LICENSE("GPL"); |