blob: 0dd0b7e07ab9f6055d239cb1c38fb6e41e4b3222 [file] [log] [blame]
/* 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.
*/
/* Motion sense module to read from various motion sensors. */
#include "accelgyro.h"
#include "common.h"
#include "console.h"
#include "hooks.h"
#include "host_command.h"
#include "lid_angle.h"
#include "math_util.h"
#include "motion_sense.h"
#include "timer.h"
#include "task.h"
#include "util.h"
/* Console output macros */
#define CPUTS(outstr) cputs(CC_MOTION_SENSE, outstr)
#define CPRINTS(format, args...) cprints(CC_MOTION_SENSE, format, ## args)
/* Minimum time in between running motion sense task loop. */
#define MIN_MOTION_SENSE_WAIT_TIME (1 * MSEC)
static const struct motion_sensor_t *base;
static const struct motion_sensor_t *lid;
/* Current acceleration vectors and current lid angle. */
static vector_3_t acc_lid_raw, acc_lid, acc_base;
static vector_3_t acc_lid_host, acc_base_host;
static float lid_angle_deg;
static int lid_angle_is_reliable;
/* Bounds for setting the sensor polling interval. */
#define MIN_POLLING_INTERVAL_MS 5
#define MAX_POLLING_INTERVAL_MS 1000
/* Accelerometer polling intervals based on chipset state. */
static int accel_interval_ap_on_ms = 10;
static const int accel_interval_ap_suspend_ms = 100;
/*
* Angle threshold for how close the hinge aligns with gravity before
* considering the lid angle calculation unreliable. For computational
* efficiency, value is given unit-less, so if you want the threshold to be
* at 15 degrees, the value would be cos(15 deg) = 0.96593.
*/
#define HINGE_ALIGNED_WITH_GRAVITY_THRESHOLD 0.96593F
/* Sampling interval for measuring acceleration and calculating lid angle. */
static int accel_interval_ms;
#ifdef CONFIG_CMD_LID_ANGLE
static int accel_disp;
#endif
/* For vector_3_t, define which coordinates are in which location. */
enum {
X, Y, Z
};
/* Pointer to constant acceleration orientation data. */
const struct accel_orientation * const p_acc_orient = &acc_orient;
/**
* Calculate the lid angle using two acceleration vectors, one recorded in
* the base and one in the lid.
*
* @param base Base accel vector
* @param lid Lid accel vector
* @param lid_angle Pointer to location to store lid angle result
*
* @return flag representing if resulting lid angle calculation is reliable.
*/
static int calculate_lid_angle(vector_3_t base, vector_3_t lid,
float *lid_angle)
{
vector_3_t v;
float ang_lid_to_base, ang_lid_90, ang_lid_270;
float lid_to_base, base_to_hinge;
int reliable = 1;
/*
* The angle between lid and base is:
* acos((cad(base, lid) - cad(base, hinge)^2) /(1 - cad(base, hinge)^2))
* where cad() is the cosine_of_angle_diff() function.
*
* Make sure to check for divide by 0.
*/
lid_to_base = cosine_of_angle_diff(base, lid);
base_to_hinge = cosine_of_angle_diff(base, p_acc_orient->hinge_axis);
/*
* If hinge aligns too closely with gravity, then result may be
* unreliable.
*/
if (ABS(base_to_hinge) > HINGE_ALIGNED_WITH_GRAVITY_THRESHOLD)
reliable = 0;
base_to_hinge = SQ(base_to_hinge);
/* Check divide by 0. */
if (ABS(1.0F - base_to_hinge) < 0.01F) {
*lid_angle = 0.0;
return 0;
}
ang_lid_to_base = arc_cos(
(lid_to_base - base_to_hinge) / (1 - base_to_hinge));
/*
* The previous calculation actually has two solutions, a positive and
* a negative solution. To figure out the sign of the answer, calculate
* the angle between the actual lid angle and the estimated vector if
* the lid were open to 90 deg, ang_lid_90. Also calculate the angle
* between the actual lid angle and the estimated vector if the lid
* were open to 270 deg, ang_lid_270. The smaller of the two angles
* represents which one is closer. If the lid is closer to the
* estimated 270 degree vector then the result is negative, otherwise
* it is positive.
*/
rotate(base, &p_acc_orient->rot_hinge_90, &v);
ang_lid_90 = cosine_of_angle_diff(v, lid);
rotate(v, &p_acc_orient->rot_hinge_180, &v);
ang_lid_270 = cosine_of_angle_diff(v, lid);
/*
* Note that ang_lid_90 and ang_lid_270 are not in degrees, because
* the arc_cos() was never performed. But, since arc_cos() is
* monotonically decreasing, we can do this comparison without ever
* taking arc_cos(). But, since the function is monotonically
* decreasing, the logic of this comparison is reversed.
*/
if (ang_lid_270 > ang_lid_90)
ang_lid_to_base = -ang_lid_to_base;
/* Place lid angle between 0 and 360 degrees. */
if (ang_lid_to_base < 0)
ang_lid_to_base += 360;
*lid_angle = ang_lid_to_base;
return reliable;
}
int motion_get_lid_angle(void)
{
if (lid_angle_is_reliable)
/*
* Round to nearest int by adding 0.5. Note, only works because
* lid angle is known to be positive.
*/
return (int)(lid_angle_deg + 0.5F);
else
return (int)LID_ANGLE_UNRELIABLE;
}
#ifdef CONFIG_ACCEL_CALIBRATE
void motion_get_accel_lid(vector_3_t *v, int adjusted)
{
memcpy(v, adjusted ? &acc_lid : &acc_lid_raw, sizeof(vector_3_t));
}
void motion_get_accel_base(vector_3_t *v)
{
memcpy(v, &acc_base, sizeof(vector_3_t));
}
#endif
static void set_ap_suspend_polling(void)
{
accel_interval_ms = accel_interval_ap_suspend_ms;
}
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, set_ap_suspend_polling, HOOK_PRIO_DEFAULT);
static void set_ap_on_polling(void)
{
accel_interval_ms = accel_interval_ap_on_ms;
}
DECLARE_HOOK(HOOK_CHIPSET_RESUME, set_ap_on_polling, HOOK_PRIO_DEFAULT);
void motion_sense_task(void)
{
static timestamp_t ts0, ts1;
int wait_us;
int ret;
uint8_t *lpc_status;
uint16_t *lpc_data;
int sample_id = 0;
int i;
lpc_status = host_get_memmap(EC_MEMMAP_ACC_STATUS);
lpc_data = (uint16_t *)host_get_memmap(EC_MEMMAP_ACC_DATA);
/*
* TODO(crosbug.com/p/27320): The motion_sense task currently assumes
* one configuration of motion sensors. Namely, it assumes there is
* one accel in the base, one in the lid. Eventually, these
* assumptions will have to be removed when we have other
* configurations of motion sensors.
*/
for (i = 0; i < motion_sensor_count; ++i) {
if (motion_sensors[i].location == LOCATION_LID)
lid = &motion_sensors[i];
else if (motion_sensors[i].location == LOCATION_BASE)
base = &motion_sensors[i];
}
if (lid == NULL || base == NULL) {
CPRINTS("Invalid motion_sensors list, lid and base required");
return;
}
/* Initialize accelerometers. */
ret = lid->drv->init(lid->drv_data, lid->i2c_addr);
ret |= base->drv->init(base->drv_data, base->i2c_addr);
/* If accelerometers do not initialize, then end task. */
if (ret != EC_SUCCESS) {
CPRINTS("Accel init failed; stopping MS");
return;
}
/* Initialize sampling interval. */
accel_interval_ms = accel_interval_ap_suspend_ms;
/* Set default accelerometer parameters. */
lid->drv->set_range(lid->drv_data, 2, 1);
lid->drv->set_resolution(lid->drv_data, 12, 1);
lid->drv->set_datarate(lid->drv_data, 100000, 1);
base->drv->set_range(base->drv_data, 2, 1);
base->drv->set_resolution(base->drv_data, 12, 1);
base->drv->set_datarate(base->drv_data, 100000, 1);
/* Write to status byte to represent that accelerometers are present. */
*lpc_status |= EC_MEMMAP_ACC_STATUS_PRESENCE_BIT;
while (1) {
ts0 = get_time();
/* Read all accelerations. */
lid->drv->read(lid->drv_data, &acc_lid_raw[X], &acc_lid_raw[Y],
&acc_lid_raw[Z]);
base->drv->read(base->drv_data, &acc_base[X], &acc_base[Y],
&acc_base[Z]);
/*
* Rotate the lid vector so the reference frame aligns with
* the base sensor.
*/
rotate(acc_lid_raw, &p_acc_orient->rot_align, &acc_lid);
/* Calculate angle of lid. */
lid_angle_is_reliable = calculate_lid_angle(acc_base, acc_lid,
&lid_angle_deg);
/* TODO(crosbug.com/p/25597): Add filter to smooth lid angle. */
/* Rotate accels into standard reference frame for the host. */
rotate(acc_base, &p_acc_orient->rot_standard_ref,
&acc_base_host);
rotate(acc_lid, &p_acc_orient->rot_standard_ref,
&acc_lid_host);
/*
* Set the busy bit before writing the sensor data. Increment
* the counter and clear the busy bit after writing the sensor
* data. On the host side, the host needs to make sure the busy
* bit is not set and that the counter remains the same before
* and after reading the data.
*/
*lpc_status |= EC_MEMMAP_ACC_STATUS_BUSY_BIT;
/*
* Copy sensor data to shared memory. Note that this code
* assumes little endian, which is what the host expects. Also,
* note that we share the lid angle calculation with host only
* for debugging purposes. The EC lid angle is an approximation
* with un-calibrated accels. The AP calculates a separate,
* more accurate lid angle.
*/
lpc_data[0] = motion_get_lid_angle();
lpc_data[1] = acc_base_host[X];
lpc_data[2] = acc_base_host[Y];
lpc_data[3] = acc_base_host[Z];
lpc_data[4] = acc_lid_host[X];
lpc_data[5] = acc_lid_host[Y];
lpc_data[6] = acc_lid_host[Z];
/*
* Increment sample id and clear busy bit to signal we finished
* updating data.
*/
sample_id = (sample_id + 1) &
EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK;
*lpc_status = EC_MEMMAP_ACC_STATUS_PRESENCE_BIT | sample_id;
#ifdef CONFIG_LID_ANGLE_KEY_SCAN
lidangle_keyscan_update(motion_get_lid_angle());
#endif
#ifdef CONFIG_CMD_LID_ANGLE
if (accel_disp) {
CPRINTS("ACC base=%-5d, %-5d, %-5d lid=%-5d, "
"%-5d, %-5d a=%-6.1d r=%d",
acc_base[X], acc_base[Y], acc_base[Z],
acc_lid[X], acc_lid[Y], acc_lid[Z],
(int)(10*lid_angle_deg),
lid_angle_is_reliable);
}
#endif
/* Delay appropriately to keep sampling time consistent. */
ts1 = get_time();
wait_us = accel_interval_ms * MSEC - (ts1.val-ts0.val);
/*
* Guarantee some minimum delay to allow other lower priority
* tasks to run.
*/
if (wait_us < MIN_MOTION_SENSE_WAIT_TIME)
wait_us = MIN_MOTION_SENSE_WAIT_TIME;
task_wait_event(wait_us);
}
}
void accel_int_lid(enum gpio_signal signal)
{
/*
* Print statement is here for testing with console accelint command.
* Remove print statement when interrupt is used for real.
*/
CPRINTS("Accelerometer wake-up interrupt occurred on lid");
}
void accel_int_base(enum gpio_signal signal)
{
/*
* Print statement is here for testing with console accelint command.
* Remove print statement when interrupt is used for real.
*/
CPRINTS("Accelerometer wake-up interrupt occurred on base");
}
/*****************************************************************************/
/* Host commands */
/* Function to map host sensor IDs to motion sensor. */
static const struct motion_sensor_t
*host_sensor_id_to_motion_sensor(int host_id)
{
switch (host_id) {
case EC_MOTION_SENSOR_ACCEL_BASE:
return base;
case EC_MOTION_SENSOR_ACCEL_LID:
return lid;
}
/* If no match then the EC currently doesn't support ID received. */
return NULL;
}
static int host_cmd_motion_sense(struct host_cmd_handler_args *args)
{
const struct ec_params_motion_sense *in = args->params;
struct ec_response_motion_sense *out = args->response;
const struct motion_sensor_t *sensor;
int data;
switch (in->cmd) {
case MOTIONSENSE_CMD_DUMP:
/*
* TODO(crosbug.com/p/27320): Need to remove hard coding and
* use some motion_sense data structure from the board file to
* help fill in this response.
*/
out->dump.module_flags =
(*(host_get_memmap(EC_MEMMAP_ACC_STATUS)) &
EC_MEMMAP_ACC_STATUS_PRESENCE_BIT) ?
MOTIONSENSE_MODULE_FLAG_ACTIVE : 0;
out->dump.sensor_flags[0] = MOTIONSENSE_SENSOR_FLAG_PRESENT;
out->dump.sensor_flags[1] = MOTIONSENSE_SENSOR_FLAG_PRESENT;
out->dump.sensor_flags[2] = 0;
out->dump.data[0] = acc_base_host[X];
out->dump.data[1] = acc_base_host[Y];
out->dump.data[2] = acc_base_host[Z];
out->dump.data[3] = acc_lid_host[X];
out->dump.data[4] = acc_lid_host[Y];
out->dump.data[5] = acc_lid_host[Z];
args->response_size = sizeof(out->dump);
break;
case MOTIONSENSE_CMD_INFO:
/*
* TODO(crosbug.com/p/27320): Need to remove hard coding and
* use some motion_sense data structure from the board file to
* help fill in this response.
*/
sensor = host_sensor_id_to_motion_sensor(
in->sensor_odr.sensor_num);
if (sensor == NULL)
return EC_RES_INVALID_PARAM;
if (sensor->drv->sensor_type == SENSOR_ACCELEROMETER)
out->info.type = MOTIONSENSE_TYPE_ACCEL;
else if (sensor->drv->sensor_type == SENSOR_GYRO)
out->info.type = MOTIONSENSE_TYPE_GYRO;
if (sensor->location == LOCATION_BASE)
out->info.location = MOTIONSENSE_LOC_BASE;
else if (sensor->location == LOCATION_LID)
out->info.location = MOTIONSENSE_LOC_LID;
if (sensor->drv->chip_type == CHIP_KXCJ9)
out->info.chip = MOTIONSENSE_CHIP_KXCJ9;
else if (sensor->drv->chip_type == CHIP_LSM6DS0)
out->info.chip = MOTIONSENSE_CHIP_LSM6DS0;
args->response_size = sizeof(out->info);
break;
case MOTIONSENSE_CMD_EC_RATE:
/*
* Set new sensor sampling rate when AP is on, if the data arg
* has a value.
*/
if (in->ec_rate.data != EC_MOTION_SENSE_NO_VALUE) {
/* Bound the new sampling rate. */
data = in->ec_rate.data;
if (data < MIN_POLLING_INTERVAL_MS)
data = MIN_POLLING_INTERVAL_MS;
if (data > MAX_POLLING_INTERVAL_MS)
data = MAX_POLLING_INTERVAL_MS;
accel_interval_ap_on_ms = data;
accel_interval_ms = data;
}
out->ec_rate.ret = accel_interval_ap_on_ms;
args->response_size = sizeof(out->ec_rate);
break;
case MOTIONSENSE_CMD_SENSOR_ODR:
/* Verify sensor number is valid. */
sensor = host_sensor_id_to_motion_sensor(
in->sensor_odr.sensor_num);
if (sensor == NULL)
return EC_RES_INVALID_PARAM;
/* Set new datarate if the data arg has a value. */
if (in->sensor_odr.data != EC_MOTION_SENSE_NO_VALUE) {
if (sensor->drv->set_datarate(sensor->drv_data,
in->sensor_odr.data,
in->sensor_odr.roundup)
!= EC_SUCCESS) {
CPRINTS("MS bad sensor rate %d",
in->sensor_odr.data);
return EC_RES_INVALID_PARAM;
}
}
sensor->drv->get_datarate(sensor->drv_data, &data);
out->sensor_odr.ret = data;
args->response_size = sizeof(out->sensor_odr);
break;
case MOTIONSENSE_CMD_SENSOR_RANGE:
/* Verify sensor number is valid. */
sensor = host_sensor_id_to_motion_sensor(
in->sensor_odr.sensor_num);
if (sensor == NULL)
return EC_RES_INVALID_PARAM;
/* Set new datarate if the data arg has a value. */
if (in->sensor_range.data != EC_MOTION_SENSE_NO_VALUE) {
if (sensor->drv->set_range(sensor->drv_data,
in->sensor_range.data,
in->sensor_range.roundup)
!= EC_SUCCESS) {
CPRINTS("MS bad sensor range %d",
in->sensor_range.data);
return EC_RES_INVALID_PARAM;
}
}
sensor->drv->get_range(sensor->drv_data, &data);
out->sensor_range.ret = data;
args->response_size = sizeof(out->sensor_range);
break;
case MOTIONSENSE_CMD_KB_WAKE_ANGLE:
#ifdef CONFIG_LID_ANGLE_KEY_SCAN
/* Set new keyboard wake lid angle if data arg has value. */
if (in->kb_wake_angle.data != EC_MOTION_SENSE_NO_VALUE)
lid_angle_set_kb_wake_angle(in->kb_wake_angle.data);
out->kb_wake_angle.ret = lid_angle_get_kb_wake_angle();
#else
out->kb_wake_angle.ret = 0;
#endif
args->response_size = sizeof(out->kb_wake_angle);
break;
default:
CPRINTS("MS bad cmd 0x%x", in->cmd);
return EC_RES_INVALID_PARAM;
}
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_MOTION_SENSE_CMD,
host_cmd_motion_sense,
EC_VER_MASK(0));
/*****************************************************************************/
/* Console commands */
#ifdef CONFIG_CMD_LID_ANGLE
static int command_ctrl_print_lid_angle_calcs(int argc, char **argv)
{
char *e;
int val;
if (argc > 3)
return EC_ERROR_PARAM_COUNT;
/* First argument is on/off whether to display accel data. */
if (argc > 1) {
if (!parse_bool(argv[1], &val))
return EC_ERROR_PARAM1;
accel_disp = val;
}
/*
* Second arg changes the accel task time interval. Note accel
* sampling interval will be clobbered when chipset suspends or
* resumes.
*/
if (argc > 2) {
val = strtoi(argv[2], &e, 0);
if (*e)
return EC_ERROR_PARAM2;
accel_interval_ms = val;
}
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(lidangle, command_ctrl_print_lid_angle_calcs,
"on/off [interval]",
"Print lid angle calculations and set calculation frequency.", NULL);
#endif /* CONFIG_CMD_LID_ANGLE */
#ifdef CONFIG_CMD_ACCELS
static int command_accelrange(int argc, char **argv)
{
char *e;
int id, data, round = 1;
struct motion_sensor_t *sensor;
if (argc < 2 || argc > 4)
return EC_ERROR_PARAM_COUNT;
/* First argument is sensor id. */
id = strtoi(argv[1], &e, 0);
if (*e || id < 0 || id > motion_sensor_count)
return EC_ERROR_PARAM1;
sensor = motion_sensors[id];
if (argc >= 3) {
/* Second argument is data to write. */
data = strtoi(argv[2], &e, 0);
if (*e)
return EC_ERROR_PARAM2;
if (argc == 4) {
/* Third argument is rounding flag. */
round = strtoi(argv[3], &e, 0);
if (*e)
return EC_ERROR_PARAM3;
}
/*
* Write new range, if it returns invalid arg, then return
* a parameter error.
*/
if (sensor->drv->set_range(sensor->drv_data,
data,
round) == EC_ERROR_INVAL)
return EC_ERROR_PARAM2;
} else {
sensor->drv->get_range(sensor->drv_data, &data);
ccprintf("Range for sensor %d: %d\n", id, data);
}
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(accelrange, command_accelrange,
"id [data [roundup]]",
"Read or write accelerometer range", NULL);
static int command_accelresolution(int argc, char **argv)
{
char *e;
int id, data, round = 1;
struct motion_sensor_t *sensor;
if (argc < 2 || argc > 4)
return EC_ERROR_PARAM_COUNT;
/* First argument is sensor id. */
id = strtoi(argv[1], &e, 0);
if (*e || id < 0 || id > motion_sensor_count)
return EC_ERROR_PARAM1;
sensor = motion_sensors[id];
if (argc >= 3) {
/* Second argument is data to write. */
data = strtoi(argv[2], &e, 0);
if (*e)
return EC_ERROR_PARAM2;
if (argc == 4) {
/* Third argument is rounding flag. */
round = strtoi(argv[3], &e, 0);
if (*e)
return EC_ERROR_PARAM3;
}
/*
* Write new resolution, if it returns invalid arg, then
* return a parameter error.
*/
if (sensor->drv->set_resolution(sensor->drv_data, data, round)
== EC_ERROR_INVAL)
return EC_ERROR_PARAM2;
} else {
sensor->drv->get_resolution(sensor->drv_data, &data);
ccprintf("Resolution for sensor %d: %d\n", id, data);
}
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(accelres, command_accelresolution,
"id [data [roundup]]",
"Read or write accelerometer resolution", NULL);
static int command_acceldatarate(int argc, char **argv)
{
char *e;
int id, data, round = 1;
struct motion_sensor_t *sensor;
if (argc < 2 || argc > 4)
return EC_ERROR_PARAM_COUNT;
/* First argument is sensor id. */
id = strtoi(argv[1], &e, 0);
if (*e || id < 0 || id > motion_sensor_count)
return EC_ERROR_PARAM1;
sensor = motion_sensors[id];
if (argc >= 3) {
/* Second argument is data to write. */
data = strtoi(argv[2], &e, 0);
if (*e)
return EC_ERROR_PARAM2;
if (argc == 4) {
/* Third argument is rounding flag. */
round = strtoi(argv[3], &e, 0);
if (*e)
return EC_ERROR_PARAM3;
}
/*
* Write new data rate, if it returns invalid arg, then
* return a parameter error.
*/
if (sensor->drv->set_datarate(sensor->drv_data, data, round)
== EC_ERROR_INVAL)
return EC_ERROR_PARAM2;
} else {
sensor->drv->get_datarate(sensor->drv_data, &data);
ccprintf("Data rate for sensor %d: %d\n", id, data);
}
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(accelrate, command_acceldatarate,
"id [data [roundup]]",
"Read or write accelerometer range", NULL);
#ifdef CONFIG_ACCEL_INTERRUPTS
static int command_accelerometer_interrupt(int argc, char **argv)
{
char *e;
int id, thresh;
struct motion_sensor_t *sensor;
if (argc != 3)
return EC_ERROR_PARAM_COUNT;
/* First argument is id. */
id = strtoi(argv[1], &e, 0);
if (*e || id < 0 || id >= motion_sensor_count)
return EC_ERROR_PARAM1;
sensor = motion_sensors[id];
/* Second argument is interrupt threshold. */
thresh = strtoi(argv[2], &e, 0);
if (*e)
return EC_ERROR_PARAM2;
sensor->drv->set_interrupt(drv_data, thresh);
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(accelint, command_accelerometer_interrupt,
"id threshold",
"Write interrupt threshold", NULL);
#endif /* CONFIG_ACCEL_INTERRUPTS */
#endif /* CONFIG_CMD_ACCELS */