/* * The sensor can also generate interrupts (DRDY) but it's pretty pointless * because they are generated even if the data do not change. So it's better * to keep the interrupt for the free-fall event. The values are updated at * 40Hz (at the lowest frequency), but as it can be pretty time consuming on * some low processor, we poll the sensor only at 20Hz... enough for the * joystick.
*/
/* * LIS3LV02D spec says 1024 LSBs corresponds 1 G -> 1LSB is 1000/1024 mG * LIS302D spec says: 18 mG / digit * LIS3_ACCURACY is used to increase accuracy of the intermediate * calculation results.
*/ #define LIS3_ACCURACY 1024 /* Sensitivity values for -2G +2G scale */ #define LIS3_SENSITIVITY_12B ((LIS3_ACCURACY * 1000) / 1024) #define LIS3_SENSITIVITY_8B (18 * LIS3_ACCURACY)
/* * LIS331DLH spec says 1LSBs corresponds 4G/4096 -> 1LSB is 1000/1024 mG. * Below macros defines sensitivity values for +/-2G. Dataout bits for * +/-2G range is 12 bits so 4 bits adjustment must be done to get 12bit * data from 16bit value. Currently this driver supports only 2G range.
*/ #define LIS3DLH_SENSITIVITY_2G ((LIS3_ACCURACY * 1000) / 1024) #define SHIFT_ADJ_2G 4
/* just like param_set_int() but does sanity-check so that it won't point * over the axis array size
*/ staticint param_set_axis(constchar *val, conststruct kernel_param *kp)
{ int ret = param_set_int(val, kp); if (!ret) { int val = *(int *)kp->arg; if (val < 0)
val = -val; if (!val || val > 3) return -EINVAL;
} return ret;
}
lis3->read(lis3, reg - 1, &lo);
lis3->read(lis3, reg, &hi); /* In "12 bit right justified" mode, bit 6, bit 7, bit 8 = bit 5 */ return (s16)((hi << 8) | lo);
}
/* 12bits for 2G range, 13 bits for 4G range and 14 bits for 8G range */ static s16 lis331dlh_read_data(struct lis3lv02d *lis3, int reg)
{
u8 lo, hi; int v;
/** * lis3lv02d_get_axis - For the given axis, give the value converted * @axis: 1,2,3 - can also be negative * @hw_values: raw values returned by the hardware * * Returns the converted value.
*/ staticinlineint lis3lv02d_get_axis(s8 axis, int hw_values[3])
{ if (axis > 0) return hw_values[axis - 1]; else return -hw_values[-axis - 1];
}
/** * lis3lv02d_get_xyz - Get X, Y and Z axis values from the accelerometer * @lis3: pointer to the device struct * @x: where to store the X axis value * @y: where to store the Y axis value * @z: where to store the Z axis value * * Note that 40Hz input device can eat up about 10% CPU at 800MHZ
*/ staticvoid lis3lv02d_get_xyz(struct lis3lv02d *lis3, int *x, int *y, int *z)
{ int position[3]; int i;
if (lis3->blkread) { if (lis3->whoami == WAI_12B) {
u16 data[3];
lis3->blkread(lis3, OUTX_L, 6, (u8 *)data); for (i = 0; i < 3; i++)
position[i] = (s16)le16_to_cpu(data[i]);
} else {
u8 data[5]; /* Data: x, dummy, y, dummy, z */
lis3->blkread(lis3, OUTX, 5, data); for (i = 0; i < 3; i++)
position[i] = (s8)data[i * 2];
}
} else {
position[0] = lis3->read_data(lis3, OUTX);
position[1] = lis3->read_data(lis3, OUTY);
position[2] = lis3->read_data(lis3, OUTZ);
}
for (i = 0; i < 3; i++)
position[i] = (position[i] * lis3->scale) / LIS3_ACCURACY;
lis3->read(lis3, ctlreg, ®);
lis3->write(lis3, ctlreg, (reg | selftest));
ret = lis3lv02d_get_pwron_wait(lis3); if (ret) goto fail;
/* Read directly to avoid axis remap */
x = lis3->read_data(lis3, OUTX);
y = lis3->read_data(lis3, OUTY);
z = lis3->read_data(lis3, OUTZ);
/* back to normal settings */
lis3->write(lis3, ctlreg, reg);
ret = lis3lv02d_get_pwron_wait(lis3); if (ret) goto fail;
results[0] = x - lis3->read_data(lis3, OUTX);
results[1] = y - lis3->read_data(lis3, OUTY);
results[2] = z - lis3->read_data(lis3, OUTZ);
ret = 0;
if (lis3->whoami == WAI_8B) { /* Restore original interrupt configuration */
atomic_dec(&lis3->wake_thread);
lis3->write(lis3, CTRL_REG3, ctrl_reg_data);
lis3->irq_cfg = irq_cfg;
if ((irq_cfg & LIS3_IRQ1_MASK) &&
lis3->data_ready_count[IRQ_LINE0] < 2) {
ret = SELFTEST_IRQ; goto fail;
}
if ((irq_cfg & LIS3_IRQ2_MASK) &&
lis3->data_ready_count[IRQ_LINE1] < 2) {
ret = SELFTEST_IRQ; goto fail;
}
}
if (lis3->pdata) { int i; for (i = 0; i < 3; i++) { /* Check against selftest acceptance limits */ if ((results[i] < lis3->pdata->st_min_limits[i]) ||
(results[i] > lis3->pdata->st_max_limits[i])) {
ret = SELFTEST_FAIL; goto fail;
}
}
}
/* test passed */
fail:
mutex_unlock(&lis3->mutex); return ret;
}
/* * Order of registers in the list affects to order of the restore process. * Perhaps it is a good idea to set interrupt enable register as a last one * after all other configurations
*/ static u8 lis3_wai8_regs[] = { FF_WU_CFG_1, FF_WU_THS_1, FF_WU_DURATION_1,
FF_WU_CFG_2, FF_WU_THS_2, FF_WU_DURATION_2,
CLICK_CFG, CLICK_SRC, CLICK_THSY_X, CLICK_THSZ,
CLICK_TIMELIMIT, CLICK_LATENCY, CLICK_WINDOW,
CTRL_REG1, CTRL_REG2, CTRL_REG3};
staticinlinevoid lis3_context_save(struct lis3lv02d *lis3)
{ int i; for (i = 0; i < lis3->regs_size; i++)
lis3->read(lis3, lis3->regs[i], &lis3->reg_cache[i]);
lis3->regs_stored = true;
}
staticinlinevoid lis3_context_restore(struct lis3lv02d *lis3)
{ int i; if (lis3->regs_stored) for (i = 0; i < lis3->regs_size; i++)
lis3->write(lis3, lis3->regs[i], lis3->reg_cache[i]);
}
void lis3lv02d_poweroff(struct lis3lv02d *lis3)
{ if (lis3->reg_ctrl)
lis3_context_save(lis3); /* disable X,Y,Z axis and power down */
lis3->write(lis3, CTRL_REG1, 0x00); if (lis3->reg_ctrl)
lis3->reg_ctrl(lis3, LIS3_REG_OFF);
}
EXPORT_SYMBOL_GPL(lis3lv02d_poweroff);
int lis3lv02d_poweron(struct lis3lv02d *lis3)
{ int err;
u8 reg;
lis3->init(lis3);
/* * Common configuration * BDU: (12 bits sensors only) LSB and MSB values are not updated until * both have been read. So the value read will always be correct. * Set BOOT bit to refresh factory tuning values.
*/ if (lis3->pdata) {
lis3->read(lis3, CTRL_REG2, ®); if (lis3->whoami == WAI_12B)
reg |= CTRL2_BDU | CTRL2_BOOT; elseif (lis3->whoami == WAI_3DLH)
reg |= CTRL2_BOOT_3DLH; else
reg |= CTRL2_BOOT_8B;
lis3->write(lis3, CTRL_REG2, reg);
if (lis3->pm_dev)
pm_runtime_get_sync(lis3->pm_dev);
if (lis3->pdata && lis3->whoami == WAI_8B && lis3->idev)
atomic_set(&lis3->wake_thread, 1); /* * Update coordinates for the case where poll interval is 0 and * the chip in running purely under interrupt control
*/
lis3lv02d_joystick_poll(input);
/* * Be careful: on some HP laptops the bios force DD when on battery and * the lid is closed. This leads to interrupts as soon as a little move * is done.
*/
atomic_inc(&lis3->count);
/* make sure we are not going into copy_to_user() with
* TASK_INTERRUPTIBLE state */
set_current_state(TASK_RUNNING); if (copy_to_user(buf, &byte_data, sizeof(byte_data)))
retval = -EFAULT;
int lis3lv02d_joystick_enable(struct lis3lv02d *lis3)
{ struct input_dev *input_dev; int err; int max_val, fuzz, flat; int btns[] = {BTN_X, BTN_Y, BTN_Z};
if (lis3->idev) return -EINVAL;
input_dev = input_allocate_device(); if (!input_dev) return -ENOMEM;
void lis3lv02d_joystick_disable(struct lis3lv02d *lis3)
{ if (lis3->irq)
free_irq(lis3->irq, lis3); if (lis3->pdata && lis3->pdata->irq2)
free_irq(lis3->pdata->irq2, lis3);
if (!lis3->idev) return;
if (lis3->irq)
misc_deregister(&lis3->miscdev);
input_unregister_device(lis3->idev);
lis3->idev = NULL;
}
EXPORT_SYMBOL_GPL(lis3lv02d_joystick_disable);
/* Sysfs stuff */ staticvoid lis3lv02d_sysfs_poweron(struct lis3lv02d *lis3)
{ /* * SYSFS functions are fast visitors so put-call * immediately after the get-call. However, keep * chip running for a while and schedule delayed * suspend. This way periodic sysfs calls doesn't * suffer from relatively long power up time.
*/
if (lis3->pm_dev) {
pm_runtime_get_sync(lis3->pm_dev);
pm_runtime_put_noidle(lis3->pm_dev);
pm_schedule_suspend(lis3->pm_dev, LIS3_SYSFS_POWERDOWN_DELAY);
}
}
/* * Initialise the accelerometer and the various subsystems. * Should be rather independent of the bus system.
*/ int lis3lv02d_init_device(struct lis3lv02d *lis3)
{ int err;
irq_handler_t thread_fn; int irq_flags = 0;
if (lis3->pm_dev) {
pm_runtime_set_active(lis3->pm_dev);
pm_runtime_enable(lis3->pm_dev);
}
if (lis3lv02d_joystick_enable(lis3))
pr_err("joystick initialization failed\n");
/* passing in platform specific data is purely optional and only
* used by the SPI transport layer at the moment */ if (lis3->pdata) { struct lis3lv02d_platform_data *p = lis3->pdata;
if (lis3->whoami == WAI_8B)
lis3lv02d_8b_configure(lis3, p);
irq_flags = p->irq_flags1 & IRQF_TRIGGER_MASK;
lis3->irq_cfg = p->irq_cfg; if (p->irq_cfg)
lis3->write(lis3, CTRL_REG3, p->irq_cfg);
if (p->default_rate)
lis3lv02d_set_odr(lis3, p->default_rate);
}
/* bail if we did not get an IRQ from the bus layer */ if (!lis3->irq) {
pr_debug("No IRQ. Disabling /dev/freefall\n"); goto out;
}
/* * The sensor can generate interrupts for free-fall and direction * detection (distinguishable with FF_WU_SRC and DD_SRC) but to keep * the things simple and _fast_ we activate it only for free-fall, so * no need to read register (very slow with ACPI). For the same reason, * we forbid shared interrupts. * * IRQF_TRIGGER_RISING seems pointless on HP laptops because the * io-apic is not configurable (and generates a warning) but I keep it * in case of support for other hardware.
*/ if (lis3->pdata && lis3->whoami == WAI_8B)
thread_fn = lis302dl_interrupt_thread1_8b; else
thread_fn = NULL;
if (misc_register(&lis3->miscdev))
pr_err("misc_register failed\n");
out: return 0;
}
EXPORT_SYMBOL_GPL(lis3lv02d_init_device);
MODULE_DESCRIPTION("ST LIS3LV02Dx three-axis digital accelerometer driver");
MODULE_AUTHOR("Yan Burman, Eric Piel, Pavel Machek");
MODULE_LICENSE("GPL");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.