/*
 * Touch Panel Driver for TechToys LCD
 *    using TI ADS7846 controller
 */
#include <t_services.h>
#include <sil.h>
#include "cqnxparm.h"
#include "kernel_id.h"
#include "touch.h"
#include "lcd.h"

/*** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 * Range of touch screen area is currently hard coded as below.
 * It should be user configuratble.
 *** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 */
#define X_MAX   1848
#define X_MIN   180
#define Y_MIN   230
#define Y_MAX   1897
#define LCD_OFFSET  0

#define PENIRQ  (1<<8)  /* PENIRQ is connected to P2[8] */
#define	MEASURE_DELAY	30
#define	N_SAMPLES	5
#define	ADC_MAXVAL	2047

/*
 *  Bit7 Bit6 Bit5 Bit4 Bit3   Bit2   Bit1 Bit0
 *   S    A2   A1   A0  MODE SER/nDFR  PD1  PD0
 */
#define MODE_12     0x00
#define MODE_8      0x08
/*
 *  Input Configuration (DIN), Single-Ended Reference Mode (SER/nDFR = high)
 */
#define DIN_SER_TEMP0       (0x80 | 0x00 | 0x04)
#define DIN_SER_MEASURE_Y   (0x80 | 0x10 | 0x04)
#define DIN_SER_MEASURE_VBAT    (0x80 | 0x20 | 0x04)
#define DIN_SER_MEASURE_Z1  (0x80 | 0x30 | 0x04)
#define DIN_SER_MEASURE_Z2  (0x80 | 0x40 | 0x04)
#define DIN_SER_MEASURE_X   (0x80 | 0x50 | 0x04)
#define DIN_SER_MEASURE_AUX (0x80 | 0x60 | 0x04)
#define DIN_SER_TEMP1       (0x80 | 0x70 | 0x04)

/*
 *  Input Configuration (DIN), Differential Reference Mode (SER/nDFR = low)
 */
#define DIN_DFR_MEASURE_Y   (0x80 | 0x10)
#define DIN_DFR_MEASURE_X   (0x80 | 0x50)
#define DIN_DFR_MEASURE_Z1  (0x80 | 0x30)
#define DIN_DFR_MEASURE_Z2  (0x80 | 0x40)

#define PD_PDOWN    0x00
#define PD_ADCON    0x01
#define PD_REFON    0x02
#define PD_ALLON    0x03

/*
 * LCD module has five menu botton on the touch panel.
 * Note that these buttons are out side of LCD visible area.
 * In below definition, we defines button's X cordinate and
 * width, but no height or Y cordinate.
 */
struct but_def {
 UB xbegin, xend;
 H  button;
} button_list[] = {
 {  10,  50, BUT_HOME },
 {  55,  95, BUT_MAIL },
 { 105, 145, BUT_NOTE },
 { 150, 190, BUT_PHONE },
 { 200, 240, BUT_MUSIC }
};

void spi_init(void)
{
    SSP0CR0 = (0x07 << 0) | /* DSS: 8bit transfer */
        (0x00 << 4) |   /* FRF: SPI */
        (0x1 << 6) |    /* CPOL: 1 */
        (0x1 << 7) |    /* CPHA: 1 */
        (18 << 8);  /* SCR */

    SSP0CPSR = 2;
    SSP0CR1 = (0x1 << 1) | (0x0 << 2);  /* Master mode and enable SSP0 */
    ena_int(INTNO_SSP0);
    ena_int(INTNO_EINT3);
}

/*
 * SSP0 interrupt handler
 */
void spi_handler()
{
    if (SSP0MIS & (1<<1)) {         /* RTIM interrupt */
        SSP0IMSC &= ~(1<<1);        /* Disable RTIM interrupt */
        isig_sem(SEM_SSP0);
    }
}

