blob: a8b8ed75f5ba31e93e7dff821b1336812e166dd4 [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.
*/
/* Touch scanning module */
#include "common.h"
#include "console.h"
#include "cpu.h"
#include "debug.h"
#include "dma.h"
#include "encode.h"
#include "gpio.h"
#include "hooks.h"
#include "master_slave.h"
#include "registers.h"
#include "spi_comm.h"
#include "task.h"
#include "timer.h"
#include "touch_scan.h"
#include "util.h"
#define TS_PIN_TO_CR(p) ((((p).port_id + 1) << 16) | (1 << (p).pin))
#define TS_GPIO_TO_BASE(p) (0x40010800 + (p) * 0x400)
static uint8_t buf[2][ROW_COUNT * 2];
#ifdef CONFIG_KEYBORG_FAST_SCAN
#define SCAN_BUF_SIZE (DIV_ROUND_UP(COL_COUNT * 2, 32) + 2)
#define GET_SCAN_NEEDED(x) (scan_needed[(x) / 32 + 1] & (1 << ((x) & 31)))
static uint32_t scan_needed[SCAN_BUF_SIZE];
#else
#define GET_SCAN_NEEDED(x) 1
#endif
#define SPAN_LENGTH (2 * COL_SPAN + 1)
#define SPAN_MASK ((1 << SPAN_LENGTH) - 1)
static uint32_t mccr_list[COL_COUNT];
static uint32_t mrcr_list[ROW_COUNT];
static void set_gpio(const struct ts_pin pin, enum pin_type type)
{
uint32_t addr, mode, mask;
uint32_t port = TS_GPIO_TO_BASE(pin.port_id);
uint32_t pmask = 1 << pin.pin;
if (pmask & 0xff) {
addr = port;
mode = pmask;
} else {
addr = port + 0x04;
mode = pmask >> 8;
}
mode = mode * mode * mode * mode * 0xf;
mask = REG32(addr) & ~mode;
if (type == PIN_COL) {
/* Alternate output open-drain */
mask |= 0xdddddddd & mode;
} else if (type == PIN_PD) {
mask |= 0x88888888 & mode;
STM32_GPIO_BSRR(port) = pmask << 16;
} else if (type == PIN_Z) {
mask |= 0x44444444 & mode;
} else if (type == PIN_ROW) {
/* Nothing for PIN_ROW. Already analog input. */
}
REG32(addr) = mask;
}
void touch_scan_init(void)
{
int i;
for (i = 0; i < ROW_COUNT; ++i) {
set_gpio(row_pins[i], PIN_ROW);
STM32_PMSE_PxPMR(row_pins[i].port_id) |= 1 << row_pins[i].pin;
}
for (i = 0; i < COL_COUNT; ++i)
set_gpio(col_pins[i], PIN_PD);
for (i = 0; i < ROW_COUNT; ++i)
mrcr_list[i] = TS_PIN_TO_CR(row_pins[i]);
for (i = 0; i < COL_COUNT; ++i)
mccr_list[i] = TS_PIN_TO_CR(col_pins[i]);
}
void touch_scan_enable_interrupt(void)
{
int i;
/* Set ALLROW and ALLCOL */
for (i = 0; i < ROW_COUNT; ++i)
set_gpio(row_pins[i], PIN_Z);
for (i = 0; i < COL_COUNT; ++i) {
set_gpio(col_pins[i], PIN_COL);
STM32_PMSE_PxPMR(col_pins[i].port_id) |= 1 << col_pins[i].pin;
}
STM32_PMSE_MCCR = (1 << 31) | (0 << 20);
STM32_PMSE_MRCR = 1 << 31;
/* Enable external interrupt. EXTI3 on port E. Rising edge */
STM32_EXTI_RTSR |= 1 << 3;
STM32_AFIO_EXTICR(0) = (STM32_AFIO_EXTICR(0) & ~0xf000) | (4 << 12);
STM32_EXTI_IMR |= 1 << 3;
task_clear_pending_irq(STM32_IRQ_EXTI3);
task_enable_irq(STM32_IRQ_EXTI3);
}
void touch_scan_disable_interrupt(void)
{
int i;
for (i = 0; i < ROW_COUNT; ++i)
set_gpio(row_pins[i], PIN_ROW);
for (i = 0; i < COL_COUNT; ++i) {
set_gpio(col_pins[i], PIN_PD);
STM32_PMSE_PxPMR(col_pins[i].port_id) &=
~(1 << col_pins[i].pin);
}
}
void touch_scan_interrupt(void)
{
STM32_EXTI_PR = STM32_EXTI_PR;
}
DECLARE_IRQ(STM32_IRQ_EXTI3, touch_scan_interrupt, 1);
static void discharge(void)
{
int i;
/*
* The value 20 is deducted from experiments.
* Somehow this needs to be in reverse order
*/
for (i = 20; i >= 0; --i)
STM32_PMSE_MRCR = mrcr_list[i];
}
static void start_adc_sample(int id, int wait_cycle)
{
/* Clear EOC and STRT bit */
STM32_ADC_SR(id) &= ~((1 << 1) | (1 << 4));
/* Start conversion */
STM32_ADC_CR2(id) |= (1 << 0);
/* Wait for conversion start */
while (!(STM32_ADC_SR(id) & (1 << 4)))
;
/* Each iteration takes 3 CPU cycles */
asm("1: subs %0, #1\n"
" bne 1b\n" :: "r"(wait_cycle / 3));
}
static inline uint8_t flush_adc(int id)
{
uint16_t v;
#if ADC_SMPL_CYCLE_2 < ADC_QUNTZ_CYCLE_2
while (!(STM32_ADC_SR(id) & (1 << 1)))
;
#endif
v = STM32_ADC_DR(id) >> ADC_WINDOW_POS;
if (v > 255)
v = 255;
return v;
}
static void enable_col(int idx, int enabled)
{
if (enabled) {
set_gpio(col_pins[idx], PIN_COL);
STM32_PMSE_PxPMR(col_pins[idx].port_id) |=
1 << col_pins[idx].pin;
} else {
set_gpio(col_pins[idx], PIN_PD);
STM32_PMSE_PxPMR(col_pins[idx].port_id) &=
~(1 << col_pins[idx].pin);
}
}
#ifdef CONFIG_KEYBORG_FAST_SCAN
static inline void set_scan_needed(int col)
{
uint8_t word = (col - COL_SPAN + 32) / 32;
uint8_t bit = (col - COL_SPAN + 32) & 31;
scan_needed[word] |= SPAN_MASK << bit;
if (bit + SPAN_LENGTH > 32)
scan_needed[word + 1] |= SPAN_MASK >> (32 - bit);
}
int fast_scan(uint32_t *data)
{
int col;
int chip_col = 0;
memset(data, 0, SCAN_BUF_SIZE * 4);
STM32_PMSE_MRCR = 1 << 31;
for (col = 0; col < COL_COUNT * 2; ++col) {
if (master_slave_is_master())
chip_col = (col >= COL_COUNT) ? col - COL_COUNT : -1;
else
chip_col = (col < COL_COUNT) ? col : -1;
if (chip_col >= 0) {
enable_col(chip_col, 1);
STM32_PMSE_MCCR = mccr_list[chip_col];
}
if (master_slave_sync(5) != EC_SUCCESS)
goto fast_scan_err;
start_adc_sample(0, ADC_SMPL_CPU_CYCLE);
while (!(STM32_ADC_SR(0) & (1 << 1)))
;
if (flush_adc(0) >= COL_THRESHOLD)
set_scan_needed(col);
if (master_slave_sync(5) != EC_SUCCESS)
goto fast_scan_err;
if (chip_col >= 0) {
enable_col(chip_col, 0);
STM32_PMSE_MCCR = 0;
}
}
STM32_PMSE_MRCR = 0;
/* Discharge the panel */
discharge();
return EC_SUCCESS;
fast_scan_err:
enable_col(chip_col, 0);
STM32_PMSE_MCCR = 0;
STM32_PMSE_MRCR = 0;
return EC_ERROR_UNKNOWN;
}
#else
#define fast_scan(x) EC_SUCCESS
#endif
void scan_column(uint8_t *data)
{
int i;
STM32_PMSE_MRCR = mrcr_list[0];
start_adc_sample(0, ADC_SMPL_CPU_CYCLE);
STM32_PMSE_MRCR = mrcr_list[1];
start_adc_sample(1, ADC_SMPL_CPU_CYCLE);
for (i = 2; i < ROW_COUNT; ++i) {
data[i - 2] = flush_adc(i & 1);
STM32_PMSE_MRCR = mrcr_list[i];
start_adc_sample(i & 1, ADC_SMPL_CPU_CYCLE);
}
while (!(STM32_ADC_SR(ROW_COUNT & 1) & (1 << 1)))
;
data[ROW_COUNT - 2] = flush_adc(ROW_COUNT & 1);
while (!(STM32_ADC_SR((ROW_COUNT & 1) ^ 1) & (1 << 1)))
;
data[ROW_COUNT - 1] = flush_adc((ROW_COUNT & 1) ^ 1);
}
void touch_scan_slave_start(void)
{
int col = 0, i, v;
struct spi_comm_packet *resp = (struct spi_comm_packet *)buf;
if (fast_scan(scan_needed) != EC_SUCCESS)
goto slave_err;
for (col = 0; col < COL_COUNT * 2; ++col) {
if (col < COL_COUNT) {
enable_col(col, 1);
STM32_PMSE_MCCR = mccr_list[col];
}
if (master_slave_sync(20) != EC_SUCCESS)
goto slave_err;
if (GET_SCAN_NEEDED(col)) {
scan_column(resp->data);
/* Reverse the scanned data */
for (i = 0; ROW_COUNT - 1 - i > i; ++i) {
v = resp->data[i];
resp->data[i] = resp->data[ROW_COUNT - 1 - i];
resp->data[ROW_COUNT - 1 - i] = v;
}
resp->size = ROW_COUNT;
} else {
resp->size = 0;
}
resp->cmd_sts = EC_SUCCESS;
/* Flush the last response */
if (col != 0)
if (spi_slave_send_response_flush() != EC_SUCCESS)
goto slave_err;
/* Start sending the response for the current column */
if (spi_slave_send_response_async(resp) != EC_SUCCESS)
goto slave_err;
/* Disable the current column and discharge */
if (col < COL_COUNT) {
enable_col(col, 0);
STM32_PMSE_MCCR = 0;
}
discharge();
}
spi_slave_send_response_flush();
master_slave_sync(20);
return;
slave_err:
if (col < COL_COUNT)
enable_col(col, 0);
STM32_PMSE_MCCR = 0;
spi_slave_send_response_flush();
}
int touch_scan_full_matrix(void)
{
struct spi_comm_packet cmd;
const struct spi_comm_packet *resp;
int col = 0;
timestamp_t st = get_time();
uint8_t *dptr = NULL, *last_dptr = NULL;
cmd.cmd_sts = TS_CMD_FULL_SCAN;
cmd.size = 0;
if (spi_master_send_command(&cmd))
goto master_err;
encode_reset();
if (fast_scan(scan_needed) != EC_SUCCESS)
goto master_err;
for (col = 0; col < COL_COUNT * 2; ++col) {
if (col >= COL_COUNT) {
enable_col(col - COL_COUNT, 1);
STM32_PMSE_MCCR = mccr_list[col - COL_COUNT];
}
if (master_slave_sync(20) != EC_SUCCESS)
goto master_err;
last_dptr = dptr;
dptr = buf[col & 1];
if (GET_SCAN_NEEDED(col))
scan_column(dptr + ROW_COUNT);
else
memset(dptr + ROW_COUNT, 0, ROW_COUNT);
if (col > 0) {
/* Flush the data from the slave for the last column */
resp = spi_master_wait_response_done();
if (resp == NULL)
goto master_err;
if (resp->size)
memcpy(last_dptr, resp->data, ROW_COUNT);
else
memset(last_dptr, 0, ROW_COUNT);
encode_add_column(last_dptr);
}
/* Start receiving data for the current column */
if (spi_master_wait_response_async() != EC_SUCCESS)
goto master_err;
/* Disable the current column and discharge */
if (col >= COL_COUNT) {
enable_col(col - COL_COUNT, 0);
STM32_PMSE_MCCR = 0;
}
discharge();
}
resp = spi_master_wait_response_done();
if (resp == NULL)
goto master_err;
if (resp->size)
memcpy(last_dptr, resp->data, ROW_COUNT);
else
memset(last_dptr, 0, ROW_COUNT);
encode_add_column(last_dptr);
master_slave_sync(20);
debug_printf("Sampling took %d us\n", get_time().val - st.val);
encode_dump_matrix();
return EC_SUCCESS;
master_err:
spi_master_wait_response_done();
if (col >= COL_COUNT)
enable_col(col - COL_COUNT, 0);
STM32_PMSE_MCCR = 0;
return EC_ERROR_UNKNOWN;
}