HelenOS sources

root/uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. main
  2. s3c24xx_ts_init
  3. s3c24xx_ts_wait_for_int_mode
  4. s3c24xx_ts_irq_handler
  5. s3c24xx_ts_pen_down
  6. s3c24xx_ts_pen_up
  7. s3c24xx_ts_eoc
  8. s3c24xx_ts_convert_samples
  9. lin_map_range
  10. s3c24xx_ts_connection

/*
 * Copyright (c) 2023 Jiri Svoboda
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup uspace_srv_s3c24xx_ts
 * @{
 */
/**
 * @file
 * @brief Samsung S3C24xx on-chip ADC and touch-screen interface driver.
 *
 * This interface is present on the Samsung S3C24xx CPU (on the gta02 platform).
 */

#include <ddi.h>
#include <loc.h>
#include <vfs/vfs.h>
#include <ipc/mouseev.h>
#include <async.h>
#include <stdio.h>
#include <stdlib.h>
#include <sysinfo.h>
#include <errno.h>
#include <inttypes.h>
#include "s3c24xx_ts.h"

#define NAME       "s3c24xx_ts"
#define NAMESPACE  "hid"

static irq_cmd_t ts_irq_cmds[] = {
        {
                .cmd = CMD_ACCEPT
        }
};

static irq_code_t ts_irq_code = {
        0,
        NULL,
        sizeof(ts_irq_cmds) / sizeof(irq_cmd_t),
        ts_irq_cmds
};

/** S3C24xx touchscreen instance structure */
static s3c24xx_ts_t *ts;

static void s3c24xx_ts_connection(ipc_call_t *, void *);
static void s3c24xx_ts_irq_handler(ipc_call_t *, void *);
static void s3c24xx_ts_pen_down(s3c24xx_ts_t *);
static void s3c24xx_ts_pen_up(s3c24xx_ts_t *);
static void s3c24xx_ts_eoc(s3c24xx_ts_t *);
static int s3c24xx_ts_init(s3c24xx_ts_t *);
static void s3c24xx_ts_wait_for_int_mode(s3c24xx_ts_t *, ts_updn_t);
static void s3c24xx_ts_convert_samples(int, int, int *, int *);
static int lin_map_range(int, int, int, int, int);

int main(int argc, char *argv[])
{
        loc_srv_t *srv;

        printf("%s: S3C24xx touchscreen driver\n", NAME);

        async_set_fallback_port_handler(s3c24xx_ts_connection, NULL);
        errno_t rc = loc_server_register(NAME, &srv);
        if (rc != EOK) {
                printf("%s: Unable to register driver.\n", NAME);
                return rc;
        }

        ts = malloc(sizeof(s3c24xx_ts_t));
        if (ts == NULL) {
                loc_server_unregister(srv);
                return -1;
        }

        if (s3c24xx_ts_init(ts) != EOK) {
                free(ts);
                loc_server_unregister(srv);
                return -1;
        }

        rc = loc_service_register(srv, NAMESPACE "/mouse", &ts->service_id);
        if (rc != EOK) {
                // XXX s3c24xx_ts_fini();
                free(ts);
                loc_server_unregister(srv);
                printf(NAME ": Unable to register device %s.\n",
                    NAMESPACE "/mouse");
                return -1;
        }

        printf(NAME ": Registered device %s.\n", NAMESPACE "/mouse");

        printf(NAME ": Accepting connections\n");
        task_retval(0);
        async_manager();

        /* Not reached */
        return 0;
}

/** Initialize S3C24xx touchscreen interface. */
static int s3c24xx_ts_init(s3c24xx_ts_t *ts)
{
        void *vaddr;
        sysarg_t inr;

        inr = S3C24XX_TS_INR;
        ts->paddr = S3C24XX_TS_ADDR;

        if (pio_enable((void *) ts->paddr, sizeof(s3c24xx_adc_io_t),
            &vaddr) != 0)
                return -1;

        ts->io = vaddr;
        ts->client_sess = NULL;
        ts->state = ts_wait_pendown;
        ts->last_x = 0;
        ts->last_y = 0;

        printf(NAME ": device at physical address %p, inr %" PRIun ".\n",
            (void *) ts->paddr, inr);

        async_irq_subscribe(inr, s3c24xx_ts_irq_handler, NULL, &ts_irq_code, NULL);

        s3c24xx_ts_wait_for_int_mode(ts, updn_down);

        return EOK;
}

