| /* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* KXCJ9 gsensor module for Chrome EC */ |
| |
| #include "accelgyro.h" |
| #include "common.h" |
| #include "console.h" |
| #include "driver/accel_kxcj9.h" |
| #include "gpio.h" |
| #include "i2c.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| #define CPUTS(outstr) cputs(CC_ACCEL, outstr) |
| #define CPRINTF(format, args...) cprintf(CC_ACCEL, format, ## args) |
| |
| /* Number of times to attempt to enable sensor before giving up. */ |
| #define SENSOR_ENABLE_ATTEMPTS 3 |
| |
| /* |
| * Struct for pairing an engineering value with the register value for a |
| * parameter. |
| */ |
| struct accel_param_pair { |
| int val; /* Value in engineering units. */ |
| int reg; /* Corresponding register value. */ |
| }; |
| |
| /* List of range values in +/-G's and their associated register values. */ |
| static const struct accel_param_pair ranges[] = { |
| {2, KXCJ9_GSEL_2G}, |
| {4, KXCJ9_GSEL_4G}, |
| {8, KXCJ9_GSEL_8G_14BIT} |
| }; |
| |
| /* List of resolution values in bits and their associated register values. */ |
| static const struct accel_param_pair resolutions[] = { |
| {8, KXCJ9_RES_8BIT}, |
| {12, KXCJ9_RES_12BIT} |
| }; |
| |
| /* List of ODR values in mHz and their associated register values. */ |
| static const struct accel_param_pair datarates[] = { |
| {781, KXCJ9_OSA_0_781HZ}, |
| {1563, KXCJ9_OSA_1_563HZ}, |
| {3125, KXCJ9_OSA_3_125HZ}, |
| {6250, KXCJ9_OSA_6_250HZ}, |
| {12500, KXCJ9_OSA_12_50HZ}, |
| {25000, KXCJ9_OSA_25_00HZ}, |
| {50000, KXCJ9_OSA_50_00HZ}, |
| {100000, KXCJ9_OSA_100_0HZ}, |
| {200000, KXCJ9_OSA_200_0HZ}, |
| {400000, KXCJ9_OSA_400_0HZ}, |
| {800000, KXCJ9_OSA_800_0HZ}, |
| {1600000, KXCJ9_OSA_1600_HZ} |
| }; |
| |
| /** |
| * Find index into a accel_param_pair that matches the given engineering value |
| * passed in. The round_up flag is used to specify whether to round up or down. |
| * Note, this function always returns a valid index. If the request is |
| * outside the range of values, it returns the closest valid index. |
| */ |
| static int find_param_index(const int eng_val, const int round_up, |
| const struct accel_param_pair *pairs, const int size) |
| { |
| int i; |
| |
| /* Linear search for index to match. */ |
| for (i = 0; i < size - 1; i++) { |
| if (eng_val <= pairs[i].val) |
| return i; |
| |
| if (eng_val < pairs[i+1].val) { |
| if (round_up) |
| return i + 1; |
| else |
| return i; |
| } |
| } |
| |
| return i; |
| } |
| |
| /** |
| * Read register from accelerometer. |
| */ |
| static int raw_read8(const int addr, const int reg, int *data_ptr) |
| { |
| return i2c_read8(I2C_PORT_ACCEL, addr, reg, data_ptr); |
| } |
| |
| /** |
| * Write register from accelerometer. |
| */ |
| static int raw_write8(const int addr, const int reg, int data) |
| { |
| return i2c_write8(I2C_PORT_ACCEL, addr, reg, data); |
| } |
| |
| /** |
| * Disable sensor by taking it out of operating mode. When disabled, the |
| * acceleration data does not change. |
| * |
| * Note: This is intended to be called in a pair with enable_sensor(). |
| * |
| * @data Pointer to motion sensor data |
| * @ctrl1 Pointer to location to store KXCJ9_CTRL1 register after disabling |
| * |
| * @return EC_SUCCESS if successful, EC_ERROR_* otherwise |
| */ |
| static int disable_sensor(struct kxcj9_data *data, int *ctrl1) |
| { |
| int ret; |
| |
| /* |
| * Read the current state of the ctrl1 register so that we can restore |
| * it later. |
| */ |
| ret = raw_read8(data->accel_addr, KXCJ9_CTRL1, ctrl1); |
| if (ret != EC_SUCCESS) |
| return ret; |
| |
| /* |
| * Before disabling the sensor, acquire mutex to prevent another task |
| * from attempting to access accel parameters until we enable sensor. |
| */ |
| mutex_lock(&data->accel_mutex); |
| |
| /* Disable sensor. */ |
| *ctrl1 &= ~KXCJ9_CTRL1_PC1; |
| ret = raw_write8(data->accel_addr, KXCJ9_CTRL1, *ctrl1); |
| if (ret != EC_SUCCESS) { |
| mutex_unlock(&data->accel_mutex); |
| return ret; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| /** |
| * Enable sensor by placing it in operating mode. |
| * |
| * Note: This is intended to be called in a pair with disable_sensor(). |
| * |
| * @data Pointer to motion sensor data |
| * @ctrl1 Value of KXCJ9_CTRL1 register to write to sensor |
| * |
| * @return EC_SUCCESS if successful, EC_ERROR_* otherwise |
| */ |
| static int enable_sensor(struct kxcj9_data *data, const int ctrl1) |
| { |
| int i, ret; |
| |
| for (i = 0; i < SENSOR_ENABLE_ATTEMPTS; i++) { |
| /* Enable accelerometer based on ctrl1 value. */ |
| ret = raw_write8(data->accel_addr, KXCJ9_CTRL1, |
| ctrl1 | KXCJ9_CTRL1_PC1); |
| |
| /* On first success, we are done. */ |
| if (ret == EC_SUCCESS) { |
| mutex_unlock(&data->accel_mutex); |
| return EC_SUCCESS; |
| } |
| } |
| |
| /* Release mutex. */ |
| mutex_unlock(&data->accel_mutex); |
| |
| /* Cannot enable accel, print warning and return an error. */ |
| CPRINTF("Error trying to enable accelerometer\n"); |
| |
| return ret; |
| } |
| |
| static int accel_set_range(void *drv_data, |
| const int range, |
| const int rnd) |
| { |
| int ret, ctrl1, ctrl1_new, index; |
| struct kxcj9_data *data = (struct kxcj9_data *)drv_data; |
| |
| /* Find index for interface pair matching the specified range. */ |
| index = find_param_index(range, rnd, ranges, ARRAY_SIZE(ranges)); |
| |
| /* Disable the sensor to allow for changing of critical parameters. */ |
| ret = disable_sensor(data, &ctrl1); |
| if (ret != EC_SUCCESS) |
| return ret; |
| |
| /* Determine new value of CTRL1 reg and attempt to write it. */ |
| ctrl1_new = (ctrl1 & ~KXCJ9_GSEL_ALL) | ranges[index].reg; |
| ret = raw_write8(data->accel_addr, KXCJ9_CTRL1, ctrl1_new); |
| |
| /* If successfully written, then save the range. */ |
| if (ret == EC_SUCCESS) { |
| data->sensor_range = index; |
| ctrl1 = ctrl1_new; |
| } |
| |
| /* Re-enable the sensor. */ |
| if (enable_sensor(data, ctrl1) != EC_SUCCESS) |
| return EC_ERROR_UNKNOWN; |
| |
| return ret; |
| } |
| |
| static int accel_get_range(void *drv_data, int * const range) |
| { |
| struct kxcj9_data *data = (struct kxcj9_data *)drv_data; |
| *range = ranges[data->sensor_range].val; |
| return EC_SUCCESS; |
| } |
| |
| static int accel_set_resolution(void *drv_data, |
| const int res, |
| const int rnd) |
| { |
| int ret, ctrl1, ctrl1_new, index; |
| struct kxcj9_data *data = (struct kxcj9_data *)drv_data; |
| |
| /* Find index for interface pair matching the specified resolution. */ |
| index = find_param_index(res, rnd, resolutions, |
| ARRAY_SIZE(resolutions)); |
| |
| /* Disable the sensor to allow for changing of critical parameters. */ |
| ret = disable_sensor(data, &ctrl1); |
| if (ret != EC_SUCCESS) |
| return ret; |
| |
| /* Determine new value of CTRL1 reg and attempt to write it. */ |
| ctrl1_new = (ctrl1 & ~KXCJ9_RES_12BIT) | resolutions[index].reg; |
| ret = raw_write8(data->accel_addr, KXCJ9_CTRL1, ctrl1_new); |
| |
| /* If successfully written, then save the range. */ |
| if (ret == EC_SUCCESS) { |
| data->sensor_resolution = index; |
| ctrl1 = ctrl1_new; |
| } |
| |
| /* Re-enable the sensor. */ |
| if (enable_sensor(data, ctrl1) != EC_SUCCESS) |
| return EC_ERROR_UNKNOWN; |
| |
| return ret; |
| } |
| |
| static int accel_get_resolution(void *drv_data, int * const res) |
| { |
| struct kxcj9_data *data = (struct kxcj9_data *)drv_data; |
| *res = resolutions[data->sensor_resolution].val; |
| return EC_SUCCESS; |
| } |
| |
| static int accel_set_datarate(void *drv_data, |
| const int rate, |
| const int rnd) |
| { |
| int ret, ctrl1, index; |
| struct kxcj9_data *data = (struct kxcj9_data *)drv_data; |
| |
| /* Find index for interface pair matching the specified rate. */ |
| index = find_param_index(rate, rnd, datarates, ARRAY_SIZE(datarates)); |
| |
| /* Disable the sensor to allow for changing of critical parameters. */ |
| ret = disable_sensor(data, &ctrl1); |
| if (ret != EC_SUCCESS) |
| return ret; |
| |
| /* Set output data rate. */ |
| ret = raw_write8(data->accel_addr, KXCJ9_DATA_CTRL, |
| datarates[index].reg); |
| |
| /* If successfully written, then save the range. */ |
| if (ret == EC_SUCCESS) |
| data->sensor_datarate = index; |
| |
| /* Re-enable the sensor. */ |
| if (enable_sensor(data, ctrl1) != EC_SUCCESS) |
| return EC_ERROR_UNKNOWN; |
| |
| return ret; |
| } |
| |
| static int accel_get_datarate(void *drv_data, int * const rate) |
| { |
| struct kxcj9_data *data = (struct kxcj9_data *)drv_data; |
| *rate = datarates[data->sensor_datarate].val; |
| return EC_SUCCESS; |
| } |
| |
| |
| #ifdef CONFIG_ACCEL_INTERRUPTS |
| static int accel_set_interrupt(void *drv_data, unsigned int threshold) |
| { |
| int ctrl1, tmp, ret; |
| struct kxcj9_data *data = (struct kxcj9_data *)drv_data; |
| |
| /* Disable the sensor to allow for changing of critical parameters. */ |
| ret = disable_sensor(data, &ctrl1); |
| if (ret != EC_SUCCESS) |
| return ret; |
| |
| /* Set interrupt timer to 1 so it wakes up immediately. */ |
| ret = raw_write8(data->accel_addr, KXCJ9_WAKEUP_TIMER, 1); |
| if (ret != EC_SUCCESS) |
| goto error_enable_sensor; |
| |
| /* |
| * Set threshold, note threshold register is in units of 16 counts, so |
| * first we need to divide by 16 to get the value to send. |
| */ |
| threshold >>= 4; |
| ret = raw_write8(data->accel_addr, KXCJ9_WAKEUP_THRESHOLD, threshold); |
| if (ret != EC_SUCCESS) |
| goto error_enable_sensor; |
| |
| /* |
| * Set interrupt enable register on sensor. Note that once this |
| * function is called once, the interrupt stays enabled and it is |
| * only necessary to clear KXCJ9_INT_REL to allow the next interrupt. |
| */ |
| ret = raw_read8(data->accel_addr, KXCJ9_INT_CTRL1, &tmp); |
| if (ret != EC_SUCCESS) |
| goto error_enable_sensor; |
| if (!(tmp & KXCJ9_INT_CTRL1_IEN)) { |
| ret = raw_write8(data->accel_addr, KXCJ9_INT_CTRL1, |
| tmp | KXCJ9_INT_CTRL1_IEN); |
| if (ret != EC_SUCCESS) |
| goto error_enable_sensor; |
| } |
| |
| /* |
| * Clear any pending interrupt on sensor by reading INT_REL register. |
| * Note: this register latches motion detected above threshold. Once |
| * latched, no interrupt can occur until this register is cleared. |
| */ |
| ret = raw_read8(data->accel_addr, KXCJ9_INT_REL, &tmp); |
| |
| error_enable_sensor: |
| /* Re-enable the sensor. */ |
| if (enable_sensor(data, ctrl1) != EC_SUCCESS) |
| return EC_ERROR_UNKNOWN; |
| |
| return ret; |
| } |
| #endif |
| |
| static int accel_read(void *drv_data, |
| int * const x_acc, |
| int * const y_acc, |
| int * const z_acc) |
| { |
| uint8_t acc[6]; |
| uint8_t reg = KXCJ9_XOUT_L; |
| int ret, multiplier; |
| struct kxcj9_data *data = (struct kxcj9_data *)drv_data; |
| |
| /* Read 6 bytes starting at KXCJ9_XOUT_L. */ |
| mutex_lock(&data->accel_mutex); |
| i2c_lock(I2C_PORT_ACCEL, 1); |
| ret = i2c_xfer(I2C_PORT_ACCEL, data->accel_addr, ®, 1, acc, 6, |
| I2C_XFER_SINGLE); |
| i2c_lock(I2C_PORT_ACCEL, 0); |
| mutex_unlock(&data->accel_mutex); |
| |
| if (ret != EC_SUCCESS) |
| return ret; |
| |
| /* Determine multiplier based on stored range. */ |
| switch (ranges[data->sensor_range].reg) { |
| case KXCJ9_GSEL_2G: |
| multiplier = 1; |
| break; |
| case KXCJ9_GSEL_4G: |
| multiplier = 2; |
| break; |
| case KXCJ9_GSEL_8G: |
| case KXCJ9_GSEL_8G_14BIT: |
| multiplier = 4; |
| break; |
| default: |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| /* |
| * Convert acceleration to a signed 12-bit number. Note, based on |
| * the order of the registers: |
| * |
| * acc[0] = KXCJ9_XOUT_L |
| * acc[1] = KXCJ9_XOUT_H |
| * acc[2] = KXCJ9_YOUT_L |
| * acc[3] = KXCJ9_YOUT_H |
| * acc[4] = KXCJ9_ZOUT_L |
| * acc[5] = KXCJ9_ZOUT_H |
| */ |
| *x_acc = multiplier * (((int8_t)acc[1]) << 4) | (acc[0] >> 4); |
| *y_acc = multiplier * (((int8_t)acc[3]) << 4) | (acc[2] >> 4); |
| *z_acc = multiplier * (((int8_t)acc[5]) << 4) | (acc[4] >> 4); |
| |
| return EC_SUCCESS; |
| } |
| |
| static int accel_init(void *drv_data, int i2c_addr) |
| { |
| int ret = EC_SUCCESS; |
| int cnt = 0, ctrl1, ctrl2; |
| struct kxcj9_data *data = (struct kxcj9_data *)drv_data; |
| |
| if (data == NULL) |
| return EC_ERROR_INVAL; |
| |
| memset(&data->accel_mutex, sizeof(struct mutex), 0); |
| data->sensor_range = 0; |
| data->sensor_datarate = 6; |
| data->sensor_resolution = 1; |
| data->accel_addr = i2c_addr; |
| |
| /* Disable the sensor to allow for changing of critical parameters. */ |
| ret = disable_sensor(data, &ctrl1); |
| if (ret != EC_SUCCESS) |
| return ret; |
| |
| /* |
| * This sensor can be powered through an EC reboot, so the state of |
| * the sensor is unknown here. Initiate software reset to restore |
| * sensor to default. |
| */ |
| ret = raw_write8(data->accel_addr, KXCJ9_CTRL2, KXCJ9_CTRL2_SRST); |
| if (ret != EC_SUCCESS) |
| return ret; |
| |
| /* Wait until software reset is complete or timeout. */ |
| while (1) { |
| ret = raw_read8(data->accel_addr, KXCJ9_CTRL2, &ctrl2); |
| |
| /* Reset complete. */ |
| if (ret == EC_SUCCESS && !(ctrl2 & KXCJ9_CTRL2_SRST)) |
| break; |
| |
| /* Check for timeout. */ |
| if (cnt++ > 5) |
| return EC_ERROR_TIMEOUT; |
| |
| /* Give more time for reset action to complete. */ |
| msleep(10); |
| } |
| |
| /* Set resolution and range. */ |
| ctrl1 = resolutions[data->sensor_resolution].reg | |
| ranges[data->sensor_range].reg; |
| #ifdef CONFIG_ACCEL_INTERRUPTS |
| /* Enable wake up (motion detect) functionality. */ |
| ctrl1 |= KXCJ9_CTRL1_WUFE; |
| #endif |
| ret = raw_write8(data->accel_addr, KXCJ9_CTRL1, ctrl1); |
| |
| #ifdef CONFIG_ACCEL_INTERRUPTS |
| /* Set interrupt polarity to rising edge and keep interrupt disabled. */ |
| ret |= raw_write8(data->accel_addr, |
| KXCJ9_INT_CTRL1, |
| KXCJ9_INT_CTRL1_IEA); |
| |
| /* Set output data rate for wake-up interrupt function. */ |
| ret |= raw_write8(data->accel_addr, KXCJ9_CTRL2, KXCJ9_OWUF_100_0HZ); |
| |
| /* Set interrupt to trigger on motion on any axis. */ |
| ret |= raw_write8(data->accel_addr, KXCJ9_INT_CTRL2, |
| KXCJ9_INT_SRC2_XNWU | KXCJ9_INT_SRC2_XPWU | |
| KXCJ9_INT_SRC2_YNWU | KXCJ9_INT_SRC2_YPWU | |
| KXCJ9_INT_SRC2_ZNWU | KXCJ9_INT_SRC2_ZPWU); |
| |
| /* |
| * Enable accel interrupts. Note: accels will not initiate an interrupt |
| * until interrupt enable bit in KXCJ9_INT_CTRL1 is set on the device. |
| */ |
| gpio_enable_interrupt(GPIO_ACCEL_INT_LID); |
| gpio_enable_interrupt(GPIO_ACCEL_INT_BASE); |
| #endif |
| |
| /* Set output data rate. */ |
| ret |= raw_write8(data->accel_addr, KXCJ9_DATA_CTRL, |
| datarates[data->sensor_datarate].reg); |
| |
| /* Enable the sensor. */ |
| ret |= enable_sensor(data, ctrl1); |
| |
| return ret; |
| } |
| |
| const struct accelgyro_info accel_kxcj9 = { |
| .chip_type = CHIP_KXCJ9, |
| .sensor_type = SENSOR_ACCELEROMETER, |
| .init = accel_init, |
| .read = accel_read, |
| .set_range = accel_set_range, |
| .get_range = accel_get_range, |
| .set_resolution = accel_set_resolution, |
| .get_resolution = accel_get_resolution, |
| .set_datarate = accel_set_datarate, |
| .get_datarate = accel_get_datarate, |
| #ifdef CONFIG_ACCEL_INTERRUPTS |
| .set_interrupt = accel_set_interrupt, |
| #endif |
| }; |