#ifdef TEST_CODE
static UB touch_results[4];
static UB touch_param[] = {
    DIN_SER_TEMP0,          // 0
    DIN_SER_MEASURE_Y,      // 1
    DIN_SER_MEASURE_VBAT,   // 2
    DIN_SER_MEASURE_Z1,     // 3
    DIN_SER_MEASURE_Z2,     // 4
    DIN_SER_MEASURE_X,      // 5
    DIN_SER_MEASURE_AUX,    // 6
    DIN_SER_TEMP1,          // 7
    DIN_DFR_MEASURE_Y,      // 8
    DIN_DFR_MEASURE_X,      // 9
    DIN_DFR_MEASURE_Z1,     // 10
    DIN_DFR_MEASURE_Z2,     // 11
};
#endif

#define SSP_TFE (1<<0)
#define SSP_RNE (1<<2)
#define SSP_BSY (1<<4)
int send_touch_command(UB command)
{
    int val;
    UB results[3];

    /* Be sure that SSP0 is ready and FIFO is empty */
    while (SSP0SR & SSP_BSY)
        ;
    while (SSP0SR & SSP_RNE)
        val = SSP0DR;
    loc_cpu();
    SSP0DR = command;
    SSP0DR = 0xff;
    SSP0DR = 0xff;
    SSP0IMSC |= (1<<1);         /* Enable RTIM interrupt */
    unl_cpu();
    wai_sem(SEM_SSP0);
    results[0] = SSP0DR;
    results[1] = SSP0DR;
    results[2] = SSP0DR;

    val = (results[1] << 4) | (results[2] >> 4);
    return val;
}

void ads7846_handler(void)
{
    /* Disable further interrupt, then wake up the task */
    if (IO2_INT_STAT_F & PENIRQ) {
        IO2_INT_EN_F = 0;
        IO2_INT_CLR = PENIRQ;
        iwup_tsk(TOUCH_TASK);
    }
}

/*
 *  Get position cordinate from A/D converter value.
 *  A/D conversion value is not accurate immediate after
 *  the conversion started. We'll check few samples.
 */
static int measure_count, measure_called;

int do_measure(int cmd)
{
    int i, oval, val;

    oval = 0;
    measure_called++;
    for (i = 0; i < N_SAMPLES; i++) {
        val = send_touch_command(cmd);
        measure_count++;
        if (FIO2PIN & PENIRQ) {
            val = -1;
            break;
        }
        oval = oval - val;
        if (oval < 0) oval = -oval;
        if (oval < 30)	/* Hope that the value is stable, now */
            break;
        oval = val;
    }
    return val;
}

static int check_button(int xpos)
{
    struct but_def *pbd = button_list;
    int i;

    for (i = 0; i < sizeof(button_list)/sizeof(struct but_def); i++) {
        if (xpos >= pbd->xbegin && xpos <= pbd->xend)
            return pbd->button;
        pbd++;
    }
    return 0;
}

#define X_PLATE_OHMS    1200

#define RANGE_TEST
#ifdef RANGE_TEST
static int ymin, ymax, xmin, xmax;
#endif