/** Switch interface to wait for interrupt mode.
 *
 * In this mode we receive an interrupt when pen goes up/down, depending
 * on @a updn.
 *
 * @param ts    Touchscreen instance
 * @param updn  @c updn_up to wait for pen up, @c updn_down to wait for pen
 *              down.
 */
static void s3c24xx_ts_wait_for_int_mode(s3c24xx_ts_t *ts, ts_updn_t updn)
{
        uint32_t con, tsc;

        /*
         * Configure ADCCON register
         */

        con = pio_read_32(&ts->io->con);

        /* Disable standby, disable start-by-read, clear manual start bit */
        con = con & ~(ADCCON_STDBM | ADCCON_READ_START | ADCCON_ENABLE_START);

        /* Set prescaler value 0xff, XP for input. */
        con = con | (ADCCON_PRSCVL(0xff) << 6) | ADCCON_SEL_MUX(SMUX_XP);

        /* Enable prescaler. */
        con = con | ADCCON_PRSCEN;

        pio_write_32(&ts->io->con, con);

        /*
         * Configure ADCTSC register
         */

        tsc = pio_read_32(&ts->io->tsc);

        /* Select whether waiting for pen up or pen down. */
        if (updn == updn_up)
                tsc |= ADCTSC_DSUD_UP;
        else
                tsc &= ~ADCTSC_DSUD_UP;

        /*
         * Enable XP pull-up and disable all drivers except YM. This is
         * according to the manual. This gives us L on XP input when touching
         * and (pulled up to) H when not touching.
         */
        tsc = tsc & ~(ADCTSC_XM_ENABLE | ADCTSC_AUTO_PST |
            ADCTSC_PULLUP_DISABLE);
        tsc = tsc | ADCTSC_YP_DISABLE | ADCTSC_XP_DISABLE | ADCTSC_YM_ENABLE;

        /* Select wait-for-interrupt mode. */
        tsc = (tsc & ~ADCTSC_XY_PST_MASK) | ADCTSC_XY_PST_WAITINT;

        pio_write_32(&ts->io->tsc, tsc);
}

/** Handle touchscreen interrupt */
static void s3c24xx_ts_irq_handler(ipc_call_t *call, void *arg)
{
        ts_updn_t updn;

        (void) call;

        /* Read up/down interrupt flags. */
        updn = pio_read_32(&ts->io->updn);

        if (updn & (ADCUPDN_TSC_DN | ADCUPDN_TSC_UP)) {
                /* Clear up/down interrupt flags. */
                pio_write_32(&ts->io->updn, updn &
                    ~(ADCUPDN_TSC_DN | ADCUPDN_TSC_UP));
        }

        if (updn & ADCUPDN_TSC_DN) {
                /* Pen-down interrupt */
                s3c24xx_ts_pen_down(ts);
        } else if (updn & ADCUPDN_TSC_UP) {
                /* Pen-up interrupt */
                s3c24xx_ts_pen_up(ts);
        } else {
                /* Presumably end-of-conversion interrupt */

                /* Check end-of-conversion flag. */
                if ((pio_read_32(&ts->io->con) & ADCCON_ECFLG) == 0) {
                        printf(NAME ": Unrecognized ts int.\n");
                        return;
                }

                if (ts->state != ts_sample_pos) {
                        /*
                         * We got an extra interrupt ater switching to
                         * wait for interrupt mode.
                         */
                        return;
                }

                /* End-of-conversion interrupt */
                s3c24xx_ts_eoc(ts);
        }
}

/** Handle pen-down interrupt.
 *
 * @param ts    Touchscreen instance
 */
static void s3c24xx_ts_pen_down(s3c24xx_ts_t *ts)
{
        /* Pen-down interrupt */

        ts->state = ts_sample_pos;

        /* Enable auto xy-conversion mode */
        pio_write_32(&ts->io->tsc, (pio_read_32(&ts->io->tsc) & ~3) | 4);

        /* Start the conversion. */
        pio_write_32(&ts->io->con, pio_read_32(&ts->io->con) | ADCCON_ENABLE_START);
}

