| /* |
| * 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/slab.h> |
| #include <linux/iio/iio.h> |
| #include <linux/firmware.h> |
| #include <linux/fs.h> |
| #include <linux/cdev.h> |
| #include <linux/uaccess.h> |
| #include <linux/delay.h> |
| #include <linux/of_gpio.h> |
| #include <linux/of_irq.h> |
| #include <linux/interrupt.h> |
| #include <linux/poll.h> |
| #include <linux/list.h> |
| #include <linux/vmalloc.h> |
| #include <linux/spinlock.h> |
| #include <linux/semaphore.h> |
| #include <linux/sched.h> |
| #include <linux/sched/rt.h> |
| #include <linux/time.h> |
| #include <linux/platform_data/nanohub.h> |
| |
| #include "main.h" |
| #include "comms.h" |
| #include "bl.h" |
| #include "spi.h" |
| #include "bq27xxx_fuelgauge.h" |
| #include "custom_app_event.h" |
| |
| #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 |
| |
| enum APP_TO_HOST_EVENT_SUBID { |
| APP_TO_HOST_EVENT_SUBID_OTHERS = 0, |
| APP_TO_HOST_EVENT_SUBID_FUELGAUGE = 1, |
| APP_TO_HOST_EVENT_SUBID_HR_LOG, |
| APP_TO_HOST_EVENT_SUBID_FLASH, |
| APP_TO_HOST_EVENT_SUBID_CALIBRATE, |
| APP_TO_HOST_EVENT_SUBID_SELFTEST, |
| }; |
| |
| #define OS_LOG_EVENTID 0x3B474F4C |
| #define WAKEUP_INTERRUPT 1 |
| #define WAKEUP_TIMEOUT_MS 2000 |
| #define SUSPEND_TIMEOUT_MS 200 |
| #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 |
| |
| /** |
| * 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; |
| }; |
| |
| enum gpio_status { |
| STATUS_LOW, |
| STATUS_TOGGLE, |
| STATUS_HIGH, |
| STATUS_START_TO_CHECK_TIMEOUT, |
| }; |
| |
| #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); |
| |
| static struct class *sensor_class; |
| static int major; |
| |
| static const struct gpio_config gconf[] = { |
| { PLAT_GPIO_DEF(nreset, GPIOF_OUT_INIT_HIGH) }, |
| { PLAT_GPIO_DEF(wakeup, GPIOF_OUT_INIT_HIGH) }, |
| { PLAT_GPIO_DEF(int, GPIOF_OUT_INIT_HIGH) }, |
| { PLAT_GPIO_DEF(mode1, GPIOF_OUT_INIT_HIGH) }, |
| { PLAT_GPIO_DEF(mode2, GPIOF_OUT_INIT_HIGH) }, |
| { PLAT_GPIO_DEF(mode3, GPIOF_OUT_INIT_HIGH) }, |
| { PLAT_GPIO_DEF(mode4, GPIOF_OUT_INIT_HIGH) }, |
| { PLAT_GPIO_DEF(boot0, GPIOF_OUT_INIT_LOW) }, |
| { PLAT_GPIO_DEF_IRQ(irq1, GPIOF_DIR_IN, 0) }, |
| { PLAT_GPIO_DEF_IRQ(irq2, GPIOF_DIR_IN, GPIO_OPT_OPTIONAL) }, |
| { PLAT_GPIO_DEF_IRQ(irq3, GPIOF_DIR_IN, GPIO_OPT_OPTIONAL) }, |
| }; |
| |
| static const struct iio_info nanohub_iio_info = { |
| .driver_module = THIS_MODULE, |
| }; |
| |
| 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_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) |
| { |
| init_waitqueue_head(&io->buf_wait); |
| INIT_LIST_HEAD(&io->buf_list); |
| io->data = data; |
| io->dev = dev; |
| } |
| |
| 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; |
| } |
| |
| /* the following fragment is based on wait_event_* code from wait.h */ |
| #define wait_event_interruptible_timeout_locked(q, cond, tmo) \ |
| ({ \ |
| long __ret = (tmo); \ |
| DEFINE_WAIT(__wait); \ |
| if (!(cond)) { \ |
| for (;;) { \ |
| __wait.flags &= ~WQ_FLAG_EXCLUSIVE; \ |
| if (list_empty(&__wait.task_list)) \ |
| __add_wait_queue_tail(&(q), &__wait); \ |
| set_current_state(TASK_INTERRUPTIBLE); \ |
| if ((cond)) \ |
| break; \ |
| if (signal_pending(current)) { \ |
| __ret = -ERESTARTSYS; \ |
| break; \ |
| } \ |
| spin_unlock(&(q).lock); \ |
| __ret = schedule_timeout(__ret); \ |
| spin_lock(&(q).lock); \ |
| if (!__ret) { \ |
| if ((cond)) \ |
| __ret = 1; \ |
| break; \ |
| } \ |
| } \ |
| __set_current_state(TASK_RUNNING); \ |
| if (!list_empty(&__wait.task_list)) \ |
| list_del_init(&__wait.task_list); \ |
| else if (__ret == -ERESTARTSYS && \ |
| /*reimplementation of wait_abort_exclusive() */\ |
| waitqueue_active(&(q))) \ |
| __wake_up_locked_key(&(q), TASK_INTERRUPTIBLE, \ |
| NULL); \ |
| } else { \ |
| __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 *sensor_dev = data->io[ID_NANOHUB_SENSOR].dev; |
| int ret; |
| 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(sensor_dev, |
| "wakeup: hard reset due to consistent error\n"); |
| ret = nanohub_hw_reset(data); |
| if (ret) { |
| dev_info(sensor_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) |
| 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_SENSOR].dev; |
| int cmd = mask ? CMD_COMMS_MASK_INTR : CMD_COMMS_UNMASK_INTR; |
| |
| 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, |
| &interrupt, sizeof(interrupt), |
| &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); |
| } |
| |
| static inline void nanohub_unmask_interrupt(struct nanohub_data *data, |
| u8 interrupt) |
| { |
| __nanohub_interrupt_cfg(data, interrupt, false); |
| } |
| |
| static ssize_t nanohub_wakeup_query(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nanohub_data *data = dev_get_nanohub_data(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 INT3: %d\n", |
| gpio_get_value(pdata->wakeup_gpio), |
| gpio_get_value(pdata->irq1_gpio), |
| data->irq2 ? gpio_get_value(pdata->irq2_gpio) : -1, |
| data->irq3 ? gpio_get_value(pdata->irq3_gpio) : -1); |
| } |
| |
| static ssize_t nanohub_app_info(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nanohub_data *data = dev_get_nanohub_data(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, (uint8_t *)&i, |
| sizeof(i), (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_nanohub_data(dev); |
| uint16_t buffer[6]; |
| |
| if (request_wakeup_timeout(data, WAKEUP_TIMEOUT_MS)) |
| return -ERESTARTSYS; |
| |
| if (nanohub_comms_tx_rx_retrans |
| (data, CMD_COMMS_GET_OS_HW_VERSIONS, NULL, 0, (uint8_t *)&buffer, |
| sizeof(buffer), false, 10, 10) == sizeof(buffer)) { |
| release_wakeup(data); |
| data->nanohub_hw_type = buffer[0]; |
| data->nanohub_variant_version = buffer[5] << 16 | buffer[4]; |
| pr_info("nanohub_firmware_query: hw type 0x%04x, variant vesion 0x%08x\n", |
| data->nanohub_hw_type, |
| data->nanohub_variant_version); |
| 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 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 (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; |
| } |
| 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 (mode == LOCK_MODE_IO || mode == LOCK_MODE_IO_BL) |
| nanohub_bl_close(data); |
| 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; |
| } |
| |
| static void __nanohub_hw_reset(struct nanohub_data *data, int boot0) |
| { |
| const struct nanohub_platform_data *pdata = data->pdata; |
| |
| if (pdata->nreset_polarity == NRESET_ACTIVE_LOW) |
| gpio_set_value(pdata->nreset_gpio, 0); |
| else |
| gpio_set_value(pdata->nreset_gpio, 1); |
| |
| gpio_set_value(pdata->boot0_gpio, boot0 > 0); |
| usleep_range(30, 40); |
| if (pdata->nreset_polarity == NRESET_ACTIVE_LOW) |
| gpio_set_value(pdata->nreset_gpio, 1); |
| else |
| gpio_set_value(pdata->nreset_gpio, 0); |
| |
| if (boot0 > 0) |
| usleep_range(70000, 75000); |
| else if (!boot0) |
| usleep_range(750000, 800000); |
| nanohub_clear_err_cnt(data); |
| } |
| |
| static int nanohub_hw_reset(struct nanohub_data *data) |
| { |
| int ret; |
| |
| ret = nanohub_wakeup_lock(data, LOCK_MODE_RESET); |
| |
| if (!ret) { |
| __nanohub_hw_reset(data, 0); |
| nanohub_wakeup_unlock(data); |
| } |
| |
| 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_nanohub_data(dev); |
| int ret; |
| |
| ret = nanohub_hw_reset(data); |
| |
| return ret < 0 ? ret : count; |
| } |
| |
| int __nanohub_set_mode_pin(struct nanohub_data *data, enum AP_GPIO_CMD mode) |
| { |
| const struct nanohub_platform_data *pdata = data->pdata; |
| gpio_set_value(pdata->mode1_gpio, (mode & 0x08) ? 1 : 0); |
| gpio_set_value(pdata->mode2_gpio, (mode & 0x04) ? 1 : 0); |
| gpio_set_value(pdata->mode3_gpio, (mode & 0x02) ? 1 : 0); |
| gpio_set_value(pdata->mode4_gpio, (mode & 0x01) ? 1 : 0); |
| usleep_range(1000, 1500); |
| |
| if (gpio_get_value(pdata->int_gpio) != 0) { |
| gpio_set_value(pdata->int_gpio, 0); |
| usleep_range(1000, 1500); |
| } |
| gpio_set_value(pdata->int_gpio, 1); |
| /*creat a intterupt(high rise edge) to mcu*/ |
| usleep_range(10000, 15000); |
| gpio_set_value(pdata->int_gpio, 0); |
| |
| gpio_set_value(pdata->mode1_gpio, 0); |
| gpio_set_value(pdata->mode2_gpio, 0); |
| gpio_set_value(pdata->mode3_gpio, 0); |
| gpio_set_value(pdata->mode4_gpio, 0); |
| return 0; |
| } |
| |
| int __nanohub_send_AP_cmd(struct nanohub_data *data, enum AP_GPIO_CMD mode) |
| { |
| int ret = 0; |
| |
| if ((mode < GPIO_CMD_POWEROFF) || (mode > GPIO_CMD_RESEND)) { |
| pr_err("nanohub: invalid mode = %d\n", mode); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&(data->hub_mode_set_lock)); |
| |
| switch (mode) { |
| case GPIO_CMD_POWEROFF: /*0000*/ |
| case GPIO_CMD_BAND: /*0001*/ |
| atomic_set(&data->hub_mode_ap_pwr_down, mode); /* Only remember the state after AP pwr off */ |
| atomic_set(&data->hub_mode_ap_active, mode); |
| ret = __nanohub_set_mode_pin(data, mode); |
| break; |
| |
| case GPIO_CMD_AMBIENT: /*0010*/ |
| case GPIO_CMD_NORMAL: /*0011*/ |
| case GPIO_CMD_SUSPEND: /*0110*/ |
| case GPIO_CMD_RESUME: /*0111*/ |
| ret = __nanohub_set_mode_pin(data, mode); |
| atomic_set(&data->hub_mode_ap_active, mode); |
| break; |
| |
| case GPIO_CMD_FLASH_ERASE: /*0100*/ |
| case GPIO_CMD_REQUEST_FUELGAUGE: /*0101*/ |
| case GPIO_CMD_FORCE_REQUEST_FUELGAUGE: /*1000*/ |
| case GPIO_CMD_TEST: /*1111*/ |
| ret = __nanohub_set_mode_pin(data, mode); |
| break; |
| |
| case GPIO_CMD_RESEND: /*send mode again if mcu needed*/ |
| ret = __nanohub_set_mode_pin(data, |
| atomic_read(&data->hub_mode_ap_active)); |
| break; |
| |
| default: |
| break; |
| } |
| |
| mutex_unlock(&(data->hub_mode_set_lock)); |
| |
| return ret; |
| } |
| |
| static ssize_t nanohub_mode_set(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_nanohub_data(dev); |
| int mode = 0; |
| |
| if (sscanf(buf, "%d\n", &mode) > 0) |
| __nanohub_send_AP_cmd(data, mode); |
| |
| return count; |
| } |
| |
| static ssize_t nanohub_lcd_mutex(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct nanohub_data *data = dev_get_nanohub_data(dev); |
| int lcd_mutex = 0; |
| |
| if (sscanf(buf, "%d\n", &lcd_mutex) > 0) |
| atomic_set(&data->lcd_mutex, |
| lcd_mutex?LCD_MUTEX_ON:LCD_MUTEX_OFF); |
| |
| return count; |
| } |
| |
| 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_nanohub_data(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_nanohub_data(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_nanohub_data(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) { |
| atomic_set(&data->download_bl_status, |
| DOWNLOAD_BL_TIMEOUT); |
| return ret; |
| } |
| |
| atomic_set(&data->download_bl_status, |
| DOWNLOAD_BL_RUNNING); |
| |
| __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); |
| if (status == CMD_ACK) |
| atomic_set(&data->download_bl_status, |
| DOWNLOAD_BL_SUCCESS); |
| else |
| atomic_set(&data->download_bl_status, |
| DOWNLOAD_BL_FAILED); |
| |
| release_firmware(fw_entry); |
| } |
| __nanohub_hw_reset(data, 0); |
| nanohub_wakeup_unlock(data); |
| |
| return ret < 0 ? ret : count; |
| } |
| |
| static ssize_t nanohub_download_bl_status(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nanohub_data *data = dev_get_nanohub_data(dev); |
| char status[][10] = {"Not Start", |
| "Running", |
| "Success", |
| "Failed", |
| "Time Out"}; |
| return scnprintf(buf, PAGE_SIZE, |
| "%s\n", status[data->download_bl_status.counter]); |
| } |
| |
| 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_nanohub_data(dev); |
| const struct firmware *fw_entry; |
| int ret; |
| int ret_val; |
| |
| ret = request_firmware(&fw_entry, "nanohub.update.bin", dev); |
| if (ret) { |
| dev_err(dev, "nanohub_download_kernel: err=%d\n", ret); |
| ret_val = -EIO; |
| } else { |
| ret = |
| nanohub_comms_kernel_download(data, fw_entry->data, |
| fw_entry->size); |
| |
| release_firmware(fw_entry); |
| |
| ret_val = count; |
| } |
| |
| return ret_val; |
| } |
| |
| 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_nanohub_data(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; |
| } |
| |
| 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_nanohub_data(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, |
| (uint8_t *)&id, sizeof(id), |
| (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; |
| } |
| |
| 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_nanohub_data(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_nanohub_data(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; |
| } |
| |
| 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(download_bl, 0220, NULL, nanohub_download_bl), |
| __ATTR(download_kernel, 0220, NULL, nanohub_download_kernel), |
| __ATTR(download_kernel_bl, 0220, NULL, nanohub_download_kernel_bl), |
| __ATTR(download_app, 0220, NULL, nanohub_download_app), |
| __ATTR(erase_shared, 0220, NULL, nanohub_erase_shared), |
| __ATTR(erase_shared_bl, 0220, NULL, nanohub_erase_shared_bl), |
| __ATTR(reset, 0220, NULL, nanohub_try_hw_reset), |
| __ATTR(lock, 0220, NULL, nanohub_lock_bl), |
| __ATTR(unlock, 0220, NULL, nanohub_unlock_bl), |
| __ATTR(mode, 0220, NULL, nanohub_mode_set), |
| __ATTR(download_bl_status, 0444, nanohub_download_bl_status, NULL), |
| __ATTR(lcd_mutex, 0220, NULL, nanohub_lcd_mutex), |
| }; |
| |
| static inline int nanohub_create_sensor(struct nanohub_data *data) |
| { |
| int i, ret; |
| struct device *sensor_dev = data->io[ID_NANOHUB_SENSOR].dev; |
| |
| for (i = 0, ret = 0; i < ARRAY_SIZE(attributes); i++) { |
| ret = device_create_file(sensor_dev, &attributes[i]); |
| if (ret) { |
| dev_err(sensor_dev, |
| "create sysfs attr %d [%s] failed; err=%d\n", |
| i, attributes[i].attr.name, ret); |
| goto fail_attr; |
| } |
| } |
| |
| ret = sysfs_create_link(&sensor_dev->kobj, |
| &data->iio_dev->dev.kobj, "iio"); |
| if (ret) { |
| dev_err(sensor_dev, |
| "sysfs_create_link failed; err=%d\n", ret); |
| goto fail_attr; |
| } |
| goto done; |
| |
| fail_attr: |
| for (i--; i >= 0; i--) |
| device_remove_file(sensor_dev, &attributes[i]); |
| done: |
| return ret; |
| } |
| |
| static int nanohub_create_devices(struct nanohub_data *data) |
| { |
| int i, ret; |
| static const char *names[ID_NANOHUB_MAX] = { |
| "nanohub", "nanohub_comms", |
| "nanohub_hr_log", "nanohub_custom_flash" |
| }; |
| |
| for (i = 0; i < ID_NANOHUB_MAX; ++i) { |
| struct nanohub_io *io = &data->io[i]; |
| |
| nanohub_io_init(io, data, device_create(sensor_class, NULL, |
| MKDEV(major, i), |
| io, names[i])); |
| if (IS_ERR(io->dev)) { |
| ret = PTR_ERR(io->dev); |
| pr_err("nanohub: device_create failed for %s; err=%d\n", |
| names[i], ret); |
| goto fail_dev; |
| } |
| } |
| |
| 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; |
| |
| dev = class_find_device(sensor_class, NULL, &devt, nanohub_match_devt); |
| if (dev) { |
| file->private_data = dev_get_drvdata(dev); |
| 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; |
| |
| mutex_lock(&(data->nanohub_write_lock)); |
| ret = request_wakeup_timeout(data, WAKEUP_TIMEOUT_MS); |
| if (ret) { |
| pr_err("nanohub: error to request wakeup, ret = %d\n", ret); |
| mutex_unlock(&(data->nanohub_write_lock)); |
| return ret; |
| } |
| |
| ret = nanohub_comms_write(data, buffer, length); |
| |
| release_wakeup(data); |
| mutex_unlock(&(data->nanohub_write_lock)); |
| |
| return ret; |
| } |
| |
| 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) |
| { |
| file->private_data = NULL; |
| |
| return 0; |
| } |
| |
| static void nanohub_destroy_devices(struct nanohub_data *data) |
| { |
| int i; |
| struct device *sensor_dev = data->io[ID_NANOHUB_SENSOR].dev; |
| |
| sysfs_remove_link(&sensor_dev->kobj, "iio"); |
| for (i = 0; i < ARRAY_SIZE(attributes); i++) |
| device_remove_file(sensor_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); |
| |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| if (NANOHUB_WAKEUP_TRACE_ON == |
| atomic_read(&data->st_wakeup_trace)) { |
| data->wakeup_trace_irqs.nums_irq1++; |
| } |
| #endif |
| |
| 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); |
| |
| if (data->nanohub_hw_type == 0x4d70 && |
| data->nanohub_variant_version < 0x0000001b) |
| __nanohub_send_AP_cmd(data, GPIO_CMD_RESEND); |
| |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| if (NANOHUB_WAKEUP_TRACE_ON == |
| atomic_read(&data->st_wakeup_trace)) { |
| data->wakeup_trace_irqs.nums_irq2++; |
| } |
| #endif |
| |
| return IRQ_HANDLED; |
| } |
| |
| |
| static irqreturn_t nanohub_irq3(int irq, void *dev_id) |
| { |
| struct nanohub_data *data = (struct nanohub_data *)dev_id; |
| |
| __nanohub_send_AP_cmd(data, GPIO_CMD_RESEND); |
| |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| if (NANOHUB_WAKEUP_TRACE_ON == |
| atomic_read(&data->st_wakeup_trace)) { |
| data->wakeup_trace_irqs.nums_irq3++; |
| } |
| #endif |
| |
| 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; |
| } |
| } |
| |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| static const char * const interrupt_name[] = { |
| "INVALID", |
| "SENSOR_WAKEUP", |
| "SENSOR_NON_WAKEUP"}; |
| |
| static char *nanohub_get_event_name_from_id( |
| uint32_t event_id, |
| uint32_t event_sub_id) |
| { |
| char *evt_name; |
| |
| if (event_id > FIRST_SENSOR_EVENTID && |
| event_id <= LAST_SENSOR_EVENTID) |
| evt_name = "EVENT_SENSOR"; |
| else if (event_id == APP_TO_HOST_EVENTID) { |
| if (event_sub_id == APP_TO_HOST_EVENT_SUBID_FUELGAUGE) |
| evt_name = "EVENT_FUELGAUGE"; |
| else if (event_sub_id == APP_TO_HOST_EVENT_SUBID_HR_LOG) |
| evt_name = "EVENT_HR_LOG"; |
| else if (event_sub_id == APP_TO_HOST_EVENT_SUBID_FLASH) |
| evt_name = "EVENT_CUSTOM_FLASH"; |
| else if (event_sub_id == APP_TO_HOST_EVENT_SUBID_CALIBRATE) |
| evt_name = "EVENT_CALIBRATE_RESULT"; |
| else if (event_sub_id == APP_TO_HOST_EVENT_SUBID_SELFTEST) |
| evt_name = "EVENT_SELFTEST_RESULT"; |
| else |
| evt_name = "EVENT_OTHERS"; |
| } else if (event_id == OS_LOG_EVENTID) |
| evt_name = "EVENT_LOG"; |
| else if (event_id == APP_FROM_HOST_EVENTID) |
| evt_name = "EVENT_APP_FROM_HOST"; |
| else |
| evt_name = "EVENT_UNKNOWN"; |
| |
| return evt_name; |
| } |
| |
| static bool nanohub_wakeup_trace_event_same( |
| struct wakeup_trace_listnode *new_event, |
| struct wakeup_trace_listnode *cur_event) |
| { |
| if (new_event->trace_event.event_id != cur_event->trace_event.event_id) |
| return false; |
| if (new_event->trace_event.event_sub_id != |
| cur_event->trace_event.event_sub_id) |
| return false; |
| |
| return true; |
| } |
| |
| static void nanohub_wakeup_trace_add_to_list( |
| struct wakeup_trace_listnode *trace_head, |
| struct nanohub_buf **buf, int len) |
| { |
| struct wakeup_trace_listnode *new_trace_event = |
| kzalloc(sizeof(struct wakeup_trace_listnode), |
| GFP_KERNEL); |
| |
| struct wakeup_trace_listnode *tmp; |
| bool is_same_type = false; |
| |
| memset(new_trace_event, 0, sizeof(new_trace_event)); |
| new_trace_event->length = |
| len > sizeof(new_trace_event->buffer) ? |
| sizeof(new_trace_event->buffer) : len; |
| |
| memcpy(new_trace_event->buffer, |
| (*buf)->buffer, new_trace_event->length); |
| new_trace_event->length = len; |
| new_trace_event->trace_event.event_id = |
| le32_to_cpu((((uint32_t *)(*buf)->buffer)[0]) & 0x7FFFFFFF); |
| |
| if (new_trace_event->trace_event.event_id > FIRST_SENSOR_EVENTID && |
| new_trace_event->trace_event.event_id <= LAST_SENSOR_EVENTID) { |
| new_trace_event->trace_event.interrupt = |
| (*buf)->buffer[sizeof(uint32_t) + |
| sizeof(uint64_t) + 3]; |
| } else if (new_trace_event->trace_event.event_id == |
| APP_TO_HOST_EVENTID) { |
| struct SensorAppEventHeader *p_SensorAppEventHeader; |
| |
| p_SensorAppEventHeader = |
| (struct SensorAppEventHeader *) |
| &((*buf)->buffer[sizeof(uint32_t) |
| + sizeof(struct HostHubRawPacket)]); |
| if (!is_fuel_gauge_data(*buf, len)) |
| new_trace_event->trace_event.event_sub_id = |
| APP_TO_HOST_EVENT_SUBID_FUELGAUGE; |
| else if (!is_hr_log_data(*buf, len)) |
| new_trace_event->trace_event.event_sub_id = |
| APP_TO_HOST_EVENT_SUBID_HR_LOG; |
| else if (!is_custom_flash_data(*buf, len)) |
| new_trace_event->trace_event.event_sub_id = |
| APP_TO_HOST_EVENT_SUBID_FLASH; |
| else if (p_SensorAppEventHeader->msgId == |
| SENSOR_APP_MSG_ID_CAL_RESULT) |
| new_trace_event->trace_event.event_sub_id = |
| APP_TO_HOST_EVENT_SUBID_CALIBRATE; |
| else if (p_SensorAppEventHeader->msgId == |
| SENSOR_APP_MSG_ID_TEST_RESULT) |
| new_trace_event->trace_event.event_sub_id = |
| APP_TO_HOST_EVENT_SUBID_CALIBRATE; |
| else |
| new_trace_event->trace_event.event_sub_id = |
| APP_TO_HOST_EVENT_SUBID_OTHERS; |
| } |
| new_trace_event->trace_event.event_name = |
| nanohub_get_event_name_from_id( |
| new_trace_event->trace_event.event_id, |
| new_trace_event->trace_event.event_sub_id); |
| |
| pr_debug("nanohub: wakeup trace: get new event id 0x%08x, " |
| "event name %s, interrupt %s\n", |
| new_trace_event->trace_event.event_id, |
| new_trace_event->trace_event.event_name, |
| interrupt_name[ |
| new_trace_event->trace_event.interrupt%3]); |
| |
| list_for_each_entry(tmp, &(trace_head->event_list), event_list) { |
| is_same_type = nanohub_wakeup_trace_event_same( |
| new_trace_event, tmp); |
| if (is_same_type) { |
| tmp->trace_event.event_count++; |
| pr_debug("nanohub: wakeup trace: stored.\n"); |
| break; |
| } |
| } |
| if (!is_same_type) { |
| new_trace_event->trace_event.event_count++; |
| list_add_tail(&(new_trace_event->event_list), |
| &(trace_head->event_list)); |
| pr_debug("nanohub: wakeup trace: add to tail.\n"); |
| } else |
| kzfree(new_trace_event); |
| } |
| |
| static void nanohub_wakeup_trace_dump_list( |
| struct wakeup_trace_listnode *trace_head, |
| int call_reason) |
| { |
| struct list_head *pos, *q; |
| struct wakeup_trace_listnode *tmp; |
| struct nanohub_data *data = |
| container_of(trace_head, struct nanohub_data, |
| wakeup_trace); |
| static const char * const dump_reason_name[] = { |
| "queue empty", |
| "dump data", |
| "ap no buffer", |
| "clear irqs"}; |
| |
| pr_info( |
| "nanohub: wakeup trace: dump_reason: %s," |
| "irq_nums: irq1 %u, irq2 %u, irq3 %u\n", |
| dump_reason_name[call_reason%4], |
| data->wakeup_trace_irqs.nums_irq1, |
| data->wakeup_trace_irqs.nums_irq2, |
| data->wakeup_trace_irqs.nums_irq3); |
| |
| if (list_empty(&(trace_head->event_list))) |
| return; |
| |
| list_for_each_safe(pos, q, &(trace_head->event_list)) { |
| tmp = list_entry(pos, struct wakeup_trace_listnode, event_list); |
| pr_info("nanohub: wakeup trace: event id 0x%08x, " |
| "event name %s, interrupt %s, event_cnt %d\n", |
| tmp->trace_event.event_id, tmp->trace_event.event_name, |
| interrupt_name[tmp->trace_event.interrupt%3], |
| tmp->trace_event.event_count); |
| list_del(pos); |
| kzfree(tmp); |
| } |
| } |
| #endif |
| |
| static void nanohub_process_buffer(struct nanohub_data *data, |
| struct nanohub_buf **buf, |
| int ret) |
| { |
| uint32_t event_id; |
| uint8_t interrupt; |
| bool wakeup = false; |
| struct nanohub_io *io = &data->io[ID_NANOHUB_SENSOR]; |
| |
| data->kthread_err_cnt = 0; |
| |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| if (NANOHUB_WAKEUP_TRACE_ON == |
| atomic_read(&data->st_wakeup_trace)) |
| nanohub_wakeup_trace_add_to_list( |
| &data->wakeup_trace, buf, ret); |
| #endif |
| |
| if (ret < 4 || nanohub_os_log((*buf)->buffer, ret)) { |
| release_wakeup(data); |
| return; |
| } |
| |
| (*buf)->length = ret; |
| |
| event_id = le32_to_cpu((((uint32_t *)(*buf)->buffer)[0]) & 0x7FFFFFFF); |
| if (ret >= sizeof(uint32_t) + sizeof(uint64_t) + sizeof(uint32_t) && |
| event_id > FIRST_SENSOR_EVENTID && |
| event_id <= LAST_SENSOR_EVENTID) { |
| interrupt = (*buf)->buffer[sizeof(uint32_t) + |
| sizeof(uint64_t) + 3]; |
| if (interrupt == WAKEUP_INTERRUPT) |
| wakeup = true; |
| } |
| if (event_id == APP_TO_HOST_EVENTID) { |
| wakeup = true; |
| if (!is_fuel_gauge_data(*buf, ret)) { |
| handle_fuelgauge_data(*buf, ret); |
| release_wakeup(data); |
| return; |
| } else if (!is_hr_log_data(*buf, ret)) |
| io = &data->io[ID_NANOHUB_HR_LOG]; |
| else if (!is_custom_flash_data(*buf, ret)) |
| io = &data->io[ID_NANOHUB_CUSTOM_FLASH]; |
| else |
| io = &data->io[ID_NANOHUB_COMMS]; |
| } |
| |
| nanohub_io_put_buf(io, *buf); |
| |
| *buf = NULL; |
| /* (for wakeup interrupts): hold a wake lock for 250ms so the sensor hal |
| * has time to grab its own wake lock */ |
| if (wakeup) |
| wake_lock_timeout(&data->wakelock_read, msecs_to_jiffies(250)); |
| release_wakeup(data); |
| } |
| |
| 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 }; |
| struct device *sensor_dev = data->io[ID_NANOHUB_SENSOR].dev; |
| static const struct sched_param param = { |
| .sched_priority = (MAX_USER_RT_PRIO/2)-1, |
| }; |
| |
| data->kthread_err_cnt = 0; |
| sched_setscheduler(current, SCHED_FIFO, ¶m); |
| nanohub_set_state(data, ST_IDLE); |
| |
| while (!kthread_should_stop()) { |
| switch (nanohub_get_state(data)) { |
| 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(sensor_dev, |
| "kthread: hard reset due to consistent error\n"); |
| ret = nanohub_hw_reset(data); |
| if (ret) { |
| dev_info(sensor_dev, |
| "%s: failed to reset nanohub: ret=%d\n", |
| __func__, ret); |
| } |
| if (DOWNLOAD_BL_SUCCESS == atomic_read(&data->download_bl_status)) |
| atomic_set(&data->download_bl_status, |
| DOWNLOAD_BL_FAILED); |
| } |
| msleep_interruptible(WAKEUP_TIMEOUT_MS); |
| nanohub_set_state(data, ST_RUNNING); |
| break; |
| case ST_RUNNING: |
| break; |
| } |
| atomic_set(&data->kthread_run, 0); |
| 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(sensor_dev, |
| "%s: request_wakeup_timeout: ret=%d\n", |
| __func__, ret); |
| continue; |
| } |
| |
| ret = nanohub_comms_rx_retrans_boottime( |
| data, CMD_COMMS_READ, buf->buffer, |
| sizeof(buf->buffer), 10, 10); |
| |
| if (ret > 0) { |
| nanohub_process_buffer(data, &buf, ret); |
| if (!nanohub_irq1_fired(data) && |
| !nanohub_irq2_fired(data)) { |
| nanohub_set_state(data, ST_IDLE); |
| |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| if (NANOHUB_WAKEUP_TRACE_ON == |
| atomic_read(&data->st_wakeup_trace)) { |
| atomic_set(&data->st_wakeup_trace, |
| NANOHUB_WAKEUP_TRACE_OFF); |
| nanohub_wakeup_trace_dump_list( |
| &data->wakeup_trace, |
| DUMP_TRACE_REASON_DUMP_QUEUE); |
| } |
| #endif |
| 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); |
| |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| if (NANOHUB_WAKEUP_TRACE_ON == |
| atomic_read(&data->st_wakeup_trace)) { |
| |
| atomic_set(&data->st_wakeup_trace, |
| NANOHUB_WAKEUP_TRACE_OFF); |
| nanohub_wakeup_trace_dump_list( |
| &data->wakeup_trace, |
| DUMP_TRACE_REASON_QUEUE_EMPTY); |
| } |
| #endif |
| 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(sensor_dev, |
| "%s: kthread_err_cnt=%d, ret = %d\n", |
| __func__, |
| data->kthread_err_cnt, ret); |
| nanohub_set_state(data, ST_ERROR); |
| continue; |
| } |
| } |
| } else { |
| if (!nanohub_irq1_fired(data) && |
| !nanohub_irq2_fired(data)) { |
| nanohub_set_state(data, ST_IDLE); |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| if (NANOHUB_WAKEUP_TRACE_ON == |
| atomic_read(&data->st_wakeup_trace)) { |
| |
| atomic_set(&data->st_wakeup_trace, |
| NANOHUB_WAKEUP_TRACE_OFF); |
| nanohub_wakeup_trace_dump_list( |
| &data->wakeup_trace, |
| DUMP_TRACE_REASON_NO_BUFFER); |
| } |
| #endif |
| continue; |
| } |
| /* 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, |
| (uint8_t *) |
| clear_interrupts, |
| sizeof(clear_interrupts), |
| (uint8_t *) data-> |
| interrupts, |
| sizeof(data->interrupts), |
| false, 10, 0); |
| release_wakeup(data); |
| nanohub_set_state(data, ST_IDLE); |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| if (NANOHUB_WAKEUP_TRACE_ON == |
| atomic_read(&data->st_wakeup_trace)) { |
| |
| atomic_set(&data->st_wakeup_trace, |
| NANOHUB_WAKEUP_TRACE_OFF); |
| nanohub_wakeup_trace_dump_list( |
| &data->wakeup_trace, |
| DUMP_TRACE_REASON_CLEAR_IRQS); |
| } |
| #endif |
| } |
| } |
| |
| 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; |
| const uint32_t *tmp; |
| struct property *prop; |
| uint32_t u, i; |
| int ret; |
| |
| 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); |
| |
| pdata->irq3_gpio = of_get_named_gpio(dt, "sensorhub,irq3-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->nreset_polarity = |
| of_property_read_bool(dt, "sensorhub,nreset_active_high"); |
| if (ret) { |
| pdata->nreset_polarity = NRESET_ACTIVE_HIGH; |
| pr_info("nanohub: nreset_active_high, %d\n", |
| pdata->nreset_polarity); |
| } else { |
| pdata->nreset_polarity = NRESET_ACTIVE_LOW; |
| pr_info("nanohub: nreset_active_low, %d\n", |
| pdata->nreset_polarity); |
| } |
| |
| /* optional (stm32f bootloader) */ |
| pdata->boot0_gpio = of_get_named_gpio(dt, "sensorhub,boot0-gpio", 0); |
| |
| /* optional (stm32f mode) */ |
| pdata->int_gpio = |
| of_get_named_gpio(dt, "sensorhub,hub-flag-int-gpio", 0); |
| pdata->mode1_gpio = |
| of_get_named_gpio(dt, "sensorhub,mode-flag1-gpio", 0); |
| pdata->mode2_gpio = |
| of_get_named_gpio(dt, "sensorhub,mode-flag2-gpio", 0); |
| pdata->mode3_gpio = |
| of_get_named_gpio(dt, "sensorhub,mode-flag3-gpio", 0); |
| pdata->mode4_gpio = |
| of_get_named_gpio(dt, "sensorhub,mode-flag4-gpio", 0); |
| /* optional (spi) */ |
| pdata->spi_cs_gpio = |
| of_get_named_gpio(dt, "sensorhub,spi-cs-gpio", 0); |
| |
| /* custom flash memory define */ |
| of_property_read_u32(dt, "sensorhub,custom-flash-addr", |
| &pdata->custom_flash_addr); |
| of_property_read_u32(dt, "sensorhub,custom-flash-len", |
| &pdata->custom_flash_len); |
| |
| /* 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++; |
| } |
| } |
| |
| return pdata; |
| |
| no_mem_shared: |
| devm_kfree(dev, pdata->flash_banks); |
| no_mem: |
| ret = -ENOMEM; |
| free_pdata: |
| devm_kfree(dev, pdata); |
| return ERR_PTR(ret); |
| } |
| #else |
| static struct nanohub_platform_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); |
| |
| 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); |
| } |
| |
| ret = request_threaded_irq(data->irq3, NULL, nanohub_irq3, |
| IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
| "nanohub-irq3", data); |
| if (ret < 0) { |
| data->irq3 = 0; |
| WARN(1, "failed to request optional IRQ %d; err=%d", |
| data->irq3, ret); |
| } else { |
| disable_irq(data->irq3); |
| } |
| /* 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->irq3) |
| free_irq(data->irq3, data); |
| if (data->irq2) |
| free_irq(data->irq2, data); |
| if (data->irq1) |
| free_irq(data->irq1, data); |
| if (gpio_is_valid(pdata->irq3_gpio)) |
| gpio_free(pdata->irq3_gpio); |
| if (gpio_is_valid(pdata->irq2_gpio)) |
| gpio_free(pdata->irq2_gpio); |
| gpio_free(pdata->irq1_gpio); |
| if (pdata->nreset_polarity == NRESET_ACTIVE_LOW) |
| gpio_set_value(pdata->nreset_gpio, 0); |
| else |
| gpio_set_value(pdata->nreset_gpio, 1); |
| |
| gpio_free(pdata->nreset_gpio); |
| mcu_wakeup_gpio_set_value(data, 1); |
| gpio_free(pdata->wakeup_gpio); |
| gpio_set_value(pdata->boot0_gpio, 0); |
| gpio_free(pdata->boot0_gpio); |
| |
| gpio_set_value(pdata->int_gpio, 0); |
| gpio_free(pdata->int_gpio); |
| gpio_set_value(pdata->mode1_gpio, 0); |
| gpio_free(pdata->mode1_gpio); |
| gpio_set_value(pdata->mode2_gpio, 0); |
| gpio_free(pdata->mode2_gpio); |
| gpio_set_value(pdata->mode3_gpio, 0); |
| gpio_free(pdata->mode3_gpio); |
| gpio_set_value(pdata->mode4_gpio, 0); |
| gpio_free(pdata->mode4_gpio); |
| } |
| |
| 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; |
| |
| pr_info("nanohub: start to probe nanohub\n"); |
| |
| 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(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; |
| |
| mutex_init(&(data->hub_mode_set_lock)); |
| mutex_init(&(data->nanohub_write_lock)); |
| init_waitqueue_head(&data->kthread_wait); |
| |
| nanohub_io_init(&data->free_pool, data, dev); |
| |
| 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); |
| wake_lock_init(&data->wakelock_read, WAKE_LOCK_SUSPEND, |
| "nanohub_wakelock_read"); |
| |
| atomic_set(&data->lock_mode, LOCK_MODE_NONE); |
| atomic_set(&data->wakeup_cnt, 0); |
| atomic_set(&data->wakeup_lock_cnt, 0); |
| atomic_set(&data->wakeup_acquired, 0); |
| atomic_set(&data->download_bl_status, DOWNLOAD_BL_NOT_START); |
| atomic_set(&data->hub_mode_ap_active, GPIO_CMD_NORMAL); |
| atomic_set(&data->hub_mode_ap_pwr_down, GPIO_CMD_POWEROFF); |
| atomic_set(&data->lcd_mutex, LCD_MUTEX_OFF); |
| init_waitqueue_head(&data->wakeup_wait); |
| |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| INIT_LIST_HEAD(&data->wakeup_trace.event_list); |
| #endif |
| |
| 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; |
| |
| data->thread = kthread_run(nanohub_kthread, data, "nanohub"); |
| |
| udelay(30); |
| bq27x00_powersupply_init(dev, data); |
| pr_info("nanohub: nanohub_probe end successfully\n"); |
| |
| __nanohub_send_AP_cmd(data, GPIO_CMD_NORMAL); |
| |
| return iio_dev; |
| |
| fail_dev: |
| iio_device_unregister(iio_dev); |
| fail_irq: |
| nanohub_release_gpios_irqs(data); |
| fail_gpio: |
| wake_lock_destroy(&data->wakelock_read); |
| vfree(buf); |
| fail_vma: |
| if (own_iio_dev) |
| iio_device_free(iio_dev); |
| pr_err("nanohub: nanohub_probe fail\n"); |
| |
| return ERR_PTR(ret); |
| } |
| |
| int nanohub_reset(struct nanohub_data *data) |
| { |
| const struct nanohub_platform_data *pdata = data->pdata; |
| |
| if (pdata->nreset_polarity == NRESET_ACTIVE_LOW) { |
| gpio_set_value(pdata->nreset_gpio, 1); |
| } else { |
| gpio_set_value(pdata->nreset_gpio, 1); |
| usleep_range(30, 40); |
| gpio_set_value(pdata->nreset_gpio, 0); |
| } |
| usleep_range(650000, 700000); |
| enable_irq(data->irq1); |
| if (data->irq2) |
| enable_irq(data->irq2); |
| else |
| nanohub_unmask_interrupt(data, 2); |
| |
| if (data->irq3) |
| enable_irq(data->irq3); |
| |
| __nanohub_send_AP_cmd(data, GPIO_CMD_RESEND); |
| |
| return 0; |
| } |
| |
| int nanohub_remove(struct iio_dev *iio_dev) |
| { |
| struct nanohub_data *data = iio_priv(iio_dev); |
| |
| bq27x00_powersupply_unregister(); |
| |
| nanohub_notify_thread(data); |
| kthread_stop(data->thread); |
| |
| __nanohub_send_AP_cmd(data, GPIO_CMD_POWEROFF); |
| |
| nanohub_destroy_devices(data); |
| iio_device_unregister(iio_dev); |
| nanohub_release_gpios_irqs(data); |
| wake_lock_destroy(&data->wakelock_read); |
| vfree(data->vbuf); |
| iio_device_free(iio_dev); |
| |
| return 0; |
| } |
| |
| int nanohub_shutdown(struct iio_dev *iio_dev) |
| { |
| struct nanohub_data *data = iio_priv(iio_dev); |
| if (GPIO_CMD_BAND != atomic_read(&data->hub_mode_ap_pwr_down)) |
| __nanohub_set_mode_pin(data, GPIO_CMD_POWEROFF); |
| else |
| __nanohub_set_mode_pin(data, GPIO_CMD_BAND); |
| return 0; |
| } |
| |
| int nanohub_suspend(struct iio_dev *iio_dev) |
| { |
| struct nanohub_data *data = iio_priv(iio_dev); |
| 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, "nanohub: %s: cnt=%d\n", |
| __func__, cnt); |
| enable_irq_wake(data->irq1); |
| __nanohub_send_AP_cmd(data, GPIO_CMD_SUSPEND); |
| |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| atomic_set(&data->suspend_status, |
| NANOHUB_SUSPEND_ENTRY); |
| atomic_set(&data->st_wakeup_trace, |
| NANOHUB_WAKEUP_TRACE_ON); |
| memset(&data->wakeup_trace_irqs, 0, |
| sizeof(struct irq_nums_during_wakeup)); |
| #endif |
| return 0; |
| } |
| ret = -EBUSY; |
| dev_info(&iio_dev->dev, |
| "nanohub: %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, "nanohub: %s: could not take wakeup lock\n", |
| __func__); |
| } |
| |
| return ret; |
| } |
| |
| int nanohub_resume(struct iio_dev *iio_dev) |
| { |
| struct nanohub_data *data = iio_priv(iio_dev); |
| |
| __nanohub_send_AP_cmd(data, GPIO_CMD_RESUME); |
| |
| #if (NANOHUB_WAKEUP_TRACE_ENABLE) |
| atomic_set(&data->suspend_status, NANOHUB_SUSPEND_EXIT); |
| #endif |
| |
| disable_irq_wake(data->irq1); |
| 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_I2C |
| if (ret == 0) |
| ret = nanohub_i2c_init(); |
| #endif |
| #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_I2C |
| nanohub_i2c_cleanup(); |
| #endif |
| #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"); |