void touch_task(void)
{
    int lastx, lasty;
    int xval, yval;
    int xorg, yorg;
#ifdef USE_Z_AXIS
    int z1, z2, Rt;
#endif
    UH button;
    struct touch_event *ev;

#ifdef RANGE_TEST
    ymin = xmin = 2000;
    ymax = xmax = 0;
#endif
    IO2_INT_CLR = PENIRQ;
    IO2_INT_EN_F = 0;
    IO2_INT_EN_R = 0;
    spi_init();

    dly_tsk(3000);

    for (;;) {
        /* Set power down mode and enable /PENIRQ */
        send_touch_command(DIN_DFR_MEASURE_X | PD_PDOWN);
        /* Enable /PENIRQ from touch panel on P2[8] */
        IO2_INT_CLR = PENIRQ;
        IO2_INT_EN_F = PENIRQ;

        /* Wait for /PENIRQ */
        slp_tsk();

        get_mpf(MPF_TOUCH, (VP *)&ev);
        ev->ev_type = EV_DOWN;
        ev->ev_xpos = ev->ev_ypos = 0;
        get_tim(&ev->ev_time);
        snd_dtq(DTQ_TOUCH, ev);

        do {
            xorg = do_measure(DIN_DFR_MEASURE_X | PD_ADCON);
            if (xorg >= 0) {
                yval = do_measure(DIN_DFR_MEASURE_Y | PD_ADCON);
#ifdef M_DEBUG
                syslog(LOG_NOTICE, "(%d, %d)", xorg, yval);
#endif
                if (yval < 0)
                        break;
                xval = xorg; yorg = yval;
#ifdef RANGE_TEST
                if (xval < xmin)
                  xmin = xval;
                if (xval > xmax)
                  xmax = xval;
                if (yval < ymin)
                  ymin = yval;
                if (yval > ymax)
                  ymax = yval;
#endif
                xval -= X_MIN;
                if (xval < 0) xval = 0;
                xval = (LCD_WIDTH * xval)/(X_MAX - X_MIN);
                xval = LCD_WIDTH - xval;
                yval -= Y_MIN;

                yval = (LCD_HEIGHT * yval)/(Y_MAX - Y_MIN);
                yval = LCD_HEIGHT + LCD_OFFSET - yval;
#ifdef USE_Z_AXIS
                z1 = do_measure(DIN_DFR_MEASURE_Z1 | PD_ADCON);
                z2 = do_measure(DIN_DFR_MEASURE_Z2 | PD_ADCON);
                if (z1) {
                    Rt = z2;
                    Rt -= z1;
                    Rt *= xorg;
                    Rt *= X_PLATE_OHMS;
                    Rt /= z1;
                    Rt = (Rt + ADC_MAXVAL) >> 12;
                    if (Rt < 0) Rt = X_PLATE_OHMS;
#ifdef DEBUG
cons_printf("(%d, %d, %d)\n", xorg, yval, Rt);
#endif
                } else {
                    Rt = 0;
                }
#endif

                get_mpf(MPF_TOUCH, (VP *)&ev);
                ev->ev_type = EV_MOVE;
                ev->ev_xpos = xval; ev->ev_ypos = yval;
#ifdef USE_Z_AXIS
                ev->ev_zpos = Rt;
#endif
                get_tim(&ev->ev_time);
                snd_dtq(DTQ_TOUCH, ev);
                dly_tsk(MEASURE_DELAY);
            }
        } while (xorg >= 0);
        get_mpf(MPF_TOUCH, (VP *)&ev);
        ev->ev_type = EV_UP;
        ev->ev_xpos = ev->ev_ypos = 0;
        get_tim(&ev->ev_time);
        snd_dtq(DTQ_TOUCH, ev);
    }
}

#ifdef DEBUG_COMMAND
void touch_command(int argc, char **argv)
{
#ifdef TEST_CODE
    int val = 0;
    int command;

    if (argc > 1) {
        val = atoi(argv[1]);
        if (val < 0 || val > 11) val = 0;
    }
    command = touch_param[val] | PD_ADCON;
    if (val < 8)
        command |= PD_REFON;
    command &= 0xff;
    /* Be sure that SSP0 is ready and FIFO is empty */
    while (SSP0SR & SSP_BSY)
        ;
    while (SSP0SR & SSP_RNE)
        val = SSP0DR;
    loc_cpu();
    SSP0DR = command;
    SSP0DR = 0xff;
    SSP0DR = 0xff;
    unl_cpu();
    while (SSP0SR & SSP_BSY)
        ;
    touch_results[0] = SSP0DR;
    touch_results[1] = SSP0DR;
    touch_results[2] = SSP0DR;

    cons_printf("%02x --> %02x %02x %02x\n", command,
        touch_results[0], touch_results[1], touch_results[2]);
    cons_printf("Val: %d\n", (touch_results[1] << 4) | (touch_results[2] >> 4));
#else
    cons_printf("xmin: %d, xmax: %d\n", xmin, xmax);
    cons_printf("ymin: %d, ymax: %d\n", ymin, ymax);
#endif
}
#else
void touch_command(int argc, char **argv)
{
#ifdef RANGE_TEST
    syslog(LOG_NOTICE, "xmin: %d, xmax: %d", xmin, xmax);
    syslog(LOG_NOTICE, "ymin: %d, ymax: %d", ymin, ymax);
#endif
    syslog(LOG_NOTICE, "measure: count = %d, called = %d", measure_count, measure_called);
}
#endif
