| /* |
| * COPYRIGHT (C) 2015 PNI SENSOR CORPORATION |
| * |
| * LICENSED UNDER THE APACHE LICENSE, VERSION 2.0 (THE "LICENSE"); |
| * YOU MAY NOT USE THIS FILE EXCEPT IN COMPLIANCE WITH THE LICENSE. |
| * YOU MAY OBTAIN A COPY OF THE LICENSE AT |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * THIS SOFTWARE IS PROVIDED BY PNI SENSOR CORPORATION "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL PNI SENSOR CORPORATION BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/err.h> |
| #include <linux/i2c.h> |
| #include <linux/workqueue.h> |
| #include <linux/wakelock.h> |
| #include <linux/notifier.h> |
| #include <linux/suspend.h> |
| #include <linux/interrupt.h> |
| #include <linux/firmware.h> |
| #include <linux/of_gpio.h> |
| #include <linux/delay.h> |
| #include <linux/time.h> |
| #include <linux/wait.h> |
| #include <linux/iio/iio.h> |
| #include <linux/iio/buffer.h> |
| #include <linux/iio/kfifo_buf.h> |
| |
| #include <linux/sentral-iio.h> |
| |
| static int sentral_fifo_flush(struct sentral_device *sentral, u8 sensor_id); |
| |
| // I2C |
| static int sentral_read_byte(struct sentral_device *sentral, u8 reg) |
| { |
| int rc; |
| |
| LOGD(&sentral->client->dev, "read byte: reg: 0x%02X\n", reg); |
| |
| rc = i2c_smbus_read_byte_data(sentral->client, reg); |
| return rc; |
| } |
| |
| static int sentral_write_byte(struct sentral_device *sentral, u8 reg, u8 value) |
| { |
| int rc; |
| |
| LOGD(&sentral->client->dev, "write byte: reg: 0x%02X, value: 0x%02X\n", |
| reg, value); |
| |
| rc = i2c_smbus_write_byte_data(sentral->client, reg, value); |
| return rc; |
| } |
| |
| static int sentral_write_block(struct sentral_device *sentral, u8 reg, |
| void *buffer, size_t count) |
| { |
| u8 buf[1 + count]; |
| struct i2c_msg msg[] = { |
| { |
| .addr = sentral->client->addr, |
| .flags = 0, |
| .len = sizeof(buf), |
| .buf = buf, |
| }, |
| }; |
| int rc; |
| |
| if (!count) |
| return count; |
| |
| buf[0] = reg; |
| memcpy(&buf[1], buffer, count); |
| |
| rc = i2c_transfer(sentral->client->adapter, msg, 1); |
| |
| if (rc != 1) { |
| LOGE(&sentral->client->dev, "error (%d) writing data\n", rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int sentral_read_block(struct sentral_device *sentral, u8 reg, |
| void *buffer, size_t count) |
| { |
| struct i2c_msg msg[] = { |
| { |
| .addr = sentral->client->addr, |
| .flags = 0, |
| .len = 1, |
| .buf = ®, |
| }, |
| { |
| .addr = sentral->client->addr, |
| .flags = I2C_M_RD, |
| .len = count, |
| .buf = buffer, |
| }, |
| }; |
| int rc; |
| |
| if (!count) |
| return count; |
| |
| rc = i2c_transfer(sentral->client->adapter, msg, 2); |
| |
| if (rc != 2) { |
| LOGE(&sentral->client->dev, "error (%d) reading data\n", rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| // misc |
| |
| static u64 sentral_get_boottime_ns(void) |
| { |
| struct timespec t; |
| t.tv_sec = t.tv_nsec = 0; |
| get_monotonic_boottime(&t); |
| return (u64)(t.tv_sec) * NSEC_PER_SEC + t.tv_nsec; |
| } |
| |
| static int sentral_parameter_read(struct sentral_device *sentral, |
| u8 page_number, u8 param_number, void *param, size_t size) |
| { |
| int rc; |
| int i; |
| |
| #ifdef SEN_DBG_PIO |
| u8 dbuf[size * 5 + 1]; |
| size_t dcount = 0; |
| u8 *dparam = (u8 *)param; |
| #endif /* SEN_DBG_PIO */ |
| |
| if (size > PARAM_READ_SIZE_MAX) |
| return -EINVAL; |
| |
| #ifdef SEN_DBG_PIO |
| LOGD(&sentral->client->dev, "[PIO RD] page: %u, number: %u, count: %zu\n", |
| page_number, param_number, size); |
| #endif /* SEN_DBG_PIO */ |
| |
| if (size < PARAM_READ_SIZE_MAX) |
| page_number = (size << 4) | (page_number & 0x0F); |
| |
| mutex_lock(&sentral->lock_special); |
| |
| // select page |
| rc = sentral_write_byte(sentral, SR_PARAM_PAGE, page_number); |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "[PIO RD] error (%d) selecting parameter page: %u\n", rc, |
| page_number); |
| |
| goto exit_error_page; |
| } |
| |
| // select param number |
| rc = sentral_write_byte(sentral, SR_PARAM_REQ, param_number); |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "[PIO RD] error (%d) selecting parameter number: %u\n", rc, |
| param_number); |
| |
| goto exit_error_param; |
| } |
| |
| // wait for ack |
| for (i = 0; i < PARAM_MAX_RETRY; i++) { |
| usleep_range(8000, 10000); |
| rc = sentral_read_byte(sentral, SR_PARAM_ACK); |
| if (rc < 0) { |
| LOGE(&sentral->client->dev, |
| "[PIO RD] error (%d) reading parameter ack\n", rc); |
| |
| goto exit; |
| } |
| |
| if (rc == param_number) |
| goto acked; |
| |
| #ifdef SEN_DBG_PIO |
| LOGD(&sentral->client->dev, |
| "[PIO RD] ack: 0x%02X, expected: 0x%02X\n", rc, param_number); |
| #endif /* SEN_DBG_PIO */ |
| } |
| LOGE(&sentral->client->dev, "[PIO RD] parameter ack retries (%d) exhausted\n", |
| PARAM_MAX_RETRY); |
| |
| rc = -EIO; |
| goto exit; |
| |
| acked: |
| // read values |
| rc = sentral_read_block(sentral, SR_PARAM_SAVE, param, size); |
| if (rc < 0) { |
| LOGE(&sentral->client->dev, |
| "[PIO RD] error (%d) reading parameter data\n", rc); |
| goto exit; |
| } |
| rc = 0; |
| #ifdef SEN_DBG_PIO |
| for (i = 0; i < size; i++) { |
| dcount += scnprintf(dbuf + dcount, PAGE_SIZE - dcount, "0x%02X ", |
| *(dparam + i)); |
| } |
| LOGD(&sentral->client->dev, "[PIO RD] bytes: %s\n", dbuf); |
| #endif /* SEN_DBG_PIO */ |
| exit: |
| (void)sentral_write_byte(sentral, SR_PARAM_PAGE, 0); |
| exit_error_param: |
| (void)sentral_write_byte(sentral, SR_PARAM_REQ, 0); |
| exit_error_page: |
| mutex_unlock(&sentral->lock_special); |
| return rc; |
| } |
| |
| static int sentral_parameter_write(struct sentral_device *sentral, |
| u8 page_number, u8 param_number, void *param, size_t size) |
| { |
| int rc; |
| int i; |
| |
| #ifdef SEN_DBG_PIO |
| u8 dbuf[size * 5 + 1]; |
| size_t dcount = 0; |
| u8 *dparam = (u8 *)param; |
| #endif /* SEN_DBG_PIO */ |
| |
| if (size > PARAM_WRITE_SIZE_MAX) |
| return -EINVAL; |
| |
| #ifdef SEN_DBG_PIO |
| LOGD(&sentral->client->dev, |
| "[PIO WR] page: %u, number: %u, count: %zu\n", |
| page_number, param_number, size); |
| |
| for (i = 0; i < size; i++) { |
| dcount += scnprintf(dbuf + dcount, PAGE_SIZE - dcount, "0x%02X ", |
| *(dparam + i)); |
| } |
| LOGD(&sentral->client->dev, "[PIO WR] bytes: %s\n", dbuf); |
| #endif /* SEN_DBG_PIO */ |
| |
| if (size < PARAM_WRITE_SIZE_MAX) |
| page_number = (size << 4) | (page_number & 0x0F); |
| |
| mutex_lock(&sentral->lock_special); |
| |
| // select page |
| rc = sentral_write_byte(sentral, SR_PARAM_PAGE, page_number); |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "[PIO WR] error (%d) selecting parameter page: %u\n", rc, |
| page_number); |
| |
| goto exit_error_page; |
| } |
| |
| // write values |
| rc = sentral_write_block(sentral, SR_PARAM_LOAD, param, size); |
| if (rc < 0) { |
| LOGE(&sentral->client->dev, |
| "[PIO WR] error (%d) writing parameter data\n", rc); |
| goto exit_error_page; |
| } |
| |
| // select param number |
| param_number |= 0x80; |
| rc = sentral_write_byte(sentral, SR_PARAM_REQ, param_number); |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "[PIO WR] error (%d) selecting parameter number: %u\n", rc, |
| param_number); |
| |
| goto exit_error_param; |
| } |
| |
| LOGD(&sentral->client->dev, "[PIO WR] Page: 0x%02X, Param: 0x%02X\n", |
| page_number, param_number); |
| |
| // wait for ack |
| for (i = 0; i < PARAM_MAX_RETRY; i++) { |
| usleep_range(8000, 10000); |
| rc = sentral_read_byte(sentral, SR_PARAM_ACK); |
| |
| #ifdef SEN_DBG_PIO |
| LOGD(&sentral->client->dev, |
| "[PIO WR] ack: 0x%02X, expected: 0x%02X\n", rc, param_number); |
| #endif /* SEN_DBG_PIO */ |
| |
| if (rc < 0) { |
| LOGE(&sentral->client->dev, |
| "[PIO WR] error (%d) reading parameter ack\n", rc); |
| |
| goto exit; |
| } |
| |
| if (rc == param_number) |
| goto acked; |
| |
| } |
| LOGE(&sentral->client->dev, |
| "[PIO WR] parameter ack retries (%d) exhausted\n", PARAM_MAX_RETRY); |
| |
| rc = -EIO; |
| goto exit; |
| |
| acked: |
| rc = 0; |
| |
| exit: |
| (void)sentral_write_byte(sentral, SR_PARAM_PAGE, 0); |
| exit_error_param: |
| (void)sentral_write_byte(sentral, SR_PARAM_REQ, 0); |
| exit_error_page: |
| mutex_unlock(&sentral->lock_special); |
| return rc; |
| } |
| |
| static int sentral_stime_current_get(struct sentral_device *sentral, u32 *ts) |
| { |
| struct sentral_param_timestamp stime = {{ 0 }}; |
| int rc; |
| |
| rc = sentral_parameter_read(sentral, SPP_SYS, SP_SYS_HOST_IRQ_TS, |
| (void *)&stime, sizeof(stime)); |
| if (rc) |
| return rc; |
| |
| *ts = stime.ts.current_stime; |
| |
| return 0; |
| } |
| |
| // Sensors |
| |
| static int sentral_sensor_config_read(struct sentral_device *sentral, u8 id, |
| struct sentral_param_sensor_config *config) |
| { |
| int rc; |
| |
| rc = sentral_parameter_read(sentral, SPP_SENSORS, |
| id + PARAM_SENSORS_ACTUAL_OFFSET, |
| (void *)config, sizeof(struct sentral_param_sensor_config)); |
| |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "error (%d) reading sensor parameter for sensor id: %u\n", |
| rc, id); |
| |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int sentral_sensor_config_write(struct sentral_device *sentral, u8 id, |
| struct sentral_param_sensor_config *config) |
| { |
| int rc; |
| |
| rc = sentral_parameter_write(sentral, SPP_SENSORS, |
| id + PARAM_SENSORS_ACTUAL_OFFSET, |
| (void *)config, sizeof(struct sentral_param_sensor_config)); |
| |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "error (%d) writing sensor parameter for sensor id: %u\n", |
| rc, id); |
| |
| return rc; |
| } |
| |
| // update current sensor config |
| memcpy(&sentral->sensor_config[id], config, |
| sizeof(struct sentral_param_sensor_config)); |
| |
| return 0; |
| } |
| |
| static int sentral_sensor_config_restore(struct sentral_device *sentral) |
| { |
| int i; |
| int rc = 0; |
| |
| if (sentral->enabled_mask) { |
| for (i = 0; i < SST_MAX; i++) { |
| if (!test_bit(i, &sentral->enabled_mask)) |
| continue; |
| |
| LOGI(&sentral->client->dev, "restoring state for sensor id: %d\n", |
| i); |
| |
| rc = sentral_sensor_config_write(sentral, i, |
| &sentral->sensor_config[i]); |
| |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "error (%d) restoring sensor config for sensor id: %d\n", |
| rc, i); |
| return rc; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int sentral_sensor_info_read(struct sentral_device *sentral, u8 id, |
| struct sentral_param_sensor_info *info) |
| { |
| int rc; |
| |
| rc = sentral_parameter_read(sentral, SPP_SENSORS, id, (void *)info, |
| sizeof(struct sentral_param_sensor_info)); |
| |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "error (%d) reading sensor parameter for sensor id: %u\n", |
| rc, id); |
| |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int sentral_meta_event_ctrl_set(struct sentral_device *sentral, |
| u8 id, bool evt_enable, bool int_enable) |
| { |
| u64 meta_event_ctrl = 0; |
| int bit_evt, bit_int; |
| int rc; |
| |
| if ((id < SEN_META_FIRST) || (id >= SEN_META_MAX)) |
| return -EINVAL; |
| |
| rc = sentral_parameter_read(sentral, SPP_SYS, SP_SYS_META_EVENT_CONTROL, |
| (void *)&meta_event_ctrl, sizeof(meta_event_ctrl)); |
| if (rc) |
| return rc; |
| |
| bit_evt = 1 << (((id - 1) * 2) + 1); |
| bit_int = 1 << (((id - 1) * 2)); |
| |
| meta_event_ctrl &= ~(bit_evt); |
| if (evt_enable) |
| meta_event_ctrl |= bit_evt; |
| |
| meta_event_ctrl &= ~(bit_int); |
| if (int_enable) |
| meta_event_ctrl |= bit_int; |
| |
| return sentral_parameter_write(sentral, SPP_SYS, SP_SYS_META_EVENT_CONTROL, |
| (void *)&meta_event_ctrl, sizeof(meta_event_ctrl)); |
| } |
| |
| static int sentral_sensor_id_is_valid(u8 id) |
| { |
| return ((id >= SST_FIRST) && (id < SST_MAX)); |
| } |
| |
| static int sentral_inactivity_timeout_get(struct sentral_device *sentral, |
| u16 *timeout_s) |
| { |
| return sentral_parameter_read(sentral, SPP_ASUS, |
| SP_ASUS_INACTIVITY_TIMEOUT, (void *)timeout_s, sizeof(timeout_s)); |
| } |
| |
| static int sentral_inactivity_timeout_set(struct sentral_device *sentral, |
| u16 timeout_s) |
| { |
| LOGI(&sentral->client->dev, "setting intactivity timeout to: %u seconds\n", |
| timeout_s); |
| |
| return sentral_parameter_write(sentral, SPP_ASUS, |
| SP_ASUS_INACTIVITY_TIMEOUT, (void *)&timeout_s, sizeof(timeout_s)); |
| } |
| |
| static int sentral_rate_to_fitness_id(struct sentral_device *sentral, |
| u16 rate) |
| { |
| int fitness_id = 0; |
| int i; |
| |
| for (i = 1; i < ARRAY_SIZE(sentral_fitness_id_rates); i++) { |
| if (rate >= sentral_fitness_id_rates[i]) |
| fitness_id = i; |
| } |
| |
| return fitness_id; |
| } |
| |
| static int sentral_coach_fitness_id_get(struct sentral_device *sentral, |
| u8 *fitness_id) |
| { |
| int rc = sentral_read_byte(sentral, SR_FITNESS_ID); |
| if (rc < 0) |
| return rc; |
| |
| *fitness_id = (u8)rc; |
| return 0; |
| } |
| |
| static int sentral_coach_fitness_id_set(struct sentral_device *sentral, |
| u8 fitness_id) |
| { |
| LOGI(&sentral->client->dev, "setting coach fitness id to : %u\n", |
| fitness_id); |
| |
| return sentral_write_byte(sentral, SR_FITNESS_ID, fitness_id); |
| } |
| |
| static int sentral_error_get(struct sentral_device *sentral) |
| { |
| return sentral_read_byte(sentral, SR_ERROR); |
| } |
| |
| static int sentral_crc_get(struct sentral_device *sentral, u32 *crc) |
| { |
| return sentral_read_block(sentral, SR_CRC_HOST, (void *)crc, sizeof(*crc)); |
| } |
| |
| static int sentral_sensor_batch_set(struct sentral_device *sentral, u8 id, |
| u16 rate, u16 timeout_ms) |
| { |
| int rc; |
| struct sentral_param_sensor_config config = { |
| .sample_rate = rate, |
| .max_report_latency = timeout_ms, |
| .change_sensitivity = 0, |
| .dynamic_range = 0, |
| }; |
| |
| LOGI(&sentral->client->dev, "batch set id: %u, rate: %u, timeout: %u\n", id, |
| rate, timeout_ms); |
| |
| if (!sentral_sensor_id_is_valid(id)) |
| return -EINVAL; |
| |
| clear_bit(id, &sentral->sensor_warmup_mask); |
| if (!config.sample_rate) |
| set_bit(id, &sentral->sensor_warmup_mask); |
| |
| // inactivity sensor uses rate param to set timeout value |
| if (id == SST_INACTIVITY_ALARM) { |
| rc = sentral_inactivity_timeout_set(sentral, rate); |
| if (rc) |
| return rc; |
| |
| // change rate param to a real rate |
| if (rate) |
| config.sample_rate = SENTRAL_INACTIVITY_RATE_HZ; |
| } |
| |
| // coach sensor uses rate param to set fitness id |
| if (id == SST_COACH) { |
| int fitness_id = 0; |
| |
| rc = sentral_rate_to_fitness_id(sentral, rate); |
| if (rc < 0) |
| return rc; |
| |
| fitness_id = rc; |
| rc = sentral_coach_fitness_id_set(sentral, fitness_id); |
| if (rc) |
| return rc; |
| |
| // change rate param to a real rate |
| if (rate) |
| config.sample_rate = SENTRAL_COACH_RATE_HZ; |
| } |
| |
| // update config |
| rc = sentral_sensor_config_write(sentral, id, &config); |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "error (%d) writing sensor config for sensor id: %u\n", |
| rc, id); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int sentral_sensor_enable_set(struct sentral_device *sentral, u8 id, |
| bool enable) |
| { |
| LOGD(&sentral->client->dev, "enable set id: %u, enable: %u\n", id, enable); |
| |
| if (enable) { |
| set_bit(id, &sentral->enabled_mask); |
| } else { |
| clear_bit(id, &sentral->enabled_mask); |
| return sentral_sensor_batch_set(sentral, id, 0, 0); |
| } |
| |
| return 0; |
| } |
| |
| static int sentral_request_reset(struct sentral_device *sentral) |
| { |
| int rc; |
| |
| LOGI(&sentral->client->dev, "reset request\n"); |
| rc = sentral_write_byte(sentral, SR_RESET_REQ, 1); |
| return rc; |
| } |
| |
| static int sentral_sensor_ref_time_get(struct sentral_device *sentral, |
| struct sentral_sensor_ref_time *ref_time) |
| { |
| int rc; |
| |
| cancel_delayed_work_sync(&sentral->work_ts_ref_reset); |
| |
| ref_time->system_ns = sentral_get_boottime_ns(); |
| |
| rc = sentral_stime_current_get(sentral, &ref_time->hub_stime); |
| if (rc) |
| return rc; |
| |
| queue_delayed_work(sentral->sentral_wq, &sentral->work_ts_ref_reset, |
| (SENTRAL_TS_REF_RESET_WORK_SECS * HZ)); |
| |
| return 0; |
| } |
| |
| static void sentral_wq_wake_flush_complete(struct sentral_device *sentral) |
| { |
| LOGD(&sentral->client->dev, "Flush pending: %s\n", |
| TFSTR(sentral->flush_pending)); |
| |
| sentral->flush_complete = true; |
| |
| if (sentral->flush_pending) |
| wake_up_interruptible(&sentral->wq_flush); |
| |
| sentral->flush_pending = false; |
| } |
| |
| static void sentral_crash_reset(struct sentral_device *sentral) |
| { |
| LOGI(&sentral->client->dev, "[CRASH] Probable crash %u detected, restarting device ...\n", |
| ++sentral->crash_count); |
| |
| // queue reset |
| queue_work(sentral->sentral_wq, &sentral->work_reset); |
| } |
| |
| // IIO |
| |
| static int sentral_iio_buffer_push(struct sentral_device *sentral, |
| u16 sensor_id, void *data, u64 event_ntime, size_t bytes) |
| { |
| u8 buffer[24] = { 0 }; |
| int rc; |
| char dstr[sizeof(buffer) * 5 + 1]; |
| size_t dstr_len = 0; |
| int i; |
| |
| // sensor id 0-1 |
| memcpy(&buffer[0], &sensor_id, sizeof(sensor_id)); |
| |
| // data 2-15 |
| if (bytes > 0) |
| memcpy(&buffer[2], data, bytes); |
| |
| // timestamp 16-23 |
| memcpy(&buffer[16], &event_ntime, sizeof(event_ntime)); |
| |
| // iio data debug |
| for (i = 0, dstr_len = 0; i < sizeof(buffer); i++) { |
| dstr_len += scnprintf(dstr + dstr_len, PAGE_SIZE - dstr_len, |
| " 0x%02X", buffer[i]); |
| } |
| LOGD(&sentral->client->dev, "iio buffer bytes: %s\n", dstr); |
| |
| rc = iio_push_to_buffers(sentral->indio_dev, buffer); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) pushing to IIO buffers", rc); |
| sentral_crash_reset(sentral); |
| } |
| |
| return rc; |
| } |
| |
| static int sentral_iio_buffer_push_std(struct sentral_device *sentral, |
| u8 sensor_id, void *data, u32 event_stime, size_t bytes) |
| { |
| u64 ts = 0; |
| s64 dt_stime = 0; |
| s64 dt_nanos = 0; |
| u64 now = 0; |
| int rc; |
| |
| if (sensor_id != SST_META_EVENT) { |
| if (test_bit(sensor_id, &sentral->sensor_warmup_mask)) { |
| LOGD(&sentral->client->dev, |
| "[IIO] pending enable for sensor id %u, dropping warm-up sample ...\n", |
| sensor_id); |
| return 0; |
| } |
| |
| if (!test_bit(sensor_id, &sentral->enabled_mask)) { |
| LOGD(&sentral->client->dev, |
| "[IIO] dropping sample from disabled sensor: %d\n", sensor_id); |
| return 0; |
| } |
| } |
| |
| if (sensor_id < SST_MAX) { |
| mutex_lock(&sentral->lock_ts); |
| |
| if (test_bit(sensor_id, &sentral->ts_ref_reset_mask)) { |
| rc = sentral_sensor_ref_time_get(sentral, &sentral->ts_sensor_ref[sensor_id]); |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "[TS] error (%d) retrieving sensor reference time\n", rc); |
| mutex_unlock(&sentral->lock_ts); |
| return rc; |
| } |
| LOGD(&sentral->client->dev, |
| "[TS] ref time sync { sensor_id: %u, system_ns: %llu, hub_stime: %u }\n", |
| sensor_id, sentral->ts_sensor_ref[sensor_id].system_ns, |
| sentral->ts_sensor_ref[sensor_id].hub_stime); |
| |
| clear_bit(sensor_id, &sentral->ts_ref_reset_mask); |
| } |
| now = sentral_get_boottime_ns(); |
| |
| dt_stime = (s64)(event_stime) |
| - (s64)(sentral->ts_sensor_ref[sensor_id].hub_stime); |
| |
| if (sentral->ts_msw_offset[sensor_id]) |
| dt_stime += sentral->ts_msw_offset[sensor_id] * 65536; |
| |
| dt_nanos = dt_stime * sentral->stime_scale; |
| ts = sentral->ts_sensor_ref[sensor_id].system_ns + dt_nanos; |
| |
| LOGD(&sentral->client->dev, |
| "[TS] %u,%llu,%llu,%u,%u,%u,%u,%lld,%lld,%lld,%lld\n", |
| sensor_id, now, sentral->ts_sensor_ref[sensor_id].system_ns, |
| sentral->ts_sensor_ref[sensor_id].hub_stime, |
| event_stime, sentral->ts_msw_offset[sensor_id], |
| sentral->stime_scale, dt_stime, dt_nanos, ts, now - ts); |
| |
| mutex_unlock(&sentral->lock_ts); |
| } |
| |
| return sentral_iio_buffer_push(sentral, sensor_id, data, ts, bytes); |
| } |
| |
| static int sentral_iio_buffer_push_wrist_tilt(struct sentral_device *sentral) |
| { |
| LOGI(&sentral->client->dev, "sending wake-up wrist-tilt event\n"); |
| return sentral_iio_buffer_push(sentral, SST_WRIST_TILT_GESTURE, NULL, |
| sentral_get_boottime_ns(), 0); |
| } |
| |
| static int sentral_iio_buffer_push_sig_motion(struct sentral_device *sentral) |
| { |
| LOGI(&sentral->client->dev, "sending wake-up sig-motion event\n"); |
| return sentral_iio_buffer_push(sentral, SST_SIGNIFICANT_MOTION, NULL, |
| sentral_get_boottime_ns(), 0); |
| } |
| |
| // FIFO |
| |
| static int sentral_fifo_ctrl_get(struct sentral_device *sentral, |
| struct sentral_param_fifo_control *fifo_ctrl) |
| { |
| return sentral_parameter_read(sentral, SPP_SYS, SP_SYS_FIFO_CONTROL, |
| (void *)fifo_ctrl, sizeof(struct sentral_param_fifo_control)); |
| } |
| |
| static int sentral_fifo_ctrl_set(struct sentral_device *sentral, |
| struct sentral_param_fifo_control *fifo_ctrl) |
| { |
| return sentral_parameter_write(sentral, SPP_SYS, SP_SYS_FIFO_CONTROL, |
| (void *)fifo_ctrl, sizeof(struct sentral_param_fifo_control)); |
| } |
| |
| static int sentral_fifo_watermark_set(struct sentral_device *sentral, |
| u16 watermark) |
| { |
| struct sentral_param_fifo_control fifo_ctrl = {{ 0 }}; |
| int rc; |
| |
| rc = sentral_fifo_ctrl_get(sentral, &fifo_ctrl); |
| if (rc) |
| return rc; |
| |
| fifo_ctrl.fifo.watermark = watermark; |
| |
| return sentral_fifo_ctrl_set(sentral, &fifo_ctrl); |
| } |
| |
| static int sentral_fifo_watermark_enable(struct sentral_device *sentral, |
| bool enable) |
| { |
| int rc; |
| |
| if (enable) |
| rc = sentral_fifo_watermark_set(sentral, sentral->fifo_watermark); |
| else |
| rc = sentral_fifo_watermark_set(sentral, 0); |
| return rc; |
| } |
| |
| static int sentral_fifo_watermark_autoset(struct sentral_device *sentral) |
| { |
| struct sentral_param_fifo_control fifo_ctrl = {{ 0 }}; |
| int rc; |
| |
| // get FIFO size |
| rc = sentral_fifo_ctrl_get(sentral, &fifo_ctrl); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) getting FIFO control\n", rc); |
| return rc; |
| } |
| |
| // set the watermark below FIFO size |
| sentral->fifo_watermark = 0; |
| if (fifo_ctrl.fifo.size > SENTRAL_FIFO_WATERMARK_BUFFER) { |
| sentral->fifo_watermark = fifo_ctrl.fifo.size |
| - SENTRAL_FIFO_WATERMARK_BUFFER; |
| } |
| |
| rc = sentral_meta_event_ctrl_set(sentral, SEN_META_FIFO_WATERMARK, |
| true, false); |
| if (rc) |
| return rc; |
| |
| if (sentral->fifo_watermark) |
| return sentral_fifo_watermark_enable(sentral, true); |
| |
| return 0; |
| } |
| |
| static int sentral_handle_special_wakeup(struct sentral_device *sentral, |
| struct sentral_wake_src_count curr) |
| { |
| struct sentral_wake_src_count prev = sentral->wake_src_prev; |
| int rc; |
| |
| LOGD(&sentral->client->dev, "curr: %u, prev: %u\n", curr.byte, |
| prev.byte); |
| |
| // wrist tilt |
| if (curr.bits.wrist_tilt != prev.bits.wrist_tilt) { |
| sentral->wake_src_prev.bits.wrist_tilt = curr.bits.wrist_tilt; |
| rc = sentral_iio_buffer_push_wrist_tilt(sentral); |
| if (rc) |
| return rc; |
| } |
| |
| // significant motion |
| if (curr.bits.sig_motion != prev.bits.sig_motion) { |
| sentral->wake_src_prev.bits.sig_motion = curr.bits.sig_motion; |
| rc = sentral_iio_buffer_push_sig_motion(sentral); |
| if (rc) |
| return rc; |
| } |
| |
| sentral->wake_src_prev = curr; |
| return 0; |
| } |
| |
| static int sentral_handle_meta_data(struct sentral_device *sentral, |
| void *buffer) |
| { |
| struct sentral_data_meta *data = (struct sentral_data_meta *)buffer; |
| |
| LOGI(&sentral->client->dev, "Meta Event: %s { 0x%02X, 0x%02X }\n", |
| (data->event_id >= SEN_META_MAX) |
| ? "Unknown" |
| : sentral_meta_event_strings[data->event_id], |
| data->byte_1, data->byte_2); |
| |
| switch (data->event_id) { |
| |
| // push flush complete events |
| case SEN_META_FLUSH_COMPLETE: |
| sentral_iio_buffer_push_std(sentral, SST_META_EVENT, (void *)&data->byte_1, |
| sentral->ts_sensor_stime, sizeof(data->byte_1)); |
| sentral_wq_wake_flush_complete(sentral); |
| set_bit(data->byte_1, &sentral->ts_ref_reset_mask); |
| break; |
| |
| // restore sensors on init |
| case SEN_META_INITIALIZED: |
| (void)sentral_fifo_watermark_autoset(sentral); |
| (void)sentral_sensor_config_restore(sentral); |
| sentral_wq_wake_flush_complete(sentral); |
| sentral->overflow_count = 0; |
| break; |
| |
| // clear enable pending filter |
| case SEN_META_SAMPLE_RATE_CHANGED: |
| if (data->byte_1 < SST_MAX) { |
| sentral->ts_msw_meta_rate_change[data->byte_1] = sentral->ts_msw_last; |
| sentral->ts_msw_offset_reset[data->byte_1] = 1; |
| set_bit(data->byte_1, &sentral->ts_ref_reset_mask); |
| } |
| |
| clear_bit(data->byte_1, &sentral->sensor_warmup_mask); |
| break; |
| |
| // reset hub on too many overflows |
| case SEN_META_FIFO_OVERFLOW: |
| if (++sentral->overflow_count >= SENTRAL_FIFO_OVERFLOW_THRD) { |
| LOGE(&sentral->client->dev, "FIFO overflow exceeds %d times", |
| SENTRAL_FIFO_OVERFLOW_THRD); |
| (void)sentral_crash_reset(sentral); |
| } |
| break; |
| } |
| |
| return sizeof(*data); |
| } |
| |
| static int sentral_handle_ts_msw(struct sentral_device *sentral, void *buffer) |
| { |
| u16 ts = *(u16 *)buffer; |
| |
| mutex_lock(&sentral->lock_ts); |
| |
| sentral->ts_sensor_stime = ts << 16; |
| |
| if (ts < (sentral->ts_msw_last - 1)) { |
| LOGI(&sentral->client->dev, "[TS] MSW wrapped\n"); |
| sentral->ts_ref_reset_mask = ULONG_MAX; |
| } |
| |
| sentral->ts_msw_last = ts; |
| |
| mutex_unlock(&sentral->lock_ts); |
| |
| LOGD(&sentral->client->dev, "[TS] MSW: { ts: %5u 0x%04X }\n", ts, ts); |
| |
| return sizeof(ts); |
| } |
| |
| static int sentral_handle_ts_lsw(struct sentral_device *sentral, void *buffer) |
| { |
| u16 ts = *(u16 *)buffer; |
| |
| mutex_lock(&sentral->lock_ts); |
| |
| sentral->ts_sensor_stime &= 0xFFFF0000; |
| sentral->ts_sensor_stime |= ts; |
| sentral->ts_lsw_last = ts; |
| |
| mutex_unlock(&sentral->lock_ts); |
| |
| LOGD(&sentral->client->dev, "[TS] LSW: { ts: %5u 0x%04X }\n", ts, ts); |
| |
| return sizeof(ts); |
| } |
| |
| static int sentral_handle_debug_data(struct sentral_device *sentral, |
| void *buffer) |
| { |
| struct sentral_data_debug *data = (struct sentral_data_debug *)buffer; |
| char buf[60]; |
| size_t i; |
| size_t count = 0; |
| |
| switch (data->attr.type) { |
| case SEN_DEBUG_STRING: |
| for (i = 0; (i < data->attr.length) && (i < sizeof(buf)); i++) |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%c", |
| data->value[i]); |
| break; |
| |
| case SEN_DEBUG_BINARY: |
| for (i = 0; (i < data->attr.length) && (i < sizeof(buf)); i++) |
| count += scnprintf(buf + count, PAGE_SIZE - count, "0x%02X ", |
| data->value[i]); |
| break; |
| |
| default: |
| break; |
| } |
| dev_info(&sentral->client->dev, "Debug Event: %s\n", buf); |
| |
| return sizeof(*data); |
| } |
| |
| static int sentral_fifo_flush(struct sentral_device *sentral, u8 sensor_id) |
| { |
| bool prev_flush_pending; |
| int rc; |
| |
| LOGI(&sentral->client->dev, "FIFO flush sensor ID: 0x%02X\n", sensor_id); |
| |
| if (sensor_id == SST_SIGNIFICANT_MOTION) |
| return 0; |
| |
| mutex_lock(&sentral->lock_flush); |
| |
| prev_flush_pending = sentral->flush_pending; |
| sentral->flush_pending = true; |
| sentral->flush_complete = false; |
| |
| if (prev_flush_pending) { |
| rc = wait_event_interruptible_timeout(sentral->wq_flush, |
| sentral->flush_complete, |
| msecs_to_jiffies(SENTRAL_WQ_FLUSH_TIMEOUT_MSECS)); |
| |
| if (rc < 0) |
| goto exit; |
| |
| if (rc == 0) { |
| LOGE(&sentral->client->dev, "FIFO flush timeout\n"); |
| rc = -EBUSY; |
| goto exit; |
| } |
| |
| LOGD(&sentral->client->dev, "FIFO flush waited %d ms\n", |
| SENTRAL_WQ_FLUSH_TIMEOUT_MSECS - jiffies_to_msecs(rc)); |
| } |
| |
| rc = sentral_write_byte(sentral, SR_FIFO_FLUSH, sensor_id); |
| exit: |
| mutex_unlock(&sentral->lock_flush); |
| return rc; |
| } |
| |
| static int sentral_fifo_get_bytes_remaining(struct sentral_device *sentral, |
| u16 *bytes) |
| { |
| int rc = sentral_read_block(sentral, SR_FIFO_BYTES, (void *)bytes, |
| sizeof(*bytes)); |
| |
| LOGD(&sentral->client->dev, "FIFO bytes remaining: %u\n", *bytes); |
| |
| return rc; |
| } |
| |
| static int sentral_fifo_parse(struct sentral_device *sentral, u8 *buffer, |
| size_t bytes) |
| { |
| u8 sensor_id; |
| size_t data_size; |
| |
| while (bytes > 0) { |
| // get sensor id |
| sensor_id = *buffer++; |
| bytes--; |
| data_size = 0; |
| |
| switch (sensor_id) { |
| |
| case SST_TIMESTAMP_MSW: |
| data_size = sentral_handle_ts_msw(sentral, buffer); |
| buffer += data_size; |
| bytes -= data_size; |
| continue; |
| |
| case SST_TIMESTAMP_LSW: |
| data_size = sentral_handle_ts_lsw(sentral, buffer); |
| buffer += data_size; |
| bytes -= data_size; |
| continue; |
| |
| case SST_NOP: |
| continue; |
| |
| case SST_STEP_DETECTOR: |
| case SST_TILT_DETECTOR: |
| case SST_WAKE_GESTURE: |
| case SST_GLANCE_GESTURE: |
| case SST_PICK_UP_GESTURE: |
| case SST_INACTIVITY_ALARM: |
| data_size = 0; |
| break; |
| |
| // handled by special wake-up register |
| case SST_SIGNIFICANT_MOTION: |
| case SST_WRIST_TILT_GESTURE: |
| data_size = 0; |
| continue; |
| |
| case SST_HEART_RATE: |
| data_size = 1; |
| break; |
| |
| case SST_LIGHT: |
| case SST_PROXIMITY: |
| case SST_RELATIVE_HUMIDITY: |
| case SST_STEP_COUNTER: |
| case SST_TEMPERATURE: |
| case SST_AMBIENT_TEMPERATURE: |
| case SST_ACTIVITY: |
| data_size = 2; |
| break; |
| |
| case SST_PRESSURE: |
| case SST_COACH: |
| data_size = 3; |
| break; |
| |
| case SST_ACCELEROMETER: |
| case SST_GEOMAGNETIC_FIELD: |
| case SST_ORIENTATION: |
| case SST_GYROSCOPE: |
| case SST_GRAVITY: |
| case SST_LINEAR_ACCELERATION: |
| data_size = 7; |
| break; |
| |
| case SST_ROTATION_VECTOR: |
| case SST_GAME_ROTATION_VECTOR: |
| case SST_GEOMAGNETIC_ROTATION_VECTOR: |
| data_size = 10; |
| break; |
| |
| case SST_ALGO_DATA: |
| data_size = 17; |
| break; |
| |
| case SST_MAGNETIC_FIELD_UNCALIBRATED: |
| case SST_GYROSCOPE_UNCALIBRATED: |
| data_size = 13; |
| break; |
| |
| case SST_META_EVENT: |
| data_size = sentral_handle_meta_data(sentral, buffer); |
| buffer += data_size; |
| bytes -= data_size; |
| continue; |
| |
| case SST_DEBUG: |
| data_size = sentral_handle_debug_data(sentral, buffer); |
| buffer += data_size; |
| bytes -= data_size; |
| continue; |
| |
| default: |
| LOGE(&sentral->client->dev, "invalid sensor type: %u\n", sensor_id); |
| // invalid sensor indicates FIFO corruption so we drop parsing the rest; we need to |
| // wake up any waiting wq in case there was a flush complete event that was dropped |
| (void)sentral_wq_wake_flush_complete(sentral); |
| return -EINVAL; |
| } |
| |
| if ((sensor_id < SST_MAX) |
| && (sentral->ts_msw_offset_reset[sensor_id] == 1)) { |
| if (sentral->ts_msw_meta_rate_change[sensor_id] >= sentral->ts_msw_last) { |
| sentral->ts_msw_offset[sensor_id] |
| = sentral->ts_msw_meta_rate_change[sensor_id] |
| - sentral->ts_msw_last; |
| |
| LOGD(&sentral->client->dev, "msw offset for sensor id %d %u\n", |
| sensor_id, sentral->ts_msw_offset[sensor_id]); |
| |
| sentral->ts_msw_offset_reset[sensor_id] = 0; |
| } |
| } |
| |
| sentral_iio_buffer_push_std(sentral, sensor_id, (void *)buffer, |
| sentral->ts_sensor_stime, data_size); |
| |
| buffer += data_size; |
| bytes -= data_size; |
| |
| } |
| return 0; |
| } |
| |
| static int sentral_fifo_read_block(struct sentral_device *sentral, u8 *buffer, |
| size_t bytes) |
| { |
| int rc; |
| |
| mutex_lock(&sentral->lock_special); |
| rc = sentral_read_block(sentral, SR_FIFO_START, buffer, bytes); |
| mutex_unlock(&sentral->lock_special); |
| |
| return rc; |
| } |
| |
| static int sentral_fifo_read(struct sentral_device *sentral, u8 *buffer) |
| { |
| u16 bytes_remaining = 0; |
| int rc; |
| |
| LOGD(&sentral->client->dev, "%s\n", __func__); |
| |
| // get bytes remaining |
| rc = sentral_fifo_get_bytes_remaining(sentral, &bytes_remaining); |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "error (%d) reading FIFO bytes remaining\n", rc); |
| goto exit; |
| } |
| |
| LOGD(&sentral->client->dev, "FIFO bytes remaining: %u\n", bytes_remaining); |
| |
| // check buffer overflow |
| if (bytes_remaining > DATA_BUFFER_SIZE) { |
| LOGE(&sentral->client->dev, "FIFO read buffer overflow (%u > %u)\n", |
| bytes_remaining, DATA_BUFFER_SIZE); |
| rc = -ENOMEM; |
| goto exit; |
| } |
| |
| // read FIFO |
| rc = sentral_fifo_read_block(sentral, buffer, bytes_remaining); |
| if (rc < 0) { |
| LOGE(&sentral->client->dev, "error (%d) reading FIFO\n", rc); |
| goto exit; |
| } |
| |
| // parse buffer |
| rc = sentral_fifo_parse(sentral, buffer, bytes_remaining); |
| if (rc < 0) { |
| LOGE(&sentral->client->dev, "error (%d) parsing FIFO\n", rc); |
| goto exit; |
| } |
| |
| rc = 0; |
| |
| exit: |
| atomic_set(&sentral->fifo_pending, 0); |
| wake_up_interruptible(&sentral->wq_fifo); |
| return rc; |
| } |
| |
| static void sentral_do_work_fifo_read(struct work_struct *work) |
| { |
| struct sentral_device *sentral = container_of(work, struct sentral_device, |
| work_fifo_read); |
| |
| int rc; |
| |
| LOGD(&sentral->client->dev, "%s\n", __func__); |
| |
| rc = sentral_fifo_read(sentral, (void *)sentral->data_buffer); |
| |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "error (%d) reading FIFO in fifo read workqueue\n", rc); |
| sentral_crash_reset(sentral); |
| return; |
| } |
| queue_delayed_work(sentral->sentral_wq, &sentral->work_watchdog, |
| msecs_to_jiffies(SENTRAL_WATCHDOG_WORK_MSECS)); |
| } |
| |
| static int sentral_set_register_flag(struct sentral_device *sentral, u8 reg, |
| u8 flag, bool enable) |
| { |
| int rc; |
| u8 value; |
| |
| LOGD(&sentral->client->dev, "setting register 0x%02X flag 0x%02X to %u\n", |
| reg, flag, enable); |
| |
| rc = sentral_read_byte(sentral, reg); |
| if (rc < 0) { |
| LOGE(&sentral->client->dev, "error (%d) reading register 0x%02X\n", rc, |
| reg); |
| |
| return rc; |
| } |
| |
| value = rc & ~(flag); |
| if (enable) |
| value |= flag; |
| |
| if (value == rc) |
| return 0; |
| |
| rc = sentral_write_byte(sentral, reg, value); |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "error (%d) setting register 0x%02X to 0x%02X\n", rc, reg, |
| value); |
| |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| // chip control |
| |
| static int sentral_set_chip_control_flag(struct sentral_device *sentral, |
| u8 flag, bool enable) |
| { |
| return sentral_set_register_flag(sentral, SR_CHIP_CONTROL, flag, enable); |
| } |
| |
| static int sentral_set_cpu_run_enable(struct sentral_device *sentral, |
| bool enable) |
| { |
| LOGI(&sentral->client->dev, "setting CPU run to %s\n", ENSTR(enable)); |
| return sentral_set_chip_control_flag(sentral, SEN_CHIP_CTRL_CPU_RUN, |
| enable); |
| } |
| |
| static int sentral_set_host_upload_enable(struct sentral_device *sentral, |
| bool enable) |
| { |
| LOGI(&sentral->client->dev, "setting upload enable to %s\n", ENSTR(enable)); |
| return sentral_set_chip_control_flag(sentral, SEN_CHIP_CTRL_UPLOAD_ENABLE, |
| enable); |
| } |
| |
| // host iface control |
| |
| static int sentral_set_host_iface_control_flag(struct sentral_device *sentral, |
| u8 flag, bool enable) |
| { |
| return sentral_set_register_flag(sentral, SR_HOST_CONTROL, flag, enable); |
| } |
| |
| // ap suspend |
| |
| static int sentral_set_host_ap_suspend_enable(struct sentral_device *sentral, |
| bool enable) |
| { |
| LOGI(&sentral->client->dev, "setting AP suspend to %s\n", ENSTR(enable)); |
| return sentral_set_host_iface_control_flag(sentral, |
| SEN_HOST_CTRL_AP_SUSPENDED, enable); |
| } |
| |
| static int sentral_firmware_load(struct sentral_device *sentral, |
| const char *firmware_name) |
| { |
| const struct firmware *fw; |
| struct sentral_fw_header *fw_header; |
| struct sentral_fw_cds *fw_cds; |
| u32 *fw_data; |
| size_t fw_data_size; |
| |
| int rc = 0; |
| LOGI(&sentral->client->dev, "loading firmware: %s\n", firmware_name); |
| |
| // load fw from system |
| rc = request_firmware(&fw, firmware_name, &sentral->client->dev); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) loading firmware: %s\n", rc, |
| firmware_name); |
| |
| goto exit; |
| } |
| |
| // check fw size too small |
| if (fw->size < sizeof(*fw_header)) { |
| LOGE(&sentral->client->dev, "invalid firmware image size\n"); |
| goto exit; |
| } |
| |
| // check fw signature |
| fw_header = (struct sentral_fw_header *)fw->data; |
| if (fw_header->signature != FW_IMAGE_SIGNATURE) { |
| LOGE(&sentral->client->dev, "invalid firmware signature\n"); |
| goto exit; |
| } |
| |
| // check fw size too big |
| if ((sizeof(*fw_header) + fw_header->text_length) > fw->size) { |
| LOGE(&sentral->client->dev, "invalid firmware image size\n"); |
| goto exit; |
| } |
| |
| fw_cds = (struct sentral_fw_cds *)(sizeof(*fw_header) + fw->data |
| + fw_header->text_length - sizeof(struct sentral_fw_cds)); |
| |
| // send reset request |
| rc = sentral_request_reset(sentral); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) requesting reset\n", rc); |
| goto exit_release; |
| } |
| |
| // enable host upload |
| rc = sentral_set_host_upload_enable(sentral, true); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) enabling host upload\n", rc); |
| goto exit_release; |
| } |
| |
| fw_data = (u32 *)(((u8 *)fw->data) + sizeof(*fw_header)); |
| fw_data_size = fw->size - sizeof(*fw_header); |
| |
| while (fw_data_size > 0) { |
| u32 buf[MIN(RAM_BUF_LEN, I2C_BLOCK_SIZE_MAX) / sizeof(u32)]; |
| size_t ul_size = MIN(fw_data_size, sizeof(buf)); |
| int i; |
| |
| for (i = 0; i < ul_size / 4; i++) |
| buf[i] = swab32(*fw_data++); |
| |
| rc = sentral_write_block(sentral, SR_UPLOAD_DATA, (void *)buf, ul_size); |
| if (rc < 0) { |
| LOGE(&sentral->client->dev, "error (%d) uploading data\n", rc); |
| goto exit_release; |
| } |
| |
| fw_data_size -= ul_size; |
| } |
| |
| // disable host upload |
| rc = sentral_set_host_upload_enable(sentral, false); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) disabling host upload\n", rc); |
| goto exit_release; |
| } |
| |
| // check CRC |
| rc = sentral_crc_get(sentral, &sentral->fw_crc); |
| if (rc < 0) { |
| LOGE(&sentral->client->dev, "error (%d) reading host CRC\n", rc); |
| goto exit_release; |
| } |
| |
| LOGI(&sentral->client->dev, "host CRC: 0x%08X, fw CRC: 0x%08X\n", |
| sentral->fw_crc, fw_header->text_crc); |
| |
| if (sentral->fw_crc != fw_header->text_crc) { |
| LOGE(&sentral->client->dev, |
| "invalid firmware CRC, expected 0x%08X got 0x%08X\n", |
| sentral->fw_crc, fw_header->text_crc); |
| |
| goto exit_release; |
| } |
| |
| LOGI(&sentral->client->dev, "firmware CRC OK\n"); |
| |
| return 0; |
| exit_release: |
| release_firmware(fw); |
| exit: |
| return -EINVAL; |
| } |
| |
| // SYSFS |
| |
| // chip control |
| |
| static ssize_t sentral_sysfs_chip_control_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| ssize_t count = 0; |
| int rc; |
| struct sentral_chip_control chip_control; |
| |
| rc = sentral_read_byte(sentral, SR_CHIP_CONTROL); |
| if (rc < 0) |
| return rc; |
| |
| chip_control.byte = rc; |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%-16s: %s\n", |
| "CPU Run", (chip_control.bits.cpu_run ? "true" : "false")); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%-16s: %s\n", |
| "Upload Enable", |
| (chip_control.bits.upload_enable ? "true" : "false")); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(chip_control, S_IRUGO, sentral_sysfs_chip_control_show, |
| NULL); |
| |
| // host status |
| |
| static ssize_t sentral_sysfs_host_status_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| ssize_t count = 0; |
| int rc; |
| struct sentral_host_status host_status; |
| |
| rc = sentral_read_byte(sentral, SR_HOST_STATUS); |
| if (rc < 0) |
| return rc; |
| |
| host_status.byte = rc; |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%-16s: %s\n", |
| "CPU Reset", (host_status.bits.cpu_reset ? "true" : "false")); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%-16s: %s\n", |
| "Algo Standby", (host_status.bits.algo_standby ? "true" : "false")); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%-16s: %u\n", |
| "Host Iface ID", (host_status.bits.host_iface_id >> 2) & 0x07); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%-16s: %u\n", |
| "Algo ID", (host_status.bits.algo_id >> 5) & 0x07); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(host_status, S_IRUGO, sentral_sysfs_host_status_show, NULL); |
| |
| // chip status |
| |
| static ssize_t sentral_sysfs_chip_status_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| const char bit_strings[][20] = { |
| "EEPROM", "EEUploadDone", "EEUploadError", "Idle", "NoEEPROM", |
| }; |
| ssize_t count = 0; |
| int rc; |
| int i; |
| |
| rc = sentral_read_byte(sentral, SR_CHIP_STATUS); |
| if (rc < 0) |
| return rc; |
| |
| for (i = 0; i < sizeof(bit_strings) / 20; i++) { |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%-16s: %s\n", |
| bit_strings[i], (rc & (1 << i) ? "true" : "false")); |
| } |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(chip_status, S_IRUGO, sentral_sysfs_chip_status_show, NULL); |
| |
| // registers |
| |
| static ssize_t sentral_sysfs_registers_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| ssize_t len = SR_MAX - SR_FIRST + 1; |
| ssize_t count = 0; |
| u8 regs[len]; |
| int rc; |
| int i; |
| |
| rc = sentral_read_block(sentral, SR_FIRST, (void *)®s, len); |
| if (rc < 0) |
| return rc; |
| |
| for (i = 0; i < len; i++) |
| count += scnprintf(buf + count, PAGE_SIZE - count, "0x%02X: 0x%02X\n", |
| SR_FIRST + i, regs[i]); |
| |
| return count; |
| } |
| |
| static ssize_t sentral_sysfs_registers_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| unsigned int addr; |
| unsigned int value; |
| int rc; |
| |
| if (2 != sscanf(buf, "%u,%u", &addr, &value)) |
| return -EINVAL; |
| |
| if ((addr > SR_MAX) || (addr < SR_FIRST)) |
| return -EINVAL; |
| |
| I("[SYSFS] registers { addr: 0x%02X, value: 0x%02X }\n", addr, value); |
| |
| rc = sentral_write_byte(sentral, (u8)addr, (u8)value); |
| if (rc < 0) |
| return rc; |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(registers, S_IRUGO | S_IWUGO, sentral_sysfs_registers_show, sentral_sysfs_registers_store); |
| |
| // sensor info |
| |
| static ssize_t sentral_sysfs_sensor_info_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| ssize_t count = 0; |
| int rc; |
| int i; |
| struct sentral_param_sensor_info sensor_info; |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, |
| "%-33s,%4s,%6s,%6s,%4s,%8s,%6s,%6s\n", "SensorType", |
| "Ver", "Power", "Range", "Res", "MaxRate", "FRes", "FMax"); |
| |
| for (i = SST_FIRST; i < SST_MAX; i++) { |
| rc = sentral_sensor_info_read(sentral, i, &sensor_info); |
| if (rc) |
| return rc; |
| |
| if (!sensor_info.driver_id) |
| continue; |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, |
| "%-28s (%2d),%4u,%6u,%6u,%4u,%8u,%6u,%6u\n", |
| sentral_sensor_type_strings[i], |
| i, |
| sensor_info.driver_version, |
| sensor_info.power, |
| sensor_info.max_range, |
| sensor_info.resolution, |
| sensor_info.max_rate, |
| sensor_info.fifo_reserved, |
| sensor_info.fifo_max); |
| } |
| return count; |
| } |
| |
| static DEVICE_ATTR(sensor_info, S_IRUGO, sentral_sysfs_sensor_info_show, NULL); |
| |
| // sensor config |
| |
| static ssize_t sentral_sysfs_sensor_config_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| ssize_t count = 0; |
| int rc; |
| int i; |
| struct sentral_param_sensor_config sensor_config; |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, |
| "%-33s,%6s,%11s,%12s,%13s\n", "SensorType", "Rate", |
| "MaxLatency", "Sensitivity", "DynamicRange"); |
| |
| for (i = SST_FIRST; i < SST_MAX; i++) { |
| rc = sentral_sensor_config_read(sentral, i, &sensor_config); |
| if (rc) |
| return rc; |
| |
| if (!sentral_sensor_type_strings[i]) |
| continue; |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, |
| "%-28s (%2d),%6u,%11u,%12u,%13u\n", |
| sentral_sensor_type_strings[i], |
| i, |
| sensor_config.sample_rate, |
| sensor_config.max_report_latency, |
| sensor_config.change_sensitivity, |
| sensor_config.dynamic_range); |
| } |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(sensor_config, S_IRUGO, sentral_sysfs_sensor_config_show, |
| NULL); |
| |
| static ssize_t sentral_sysfs_phys_status_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| struct sentral_param_phys_sensor_status_page phys = {{ 0 }}; |
| ssize_t count = 0; |
| int rc; |
| |
| rc = sentral_parameter_read(sentral, SPP_SYS, SP_SYS_PHYS_SENSOR_STATUS, |
| (void *)&phys, sizeof(phys)); |
| if (rc) |
| return rc; |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, |
| "%-15s, %5s, %5s, %5s, %10s\n", "Sensor", "Rate", "Range", "Int", |
| "PowerMode"); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, |
| "%-15s, %5u, %5u, %5u, %10u\n", "Accelerometer", |
| phys.accel.sample_rate, phys.accel.dynamic_range, |
| phys.accel.flags.bits.int_enable, phys.accel.flags.bits.power_mode); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, |
| "%-15s, %5u, %5u, %5u, %10u\n", "Gyroscope", |
| phys.gyro.sample_rate, phys.gyro.dynamic_range, |
| phys.gyro.flags.bits.int_enable, phys.gyro.flags.bits.power_mode); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, |
| "%-15s, %5u, %5u, %5u, %10u\n", "Magnetometer", |
| phys.mag.sample_rate, phys.mag.dynamic_range, |
| phys.mag.flags.bits.int_enable, phys.mag.flags.bits.power_mode); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(phys_status, S_IRUGO, sentral_sysfs_phys_status_show, NULL); |
| |
| // sensor status |
| |
| static ssize_t sentral_sysfs_sensor_status_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| ssize_t count = 0; |
| int rc; |
| int i, j; |
| struct sentral_param_sensor_status sensor_status[16]; |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, |
| "%5s%10s%10s%10s%10s%10s%10s\n", "SID", "DataAvail", "I2CNACK", |
| "DevIDErr", "TransErr", "DataLost", "PowerMode"); |
| |
| for (i = 0; i < 2; i++) { |
| rc = sentral_parameter_read(sentral, SPP_SYS, |
| SP_SYS_SENSOR_STATUS_B0 + i, (void *)&sensor_status, |
| sizeof(sensor_status)); |
| |
| if (rc < 0) |
| return rc; |
| |
| for (j = 0; j < 16; j++) { |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%5d", |
| i * 16 + j + 1); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%10s", |
| TFSTR(sensor_status[j].bits.data_available)); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%10s", |
| TFSTR(sensor_status[j].bits.i2c_nack)); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%10s", |
| TFSTR(sensor_status[j].bits.device_id_error)); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%10s", |
| TFSTR(sensor_status[j].bits.transient_error)); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%10s", |
| TFSTR(sensor_status[j].bits.data_lost)); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "%10d", |
| sensor_status[j].bits.power_mode); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, "\n"); |
| } |
| } |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(sensor_status, S_IRUGO, sentral_sysfs_sensor_status_show, |
| NULL); |
| |
| static ssize_t sentral_sysfs_reset(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| |
| queue_work(sentral->sentral_wq, &sentral->work_reset); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(reset, S_IWUGO, NULL, sentral_sysfs_reset); |
| |
| // inactivity timeout |
| |
| static ssize_t sentral_sysfs_inactivity_timeout_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| u16 timeout_s = 0; |
| int rc; |
| |
| rc = sentral_inactivity_timeout_get(sentral, &timeout_s); |
| if (rc) |
| return rc; |
| |
| return scnprintf(buf, PAGE_SIZE, "%u\n", timeout_s); |
| } |
| |
| static ssize_t sentral_sysfs_inactivity_timeout_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| u16 timeout_s = 0; |
| int rc; |
| |
| rc = kstrtou16(buf, 10, &timeout_s); |
| if (rc) |
| return rc; |
| |
| I("[SYSFS] inactivity_timeout { timeout_s: %u }\n", timeout_s); |
| |
| rc = sentral_inactivity_timeout_set(sentral, timeout_s); |
| if (rc) |
| return rc; |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(inactivity_timeout, S_IRUGO | S_IWUGO, |
| sentral_sysfs_inactivity_timeout_show, |
| sentral_sysfs_inactivity_timeout_store); |
| |
| // coach fitness id |
| |
| static ssize_t sentral_sysfs_coach_fitness_id_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| u8 fitness_id = 0; |
| int rc; |
| |
| rc = sentral_coach_fitness_id_get(sentral, &fitness_id); |
| if (rc) |
| return rc; |
| |
| return scnprintf(buf, PAGE_SIZE, "%u\n", fitness_id); |
| } |
| |
| static ssize_t sentral_sysfs_coach_fitness_id_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| u8 fitness_id = 0; |
| int rc; |
| |
| rc = kstrtou8(buf, 10, &fitness_id); |
| if (rc) |
| return rc; |
| |
| I("[SYSFS] coach_fitness_id { fitness_id: %u }\n", fitness_id); |
| |
| rc = sentral_coach_fitness_id_set(sentral, fitness_id); |
| if (rc) |
| return rc; |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(coach_fitness_id, S_IRUGO | S_IWUGO, |
| sentral_sysfs_coach_fitness_id_show, |
| sentral_sysfs_coach_fitness_id_store); |
| |
| static ssize_t sentral_sysfs_ts_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| u32 stime = 0; |
| u64 ktime = sentral_get_boottime_ns(); |
| int rc; |
| |
| rc = sentral_stime_current_get(sentral, &stime); |
| if (rc) |
| return rc; |
| |
| return scnprintf(buf, PAGE_SIZE, "%u,%llu\n", stime, ktime); |
| } |
| |
| static DEVICE_ATTR(ts, S_IRUGO, sentral_sysfs_ts_show, NULL); |
| |
| static ssize_t sentral_sysfs_cal_ts_data_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| return scnprintf(buf, PAGE_SIZE, "%u\n", sentral->stime_scale); |
| } |
| |
| static ssize_t sentral_sysfs_cal_ts_data_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| unsigned int value = 0; |
| int rc; |
| |
| rc = kstrtouint(buf, 10, &value); |
| if (rc) |
| return rc; |
| |
| I("[SYSFS] cal_ts_data { value: %u }\n", value); |
| |
| sentral->stime_scale = value; |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(cal_ts_data, S_IRUGO | S_IWUGO, sentral_sysfs_cal_ts_data_show, |
| sentral_sysfs_cal_ts_data_store); |
| |
| // ANDROID sensor_poll_device_t method support |
| |
| // activate |
| |
| static ssize_t sentral_sysfs_enable_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| return scnprintf(buf, PAGE_SIZE, "%lu\n", sentral->enabled_mask); |
| } |
| |
| static ssize_t sentral_sysfs_enable_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| unsigned int id; |
| unsigned int enable; |
| int rc; |
| |
| if (2 != sscanf(buf, "%u %u", &id, &enable)) |
| return -EINVAL; |
| |
| if (!sentral_sensor_id_is_valid(id)) |
| return -EINVAL; |
| |
| I("[SYSFS] enable { id: %u, enable: %u }\n", id, enable); |
| |
| rc = sentral_sensor_enable_set(sentral, id, enable); |
| if (rc) |
| return rc; |
| |
| if (!enable) { |
| rc = sentral_fifo_flush(sentral, id); |
| if (rc) |
| return rc; |
| } |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(enable, S_IRUGO | S_IWUGO, sentral_sysfs_enable_show, |
| sentral_sysfs_enable_store); |
| |
| // set_delay |
| |
| static ssize_t sentral_sysfs_delay_ms_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| unsigned int id; |
| unsigned int delay_ms; |
| unsigned int sample_rate = 0; |
| int rc; |
| |
| if (2 != sscanf(buf, "%u %u", &id, &delay_ms)) |
| return -EINVAL; |
| |
| I("[SYSFS] delay_ms { id: %u, delay_ms: %u }\n", id, delay_ms); |
| |
| if (!sentral_sensor_id_is_valid(id)) |
| return -EINVAL; |
| |
| switch (id) { |
| // do not convert delay_ms to Hz for these sensors |
| case SST_INACTIVITY_ALARM: |
| sample_rate = delay_ms / 1000; |
| break; |
| case SST_COACH: |
| sample_rate = delay_ms / 10000; |
| break; |
| |
| // convert millis to Hz |
| default: |
| if (delay_ms > 0) { |
| delay_ms = MIN(1000, delay_ms); |
| sample_rate = 1000 / delay_ms; |
| } |
| } |
| |
| rc = sentral_sensor_batch_set(sentral, id, sample_rate, 0); |
| if (rc) |
| return rc; |
| |
| return rc; |
| } |
| |
| static DEVICE_ATTR(delay_ms, S_IWUGO, NULL, sentral_sysfs_delay_ms_store); |
| |
| // batch |
| |
| static ssize_t sentral_sysfs_batch_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| unsigned int id; |
| unsigned int flags; |
| unsigned int delay_ms; |
| unsigned int timeout_ms; |
| u16 sample_rate = 0; |
| int rc; |
| |
| if (4 != sscanf(buf, "%u %u %u %u", &id, &flags, &delay_ms, &timeout_ms)) |
| return -EINVAL; |
| |
| I("[SYSFS] batch { id: %u, flags: %u, delay_ms: %u, timeout_ms: %u }\n", |
| id, flags, delay_ms, timeout_ms); |
| |
| if (!sentral_sensor_id_is_valid(id)) |
| return -EINVAL; |
| |
| if (timeout_ms > USHRT_MAX) |
| timeout_ms = USHRT_MAX; |
| |
| switch (id) { |
| // do not convert delay_ms to Hz for these sensors |
| case SST_INACTIVITY_ALARM: |
| sample_rate = delay_ms / 1000; |
| break; |
| case SST_COACH: |
| sample_rate = delay_ms / 10000; |
| break; |
| |
| // convert millis to Hz |
| default: |
| if (delay_ms > 0) { |
| delay_ms = MIN(1000, delay_ms); |
| sample_rate = 1000 / delay_ms; |
| } |
| } |
| |
| if (sample_rate) |
| mdelay(20); |
| |
| rc = sentral_sensor_batch_set(sentral, id, sample_rate, timeout_ms); |
| if (rc) |
| return rc; |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(batch, S_IWUGO, NULL, sentral_sysfs_batch_store); |
| |
| // flush |
| |
| static ssize_t sentral_sysfs_flush_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| u8 id; |
| int rc; |
| |
| rc = kstrtou8(buf, 10, &id); |
| if (rc) |
| return rc; |
| |
| I("[SYSFS] fifo_flush { id: %u }\n", id); |
| |
| if (((id < SST_FIRST) || (id >= SST_MAX)) && (id != SST_ALL)) |
| return -EINVAL; |
| |
| rc = sentral_fifo_flush(sentral, id); |
| if (rc) |
| return rc; |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(flush, S_IWUGO, NULL, sentral_sysfs_flush_store); |
| |
| // fifo control |
| |
| static ssize_t sentral_sysfs_fifo_watermark_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| struct sentral_param_fifo_control fifo_ctrl = {{ 0 }}; |
| int rc; |
| |
| rc = sentral_parameter_read(sentral, SPP_SYS, SP_SYS_FIFO_CONTROL, |
| (void *)&fifo_ctrl, sizeof(fifo_ctrl)); |
| if (rc) |
| return rc; |
| |
| return scnprintf(buf, PAGE_SIZE, "%u\n", fifo_ctrl.fifo.watermark); |
| } |
| |
| static ssize_t sentral_sysfs_fifo_watermark_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| struct sentral_param_fifo_control fifo_ctrl = {{ 0 }}; |
| u16 watermark; |
| int rc; |
| |
| rc = kstrtou16(buf, 10, &watermark); |
| if (rc) |
| return rc; |
| |
| I("[SYSFS] fifo_watermark { watermark: %u }\n", watermark); |
| |
| // read current fifo control param |
| rc = sentral_parameter_read(sentral, SPP_SYS, SP_SYS_FIFO_CONTROL, |
| (void *)&fifo_ctrl, sizeof(fifo_ctrl)); |
| if (rc) |
| return rc; |
| |
| // update watermark portion |
| fifo_ctrl.fifo.watermark = watermark; |
| |
| rc = sentral_parameter_write(sentral, SPP_SYS, SP_SYS_FIFO_CONTROL, |
| (void *)&fifo_ctrl, sizeof(fifo_ctrl)); |
| if (rc) |
| return rc; |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(fifo_watermark, S_IRUGO | S_IWUGO, |
| sentral_sysfs_fifo_watermark_show, sentral_sysfs_fifo_watermark_store); |
| |
| static ssize_t sentral_sysfs_fifo_size_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| struct sentral_param_fifo_control fifo_ctrl = {{ 0 }}; |
| int rc; |
| |
| rc = sentral_parameter_read(sentral, SPP_SYS, SP_SYS_FIFO_CONTROL, |
| (void *)&fifo_ctrl, sizeof(fifo_ctrl)); |
| if (rc) |
| return rc; |
| |
| return scnprintf(buf, PAGE_SIZE, "%u\n", fifo_ctrl.fifo.size); |
| } |
| |
| static DEVICE_ATTR(fifo_size, S_IRUGO, sentral_sysfs_fifo_size_show, NULL); |
| |
| static ssize_t sentral_sysfs_dbg_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| size_t count = 0; |
| |
| int locked_special = mutex_is_locked(&sentral->lock_special); |
| int locked_flush = mutex_is_locked(&sentral->lock_flush); |
| int locked_reset = mutex_is_locked(&sentral->lock_reset); |
| int locked_ts = mutex_is_locked(&sentral->lock_ts); |
| |
| bool wlocked_irq = sentral->wlock_irq.active; |
| int wlocked_reset = wake_lock_active(&sentral->w_lock_reset); |
| |
| int fifo_pending = atomic_read(&sentral->fifo_pending); |
| |
| LOGE(&sentral->client->dev, |
| "locked_special: %d, locked_flush: %d, locked_reset: %d, locked_ts: %d\n", |
| locked_special, locked_flush, locked_reset, locked_ts); |
| LOGE(&sentral->client->dev, |
| "wlocked_irq: %d, wlocked_reset: %d, flush_pending: %d, fifo_pending: %d\n", |
| wlocked_irq, wlocked_reset, sentral->flush_pending, fifo_pending); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, |
| "locked_special: %d, locked_flush: %d, locked_reset: %d, locked_ts: %d\n", |
| locked_special, locked_flush, locked_reset, locked_ts); |
| |
| count += scnprintf(buf + count, PAGE_SIZE - count, |
| "wlocked_irq: %d, wlocked_reset: %d, flush_pending: %d, fifo_pending: %d\n", |
| wlocked_irq, wlocked_reset, sentral->flush_pending, fifo_pending); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(dbg, S_IRUGO, sentral_sysfs_dbg_show, NULL); |
| |
| static ssize_t sentral_sysfs_version_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| return scnprintf(buf, PAGE_SIZE, "%s.%s.%s.%s (%s)\n", SEN_DRV_PROJECT_ID, |
| SEN_DRV_SUBPROJECT_ID, SEN_DRV_VERSION, SEN_DRV_BUILD, SEN_DRV_DATE); |
| } |
| |
| static DEVICE_ATTR(version, S_IRUGO, sentral_sysfs_version_show, NULL); |
| |
| // asus bmmi attributes |
| |
| static ssize_t bmmi_chip_status_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| int rc; |
| u8 err_EEUploadError = 0x04; |
| |
| mutex_lock(&sentral->lock); |
| rc = sentral_read_byte(sentral, SR_CHIP_STATUS); |
| if (rc < 0) { |
| dev_err(dev, "error (%d) reading chip status\n", rc); |
| rc = sprintf(buf, "0\n"); |
| goto exit; |
| } |
| |
| dev_info(dev, "read chip_status: 0x%x\n", rc); |
| |
| if (rc & err_EEUploadError) { |
| rc = sprintf(buf, "0\n"); |
| goto exit; |
| } |
| |
| rc = sprintf(buf, "1\n"); |
| |
| exit: |
| mutex_unlock(&sentral->lock); |
| return rc; |
| } |
| |
| static DEVICE_ATTR(bmmi_chip_status, S_IRUGO, bmmi_chip_status_show, NULL); |
| |
| static int check_specific_sensor_status(struct sentral_device *sentral, int id) |
| { |
| int rc; |
| struct sentral_param_sensor_status sensor_status[16]; |
| u8 err = 0x01; |
| |
| rc = sentral_parameter_read(sentral, SPP_SYS, SP_SYS_SENSOR_STATUS_B0, (void *)&sensor_status, sizeof(sensor_status)); |
| if (rc < 0) { |
| dev_err(&sentral->client->dev, "error (%d) reading sensor status, bank: 0\n", rc); |
| return -EINVAL; |
| } |
| if (err & sensor_status[id].bits.i2c_nack || |
| err & sensor_status[id].bits.device_id_error || |
| err & sensor_status[id].bits.transient_error) { |
| dev_err(&sentral->client->dev, "sensor[%d] acts abnormally, i2c: 0x%x, device_id: 0x%x, transient: 0x%x\n", |
| id + 1, |
| sensor_status[id].bits.i2c_nack, |
| sensor_status[id].bits.device_id_error, |
| sensor_status[id].bits.transient_error); |
| return -EINVAL; |
| } |
| dev_info(&sentral->client->dev, "sensor[%d] acts well\n", id + 1); |
| return 0; |
| } |
| |
| static ssize_t bmmi_acc_status_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| int rc; |
| |
| mutex_lock(&sentral->lock); |
| |
| rc = check_specific_sensor_status(sentral, SST_ACCELEROMETER - 1); |
| if (rc < 0) { |
| rc = sprintf(buf, "0\n"); |
| goto exit; |
| } |
| rc = sprintf(buf, "1\n"); |
| |
| exit: |
| mutex_unlock(&sentral->lock); |
| return rc; |
| } |
| |
| static DEVICE_ATTR(bmmi_acc_status, S_IRUGO, bmmi_acc_status_show, NULL); |
| |
| static ssize_t bmmi_gyr_status_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| int rc; |
| |
| mutex_lock(&sentral->lock); |
| |
| rc = check_specific_sensor_status(sentral, SST_GYROSCOPE - 1); |
| if (rc < 0) { |
| rc = sprintf(buf, "0\n"); |
| goto exit; |
| } |
| rc = sprintf(buf, "1\n"); |
| |
| exit: |
| mutex_unlock(&sentral->lock); |
| return rc; |
| } |
| |
| static DEVICE_ATTR(bmmi_gyr_status, S_IRUGO, bmmi_gyr_status_show, NULL); |
| |
| static ssize_t bmmi_mag_status_show(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| int rc; |
| |
| mutex_lock(&sentral->lock); |
| |
| rc = check_specific_sensor_status(sentral, SST_GEOMAGNETIC_FIELD - 1); |
| if (rc < 0) { |
| rc = sprintf(buf, "0\n"); |
| goto exit; |
| } |
| rc = sprintf(buf, "1\n"); |
| |
| exit: |
| mutex_unlock(&sentral->lock); |
| return rc; |
| } |
| |
| static DEVICE_ATTR(bmmi_mag_status, S_IRUGO, bmmi_mag_status_show, NULL); |
| |
| static int get_specific_sensor_buffer(struct sentral_device *sentral, |
| int id, u8 *buffer) |
| { |
| struct sentral_param_sensor_config config; |
| int rc = 0; |
| u16 original_rate; |
| u16 original_latency; |
| |
| // read and store the sensor config |
| rc = sentral_parameter_read(sentral, SPP_SENSORS, |
| id + PARAM_SENSORS_ACTUAL_OFFSET, (void *)&config, |
| sizeof(struct sentral_param_sensor_config)); |
| if (rc < 0) { |
| dev_err(&sentral->client->dev, "error (%d) reading config: %d", rc, id); |
| goto exit; |
| } |
| original_rate = config.sample_rate; |
| original_latency = config.max_report_latency; |
| dev_info(&sentral->client->dev, |
| "(READ) config.sample_rate: %d, config.max_report_latency: %d", |
| original_rate, original_latency); |
| |
| // set sample rate |
| config.sample_rate = 200; |
| config.max_report_latency = 0; |
| rc = sentral_parameter_write(sentral, SPP_SENSORS, |
| id + PARAM_SENSORS_ACTUAL_OFFSET, (void *)&config, |
| sizeof(struct sentral_param_sensor_config)); |
| dev_info(&sentral->client->dev, |
| "(WRITE) config.sample_rate: %d, config.max_report_latency: %d", |
| config.sample_rate, config.max_report_latency); |
| if (rc < 0) { |
| dev_err(&sentral->client->dev, "error (%d) writing config: %d", rc, id); |
| goto exit; |
| } |
| |
| queue_work(sentral->sentral_wq, &sentral->work_fifo_read); |
| msleep(20); |
| |
| // write back the stored sensor config |
| config.sample_rate = original_rate; |
| config.max_report_latency = original_latency; |
| rc = sentral_parameter_write(sentral, SPP_SENSORS, |
| id + PARAM_SENSORS_ACTUAL_OFFSET, (void *)&config, |
| sizeof(struct sentral_param_sensor_config)); |
| dev_info(&sentral->client->dev, |
| "(WRITE) config.sample_rate: %d, config.max_report_latency: %d", |
| config.sample_rate, config.max_report_latency); |
| if (rc < 0) { |
| dev_err(&sentral->client->dev, "error (%d) writing config: %d", rc, id); |
| goto exit; |
| } |
| |
| memcpy(&buffer[0], sentral->latest_accel_buffer, |
| sizeof(sentral->latest_accel_buffer)); |
| |
| exit: |
| return rc; |
| } |
| |
| // asus smmi attributes |
| |
| static ssize_t smmi_acc_rawdata_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| u8 buffer[24]; |
| int x, y, z; |
| int rc, i; |
| |
| for (i = 0; i < sizeof(buffer); i++) |
| buffer[i] = 0; |
| |
| mutex_lock(&sentral->lock); |
| rc = get_specific_sensor_buffer(sentral, SST_ACCELEROMETER, buffer); |
| if (rc < 0) { |
| dev_err(&sentral->client->dev, "%s, rc: %d", __func__, rc); |
| rc = sprintf(buf, "0\n"); |
| goto exit; |
| } |
| x = (buffer[3] << 8) + buffer[2]; |
| y = (buffer[5] << 8) + buffer[4]; |
| z = (buffer[7] << 8) + buffer[6]; |
| (x >= 32768) ? (x = x - 65536) : (x = x); |
| (y >= 32768) ? (y = y - 65536) : (y = y); |
| (z >= 32768) ? (z = z - 65536) : (z = z); |
| |
| rc = sprintf(buf, "%d %d %d\n", x, y, z); |
| |
| exit: |
| mutex_unlock(&sentral->lock); |
| return rc; |
| } |
| |
| static DEVICE_ATTR(smmi_acc_rawdata, S_IRUGO, smmi_acc_rawdata_show, NULL); |
| |
| static ssize_t smmi_acc_cali_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| s64 coeffs[3]; |
| const int COEFFS_ACCEL_OFFSET = 15; |
| int rc, i; |
| |
| dev_info(&sentral->client->dev, "%s", __func__); |
| mutex_lock(&sentral->lock); |
| for (i = 0; i < 3; ++i) { |
| rc = sentral_parameter_read(sentral, SPP_ALGO_WARM_START, |
| COEFFS_ACCEL_OFFSET + i, (void *)&coeffs[i], sizeof(coeffs[i])); |
| if (rc < 0) { |
| dev_err(&sentral->client->dev, |
| "error (%d) reading acc cal coefficients", rc); |
| rc = sprintf(buf, "error (%d) reading acc cal coefficients\n", rc); |
| goto exit; |
| } |
| } |
| dev_info(&sentral->client->dev, |
| "read cali coefficients: (0x%016llx,0x%016llx,0x%016llx)", |
| coeffs[0], coeffs[1], coeffs[2]); |
| rc = sprintf(buf, "0x%016llx 0x%016llx 0x%016llx\n", |
| coeffs[0], coeffs[1], coeffs[2]); |
| |
| exit: |
| mutex_unlock(&sentral->lock); |
| return rc; |
| } |
| |
| static ssize_t smmi_acc_cali_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct sentral_device *sentral = dev_get_drvdata(dev); |
| s64 coeffs[3]; |
| const int COEFFS_ACCEL_OFFSET = 15; |
| int rc, i; |
| |
| dev_info(&sentral->client->dev, "%s", __func__); |
| mutex_lock(&sentral->lock); |
| if (3 != sscanf(buf, "%lld %lld %lld", |
| &coeffs[0], &coeffs[1], &coeffs[2])) { |
| rc = -EINVAL; |
| goto exit; |
| } |
| for (i = 0; i < 3; ++i) { |
| rc = sentral_parameter_write(sentral, SPP_ALGO_WARM_START, |
| COEFFS_ACCEL_OFFSET + i, (void *)&coeffs[i], sizeof(coeffs[i])); |
| if (rc < 0) { |
| dev_err(&sentral->client->dev, |
| "error (%d) writing acc cal coefficients", rc); |
| goto exit; |
| } |
| } |
| dev_info(&sentral->client->dev, |
| "write cali coefficients: (0x%016llx,0x%016llx,0x%016llx)", |
| coeffs[0], coeffs[1], coeffs[2]); |
| rc = count; |
| |
| exit: |
| mutex_unlock(&sentral->lock); |
| return rc; |
| } |
| |
| static DEVICE_ATTR(smmi_acc_cali, S_IRUGO|S_IWUGO, smmi_acc_cali_show, |
| smmi_acc_cali_store); |
| |
| static struct attribute *sentral_attributes[] = { |
| &dev_attr_chip_control.attr, |
| &dev_attr_host_status.attr, |
| &dev_attr_chip_status.attr, |
| &dev_attr_registers.attr, |
| &dev_attr_reset.attr, |
| &dev_attr_inactivity_timeout.attr, |
| &dev_attr_coach_fitness_id.attr, |
| &dev_attr_ts.attr, |
| &dev_attr_sensor_info.attr, |
| &dev_attr_sensor_config.attr, |
| &dev_attr_sensor_status.attr, |
| &dev_attr_phys_status.attr, |
| &dev_attr_enable.attr, |
| &dev_attr_delay_ms.attr, |
| &dev_attr_batch.attr, |
| &dev_attr_flush.attr, |
| &dev_attr_fifo_watermark.attr, |
| &dev_attr_fifo_size.attr, |
| &dev_attr_cal_ts_data.attr, |
| &dev_attr_dbg.attr, |
| &dev_attr_version.attr, |
| &dev_attr_bmmi_chip_status.attr, |
| &dev_attr_bmmi_acc_status.attr, |
| &dev_attr_bmmi_gyr_status.attr, |
| &dev_attr_bmmi_mag_status.attr, |
| &dev_attr_smmi_acc_rawdata.attr, |
| &dev_attr_smmi_acc_cali.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group sentral_attribute_group = { |
| .attrs = sentral_attributes |
| }; |
| |
| static int sentral_class_create(struct sentral_device *sentral) |
| { |
| int rc = 0; |
| |
| // custom sensor hub class |
| sentral->hub_class = class_create(THIS_MODULE, SENTRAL_HUB_CLASS_NAME); |
| if (IS_ERR(sentral->hub_class)) { |
| rc = PTR_ERR(sentral->hub_class); |
| LOGE(&sentral->client->dev, "error creating hub class: %d\n", rc); |
| goto exit; |
| } |
| |
| // custom sensor hub device |
| sentral->hub_device = device_create(sentral->hub_class, NULL, 0, |
| "%s", SENTRAL_HUB_DEVICE_NAME); |
| if (IS_ERR(sentral->hub_device)) { |
| rc = PTR_ERR(sentral->hub_device); |
| LOGE(&sentral->client->dev, "error creating hub device: %d\n", rc); |
| goto exit_class_created; |
| } |
| |
| // set device data |
| rc = dev_set_drvdata(sentral->hub_device, sentral); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error setting device data: %d\n", rc); |
| goto exit_device_created; |
| } |
| |
| return 0; |
| |
| exit_device_created: |
| device_unregister(sentral->hub_device); |
| exit_class_created: |
| class_destroy(sentral->hub_class); |
| exit: |
| return rc; |
| } |
| |
| static void sentral_class_destroy(struct sentral_device *sentral) |
| { |
| device_unregister(sentral->hub_device); |
| class_destroy(sentral->hub_class); |
| } |
| |
| // SYSFS |
| |
| static int sentral_sysfs_create(struct sentral_device *sentral) |
| { |
| int rc = 0; |
| |
| // link iio device |
| rc = sysfs_create_link(&sentral->hub_device->kobj, |
| &sentral->indio_dev->dev.kobj, "iio"); |
| if (rc < 0) { |
| LOGE(&sentral->client->dev, "error (%d) creating device iio link\n", |
| rc); |
| |
| return rc; |
| } |
| |
| // create root nodes |
| rc = sysfs_create_group(&sentral->hub_device->kobj, |
| &sentral_attribute_group); |
| |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) creating device root nodes\n", |
| rc); |
| |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static void sentral_sysfs_destroy(struct sentral_device *sentral) |
| { |
| |
| // remove iio device link |
| sysfs_remove_link(&sentral->hub_device->kobj, "iio"); |
| |
| // remove group |
| sysfs_remove_group(&sentral->hub_device->kobj, &sentral_attribute_group); |
| } |
| |
| static irqreturn_t sentral_irq_handler(int irq, void *dev_id) |
| { |
| struct sentral_device *sentral = dev_id; |
| struct sentral_wake_src_count wake_src_count; |
| int rc; |
| |
| LOGD(&sentral->client->dev, "IRQ received\n"); |
| |
| atomic_set(&sentral->fifo_pending, 1); |
| |
| if (sentral->init_complete) { |
| // cancel any delayed watchdog work |
| if (cancel_delayed_work(&sentral->work_watchdog) == 0) |
| flush_workqueue(sentral->sentral_wq); |
| |
| // check for special wakeup source |
| rc = sentral_read_block(sentral, SR_WAKE_SRC, (void *)&wake_src_count, |
| sizeof(wake_src_count)); |
| if (rc) |
| LOGE(&sentral->client->dev, "error (%d) reading wake source\n", rc); |
| |
| // handle wakeup source |
| if (wake_src_count.byte != sentral->wake_src_prev.byte) { |
| __pm_wakeup_event(&sentral->wlock_irq, 250); |
| rc = sentral_handle_special_wakeup(sentral, wake_src_count); |
| if (rc) |
| LOGE(&sentral->client->dev, "error (%d) handling wake source\n", rc); |
| } |
| |
| queue_work(sentral->sentral_wq, &sentral->work_fifo_read); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void sentral_do_work_watchdog(struct work_struct *work) |
| { |
| struct sentral_device *sentral = container_of((struct delayed_work *)work, |
| struct sentral_device, work_watchdog); |
| u8 err = 0; |
| u32 crc = 0; |
| int rc; |
| |
| LOGD(&sentral->client->dev, "[WD] Watchdog bite\n"); |
| |
| // check error register |
| err = sentral_error_get(sentral); |
| if (err > 0) |
| LOGE(&sentral->client->dev, "[WD] Error register: 0x%02X\n", err); |
| |
| switch (err) { |
| case SEN_ERR_MATH: |
| case SEN_ERR_MEM: |
| case SEN_ERR_STACK_OVERFLOW: |
| I("[WD] Fatal error: %u\n", err); |
| sentral_crash_reset(sentral); |
| return; |
| } |
| |
| // check CRC for memory corruption |
| rc = sentral_crc_get(sentral, &crc); |
| if (rc) { |
| LOGE(&sentral->client->dev, "[WD] error (%d) reading CRC\n", rc); |
| return; |
| } |
| |
| if (crc && (crc != sentral->fw_crc)) { |
| I("[WD] CRC error { fw: %u, read: %u }\n", sentral->fw_crc, crc); |
| sentral_crash_reset(sentral); |
| return; |
| } |
| |
| rc = sentral_fifo_read(sentral, (void *)sentral->data_buffer); |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "error (%d) reading FIFO in watchdog workqueue\n", rc); |
| sentral_crash_reset(sentral); |
| return; |
| } |
| } |
| |
| static void sentral_do_work_ts_ref_reset(struct work_struct *work) |
| { |
| struct sentral_device *sentral = container_of((struct delayed_work *)work, |
| struct sentral_device, work_ts_ref_reset); |
| |
| LOGI(&sentral->client->dev, "[TS] Queuing timestamp ref sync\n"); |
| |
| sentral->ts_ref_reset_mask = ULONG_MAX; |
| |
| } |
| |
| static void sentral_do_work_reset(struct work_struct *work) |
| { |
| struct sentral_device *sentral = container_of(work, struct sentral_device, |
| work_reset); |
| |
| int rc = 0; |
| |
| sentral->init_complete = false; |
| |
| mutex_lock(&sentral->lock_reset); |
| wake_lock(&sentral->w_lock_reset); |
| |
| // load firmware |
| rc = sentral_firmware_load(sentral, sentral->platform_data.firmware); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) loading firmware\n", rc); |
| goto exit; |
| } |
| |
| mdelay(100); |
| |
| // enable host run |
| rc = sentral_set_cpu_run_enable(sentral, true); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) enabling cpu run\n", rc); |
| goto exit; |
| } |
| |
| sentral->init_complete = true; |
| |
| mdelay(100); |
| |
| // queue a FIFO read |
| queue_work(sentral->sentral_wq, &sentral->work_fifo_read); |
| exit: |
| wake_unlock(&sentral->w_lock_reset); |
| mutex_unlock(&sentral->lock_reset); |
| } |
| |
| // IIO |
| |
| static const struct iio_buffer_setup_ops sentral_iio_buffer_setup_ops = { |
| .preenable = &iio_sw_buffer_preenable, |
| }; |
| |
| static int sentral_iio_buffer_create(struct iio_dev *indio_dev) |
| { |
| struct sentral_device *sentral = iio_priv(indio_dev); |
| int rc = 0; |
| |
| indio_dev->buffer = iio_kfifo_allocate(indio_dev); |
| if (!indio_dev->buffer) { |
| LOGE(&sentral->client->dev, "error allocating IIO kfifo buffer\n"); |
| return -ENOMEM; |
| } |
| |
| indio_dev->buffer->scan_timestamp = true; |
| indio_dev->setup_ops = &sentral_iio_buffer_setup_ops; |
| |
| rc = iio_buffer_register(indio_dev, indio_dev->channels, |
| indio_dev->num_channels); |
| |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) registering IIO buffer", rc); |
| goto exit_free; |
| } |
| |
| iio_scan_mask_set(indio_dev, indio_dev->buffer, SEN_SCAN_U32_1); |
| iio_scan_mask_set(indio_dev, indio_dev->buffer, SEN_SCAN_U32_2); |
| iio_scan_mask_set(indio_dev, indio_dev->buffer, SEN_SCAN_U32_3); |
| iio_scan_mask_set(indio_dev, indio_dev->buffer, SEN_SCAN_U32_4); |
| |
| return rc; |
| |
| exit_free: |
| iio_kfifo_free(indio_dev->buffer); |
| return rc; |
| } |
| |
| static void sentral_iio_buffer_destroy(struct iio_dev *indio_dev) |
| { |
| iio_buffer_unregister(indio_dev); |
| iio_kfifo_free(indio_dev->buffer); |
| } |
| |
| #define SENTRAL_IIO_CHANNEL(i) \ |
| {\ |
| .type = IIO_ACCEL,\ |
| .indexed = 1,\ |
| .channel = i,\ |
| .scan_index = i,\ |
| .scan_type = IIO_ST('u', 32, 32, 0),\ |
| } |
| |
| static const struct iio_chan_spec sentral_iio_channels[] = { |
| SENTRAL_IIO_CHANNEL(SEN_SCAN_U32_1), |
| SENTRAL_IIO_CHANNEL(SEN_SCAN_U32_2), |
| SENTRAL_IIO_CHANNEL(SEN_SCAN_U32_3), |
| SENTRAL_IIO_CHANNEL(SEN_SCAN_U32_4), |
| IIO_CHAN_SOFT_TIMESTAMP(SEN_SCAN_TS), |
| }; |
| |
| static const struct iio_info sentral_iio_info = { |
| .driver_module = THIS_MODULE, |
| }; |
| |
| static int sentral_suspend_notifier(struct notifier_block *nb, |
| unsigned long event, void *data) |
| { |
| struct sentral_device *sentral = container_of(nb, struct sentral_device, |
| nb); |
| |
| int rc; |
| |
| LOGD(&sentral->client->dev, "suspend nb: %lu\n", event); |
| |
| switch (event) { |
| |
| case PM_SUSPEND_PREPARE: |
| LOGI(&sentral->client->dev, "preparing to suspend ...\n"); |
| |
| // notify sentral of suspend |
| rc = sentral_set_host_ap_suspend_enable(sentral, true); |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "error (%d) setting AP suspend to true\n", rc); |
| } |
| |
| cancel_delayed_work_sync(&sentral->work_ts_ref_reset); |
| cancel_delayed_work_sync(&sentral->work_watchdog); |
| |
| if (atomic_read(&sentral->fifo_pending)) { |
| LOGI(&sentral->client->dev, "[PM] waiting for FIFO\n"); |
| rc = wait_event_interruptible_timeout(sentral->wq_fifo, |
| atomic_read(&sentral->fifo_pending) == 0, |
| msecs_to_jiffies(SENTRAL_WQ_FIFO_TIMEOUT_MSECS)); |
| |
| LOGD(&sentral->client->dev, "[PM] FIFO complete: %d\n", rc); |
| |
| if (rc == 1) |
| return NOTIFY_DONE; |
| |
| LOGE(&sentral->client->dev, |
| "timeout waiting for pending FIFO read, rc: %d\n", rc); |
| } |
| |
| break; |
| |
| case PM_POST_SUSPEND: |
| LOGI(&sentral->client->dev, "post suspend ...\n"); |
| |
| // notify sentral of wakeup |
| rc = sentral_set_host_ap_suspend_enable(sentral, false); |
| if (rc) { |
| LOGE(&sentral->client->dev, |
| "error (%d) setting AP suspend to false\n", rc); |
| } |
| |
| // reset overflow counter |
| sentral->overflow_count = 0; |
| |
| break; |
| } |
| |
| return NOTIFY_DONE; |
| } |
| |
| static int sentral_dt_parse(struct device *dev, |
| struct sentral_platform_data *platform_data) |
| { |
| int rc = 0; |
| |
| // IRQ |
| rc = of_get_named_gpio_flags(dev->of_node, "pni,gpio-irq", 0, NULL); |
| if (rc < 0) |
| return rc; |
| |
| platform_data->gpio_irq = rc; |
| |
| // FW name |
| rc = of_property_read_string(dev->of_node, "pni,firmware", |
| &platform_data->firmware); |
| |
| if (rc) |
| return rc; |
| |
| dev_info(dev, "platform_data->gpio_irq = %d\n", platform_data->gpio_irq); |
| dev_info(dev, "platform_data->firmware = %s\n", platform_data->firmware); |
| return 0; |
| } |
| |
| static int sentral_probe_id(struct i2c_client *client) |
| { |
| int rc; |
| struct sentral_hw_id hw_id; |
| |
| rc = i2c_smbus_read_i2c_block_data(client, SR_PRODUCT_ID, sizeof(hw_id), |
| (u8 *)&hw_id); |
| |
| if (rc < 0) |
| return rc; |
| |
| LOGI(&client->dev, "Product ID: 0x%02X, Revision ID: 0x%02X\n", |
| hw_id.product_id, hw_id.revision_id); |
| |
| return 0; |
| } |
| |
| static int sentral_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct sentral_device *sentral; |
| struct device *dev = &client->dev; |
| struct iio_dev *indio_dev; |
| int rc; |
| |
| // check i2c capabilities |
| rc = i2c_check_functionality(client->adapter, |
| I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_I2C_BLOCK); |
| |
| if (!rc) { |
| LOGE(dev, "i2c_check_functionality error\n"); |
| return -ENODEV; |
| } |
| |
| // probe for product id |
| rc = sentral_probe_id(client); |
| if (rc) { |
| LOGE(dev, "error (%d) reading hardware id\n", rc); |
| return -ENODEV; |
| } |
| |
| // allocate iio device |
| indio_dev = iio_device_alloc(sizeof(*sentral)); |
| if (!indio_dev) { |
| LOGE(dev, "couldn't allocate IIO device\n"); |
| return -ENOMEM; |
| } |
| |
| // set sentral data |
| sentral = iio_priv(indio_dev); |
| sentral->client = client; |
| sentral->device_id = id; |
| sentral->indio_dev = indio_dev; |
| |
| // alloc fifo data buffer |
| sentral->data_buffer = devm_kzalloc(&client->dev, DATA_BUFFER_SIZE, |
| GFP_KERNEL); |
| |
| if (!sentral->data_buffer) { |
| LOGE(&client->dev, "error allocating data buffer\n"); |
| rc = -ENOMEM; |
| goto error_free; |
| } |
| |
| // check platform data |
| if (!client->dev.of_node) { |
| LOGE(&client->dev, "error loading platform data\n"); |
| rc = -ENODEV; |
| goto error_free; |
| } |
| |
| // parse device tree |
| rc = sentral_dt_parse(&client->dev, &sentral->platform_data); |
| if (rc) { |
| LOGE(&client->dev, "error parsing device tree\n"); |
| rc = -ENODEV; |
| goto error_free; |
| } |
| |
| // request GPIO |
| if (gpio_is_valid(sentral->platform_data.gpio_irq)) { |
| rc = gpio_request_one(sentral->platform_data.gpio_irq, GPIOF_DIR_IN, |
| "sentral-gpio-irq"); |
| |
| if (rc) { |
| LOGE(&client->dev, "error requesting GPIO\n"); |
| rc = -ENODEV; |
| goto error_free; |
| } |
| } |
| |
| rc = gpio_to_irq(sentral->platform_data.gpio_irq); |
| if (rc < 0) { |
| LOGE(&client->dev, "error in GPIO to IRQ\n"); |
| rc = -ENODEV; |
| goto error_free; |
| } |
| sentral->irq = rc; |
| |
| // set i2c client data |
| i2c_set_clientdata(client, indio_dev); |
| |
| // set iio data |
| indio_dev->dev.parent = &client->dev; |
| indio_dev->name = SENTRAL_NAME; |
| indio_dev->info = &sentral_iio_info; |
| indio_dev->modes = INDIO_BUFFER_HARDWARE; |
| indio_dev->channels = sentral_iio_channels; |
| indio_dev->num_channels = ARRAY_SIZE(sentral_iio_channels); |
| |
| // iio buffer |
| if (sentral_iio_buffer_create(indio_dev)) { |
| LOGE(&client->dev, "IIO buffer create failed\n"); |
| goto error_iio_buffer; |
| } |
| |
| // iio device |
| if (iio_device_register(indio_dev)) { |
| LOGE(&client->dev, "IIO device register failed\n"); |
| goto error_iio_device; |
| } |
| |
| // init work callbacks |
| sentral->sentral_wq = create_singlethread_workqueue(SENTRAL_WORKQUEUE_NAME); |
| |
| INIT_WORK(&sentral->work_reset, sentral_do_work_reset); |
| INIT_WORK(&sentral->work_fifo_read, sentral_do_work_fifo_read); |
| INIT_DELAYED_WORK(&sentral->work_watchdog, &sentral_do_work_watchdog); |
| INIT_DELAYED_WORK(&sentral->work_ts_ref_reset, &sentral_do_work_ts_ref_reset); |
| |
| // init mutex, wakelock |
| mutex_init(&sentral->lock); |
| mutex_init(&sentral->lock_special); |
| mutex_init(&sentral->lock_flush); |
| mutex_init(&sentral->lock_reset); |
| mutex_init(&sentral->lock_ts); |
| init_waitqueue_head(&sentral->wq_flush); |
| |
| init_waitqueue_head(&sentral->wq_fifo); |
| atomic_set(&sentral->fifo_pending, 0); |
| |
| wakeup_source_init(&sentral->wlock_irq, dev_name(dev)); |
| wake_lock_init(&sentral->w_lock_reset, WAKE_LOCK_SUSPEND, dev_name(dev)); |
| |
| // zero wake source counters |
| sentral->fifo_watermark = 0; |
| sentral->wake_src_prev.byte = 0; |
| |
| // setup irq handler |
| LOGI(&sentral->client->dev, "requesting IRQ: %d, GPIO: %u\n", sentral->irq, |
| sentral->platform_data.gpio_irq); |
| |
| rc = devm_request_threaded_irq(&sentral->client->dev, sentral->irq, NULL, |
| sentral_irq_handler, IRQF_TRIGGER_RISING | IRQF_ONESHOT, |
| dev_name(&sentral->client->dev), sentral); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) requesting irq handler\n", rc); |
| return rc; |
| } |
| |
| // set ts defaults |
| memset(sentral->ts_sensor_ref, 0, sizeof(sentral->ts_sensor_ref)); |
| sentral->ts_ref_reset_mask = ULONG_MAX; |
| sentral->stime_scale = SENTRAL_SENSOR_TIMESTAMP_SCALE_NS; |
| |
| // init pm |
| device_init_wakeup(dev, 1); |
| if (device_may_wakeup(dev)) |
| enable_irq_wake(sentral->irq); |
| sentral->nb.notifier_call = sentral_suspend_notifier; |
| register_pm_notifier(&sentral->nb); |
| |
| // create custom class |
| rc = sentral_class_create(sentral); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) creating sensorhub class\n", |
| rc); |
| |
| goto error_class; |
| } |
| |
| // create sysfs nodes |
| rc = sentral_sysfs_create(sentral); |
| if (rc) { |
| LOGE(&sentral->client->dev, "error (%d) creating sysfs objects\n", rc); |
| goto error_sysfs; |
| } |
| |
| // startup |
| schedule_work(&sentral->work_reset); |
| return 0; |
| |
| error_sysfs: |
| sentral_class_destroy(sentral); |
| error_class: |
| iio_device_unregister(indio_dev); |
| error_iio_device: |
| sentral_iio_buffer_destroy(indio_dev); |
| error_iio_buffer: |
| error_free: |
| if (sentral->data_buffer) |
| devm_kfree(&client->dev, sentral->data_buffer); |
| |
| iio_device_free(indio_dev); |
| return -EIO; |
| } |
| |
| static int sentral_remove(struct i2c_client *client) |
| { |
| struct iio_dev *indio_dev = i2c_get_clientdata(client); |
| struct sentral_device *sentral = iio_priv(indio_dev); |
| |
| disable_irq(sentral->irq); |
| if (gpio_is_valid(sentral->platform_data.gpio_irq)) |
| gpio_free(sentral->platform_data.gpio_irq); |
| |
| cancel_work_sync(&sentral->work_fifo_read); |
| cancel_delayed_work_sync(&sentral->work_watchdog); |
| |
| destroy_workqueue(sentral->sentral_wq); |
| wakeup_source_destroy(&sentral->wlock_irq); |
| |
| sentral_sysfs_destroy(sentral); |
| sentral_class_destroy(sentral); |
| |
| iio_device_unregister(indio_dev); |
| sentral_iio_buffer_destroy(indio_dev); |
| |
| if (sentral->data_buffer) |
| devm_kfree(&client->dev, sentral->data_buffer); |
| |
| iio_device_free(indio_dev); |
| return 0; |
| } |
| |
| static const struct i2c_device_id sentral_i2c_id_table[] = { |
| {"em7184", 0}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(i2c, sentral_i2c_id_table); |
| |
| static const struct of_device_id sentral_of_id_table[] = { |
| {.compatible = "pni,em7184"}, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, sentral_of_id_table); |
| |
| static struct i2c_driver sentral_driver = { |
| .probe = sentral_probe, |
| .remove = sentral_remove, |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "sentral-iio", |
| .of_match_table = sentral_of_id_table, |
| }, |
| .id_table = sentral_i2c_id_table, |
| }; |
| module_i2c_driver(sentral_driver); |
| |
| MODULE_AUTHOR("Jeremiah Mattison <jmattison@pnicorp.com>"); |
| MODULE_DESCRIPTION("SENtral Sensor Hub Driver"); |
| MODULE_LICENSE("GPL"); |