/** Handle pen-up interrupt.
 *
 * @param ts    Touchscreen instance
 */
static void s3c24xx_ts_pen_up(s3c24xx_ts_t *ts)
{
        int button, press;

        /* Pen-up interrupt */

        ts->state = ts_wait_pendown;

        button = 1;
        press = 0;

        async_exch_t *exch = async_exchange_begin(ts->client_sess);
        async_msg_2(exch, MOUSEEV_BUTTON_EVENT, button, press);
        async_exchange_end(exch);

        s3c24xx_ts_wait_for_int_mode(ts, updn_down);
}

/** Handle end-of-conversion interrupt.
 *
 * @param ts    Touchscreen instance
 */
static void s3c24xx_ts_eoc(s3c24xx_ts_t *ts)
{
        uint32_t data;
        int button, press;
        int smp0, smp1;
        int x_pos, y_pos;
        int dx, dy;

        ts->state = ts_wait_penup;

        /* Read in sampled data. */

        data = pio_read_32(&ts->io->dat0);
        smp0 = data & 0x3ff;

        data = pio_read_32(&ts->io->dat1);
        smp1 = data & 0x3ff;

        /* Convert to screen coordinates. */
        s3c24xx_ts_convert_samples(smp0, smp1, &x_pos, &y_pos);

        printf("s0: 0x%03x, s1:0x%03x -> x:%d,y:%d\n", smp0, smp1,
            x_pos, y_pos);

        /* Get differences. */
        dx = x_pos - ts->last_x;
        dy = y_pos - ts->last_y;

        button = 1;
        press = 1;

        /* Send notifications to client. */
        async_exch_t *exch = async_exchange_begin(ts->client_sess);
        async_msg_2(exch, MOUSEEV_MOVE_EVENT, dx, dy);
        async_msg_2(exch, MOUSEEV_BUTTON_EVENT, button, press);
        async_exchange_end(exch);

        ts->last_x = x_pos;
        ts->last_y = y_pos;

        s3c24xx_ts_wait_for_int_mode(ts, updn_up);
}

/** Convert sampled data to screen coordinates. */
static void s3c24xx_ts_convert_samples(int smp0, int smp1, int *x, int *y)
{
        /*
         * The orientation and display dimensions are GTA02-specific and the
         * calibration values might even specific to the individual piece
         * of hardware.
         *
         * The calibration values can be obtained by touching corners
         * of the screen with the stylus and noting the sampled values.
         */
        *x = lin_map_range(smp1, 0xa1, 0x396, 0, 479);
        *y = lin_map_range(smp0, 0x69, 0x38a, 639, 0);
}

/** Map integer from one range to another range in a linear fashion.
 *
 * i0 < i1 is required. i0 is mapped to o0, i1 to o1. If o1 < o0, then the
 * mapping will be descending. If v is outside of [i0, i1], it is clamped.
 *
 * @param v     Value to map.
 * @param i0    Lower bound of input range.
 * @param i1    Upper bound of input range.
 * @param o0    First bound of output range.
 * @param o1    Second bound of output range.
 *
 * @return      Mapped value ov, o0 <= ov <= o1.
 */
static int lin_map_range(int v, int i0, int i1, int o0, int o1)
{
        if (v < i0)
                v = i0;

        if (v > i1)
                v = i1;

        return o0 + (o1 - o0) * (v - i0) / (i1 - i0);
}

/** Handle mouse client connection. */
static void s3c24xx_ts_connection(ipc_call_t *icall, void *arg)
{
        async_accept_0(icall);

        while (true) {
                ipc_call_t call;
                async_get_call(&call);

                if (!ipc_get_imethod(&call)) {
                        if (ts->client_sess != NULL) {
                                async_hangup(ts->client_sess);
                                ts->client_sess = NULL;
                        }

                        async_answer_0(&call, EOK);
                        return;
                }

                async_sess_t *sess =
                    async_callback_receive_start(EXCHANGE_SERIALIZE, &call);
                if (sess != NULL) {
                        if (ts->client_sess == NULL) {
                                ts->client_sess = sess;
                                async_answer_0(&call, EOK);
                        } else
                                async_answer_0(&call, ELIMIT);
                } else
                        async_answer_0(&call, EINVAL);
        }
}

/** @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub