HelenOS sources

root/uspace/drv/char/pl050/pl050.c

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

DEFINITIONS

This source file includes following definitions.
  1. pl050_from_fun
  2. pl050_interrupt
  3. pl050_init
  4. pl050_read
  5. pl050_write
  6. pl050_char_conn
  7. pl050_dev_add
  8. pl050_fun_online
  9. pl050_fun_offline
  10. main

/*
 * Copyright (c) 2009 Vineeth Pillai
 * Copyright (c) 2014 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 pl050
 * @{
 */

/** @file
 */

#include <assert.h>
#include <bitops.h>
#include <stdio.h>
#include <errno.h>
#include <str_error.h>
#include <ddf/driver.h>
#include <ddf/interrupt.h>
#include <ddf/log.h>
#include <device/hw_res.h>
#include <device/hw_res_parsed.h>
#include <io/chardev_srv.h>

#include "pl050_hw.h"

#define NAME "pl050"

enum {
        buffer_size = 64
};

static errno_t pl050_dev_add(ddf_dev_t *);
static errno_t pl050_fun_online(ddf_fun_t *);
static errno_t pl050_fun_offline(ddf_fun_t *);
static void pl050_char_conn(ipc_call_t *, void *);
static errno_t pl050_read(chardev_srv_t *, void *, size_t, size_t *,
    chardev_flags_t);
static errno_t pl050_write(chardev_srv_t *, const void *, size_t, size_t *);

static driver_ops_t driver_ops = {
        .dev_add = &pl050_dev_add,
        .fun_online = &pl050_fun_online,
        .fun_offline = &pl050_fun_offline
};

static driver_t pl050_driver = {
        .name = NAME,
        .driver_ops = &driver_ops
};

static chardev_ops_t pl050_chardev_ops = {
        .read = pl050_read,
        .write = pl050_write
};

typedef struct {
        async_sess_t *parent_sess;
        ddf_dev_t *dev;
        char *name;

        ddf_fun_t *fun_a;
        chardev_srvs_t cds;

        uintptr_t iobase;
        size_t iosize;
        kmi_regs_t *regs;
        uint8_t buffer[buffer_size];
        size_t buf_rp;
        size_t buf_wp;
        fibril_condvar_t buf_cv;
        fibril_mutex_t buf_lock;
} pl050_t;

static irq_pio_range_t pl050_ranges[] = {
        {
                .base = 0,
                .size = 9,
        }
};

static irq_cmd_t pl050_cmds[] = {
        {
                .cmd = CMD_PIO_READ_8,
                .addr = NULL,
                .dstarg = 1
        },
        {
                .cmd = CMD_AND,
                .value = BIT_V(uint8_t, kmi_stat_rxfull),
                .srcarg = 1,
                .dstarg = 3
        },
        {
                .cmd = CMD_PREDICATE,
                .value = 2,
                .srcarg = 3
        },
        {
                .cmd = CMD_PIO_READ_8,
                .addr = NULL,  /* Will be patched in run-time */
                .dstarg = 2
        },
        {
                .cmd = CMD_ACCEPT
        }
};

static irq_code_t pl050_irq_code = {
        sizeof(pl050_ranges) / sizeof(irq_pio_range_t),
        pl050_ranges,
        sizeof(pl050_cmds) / sizeof(irq_cmd_t),
        pl050_cmds
};

static pl050_t *pl050_from_fun(ddf_fun_t *fun)
{
        return (pl050_t *)ddf_dev_data_get(ddf_fun_get_dev(fun));
}

/** PL050 interrupt handler
 *
 * @param call IRQ event notification
 * @param arg Argument (pl050_t *)
 */
static void pl050_interrupt(ipc_call_t *call, void *arg)
{
        pl050_t *pl050 = (pl050_t *)arg;
        size_t nidx;

        fibril_mutex_lock(&pl050->buf_lock);
        nidx = (pl050->buf_wp + 1) % buffer_size;
        if (nidx == pl050->buf_rp) {
                /** Buffer overrunt */
                ddf_msg(LVL_WARN, "Buffer overrun.");
                fibril_mutex_unlock(&pl050->buf_lock);
                return;
        }

        pl050->buffer[pl050->buf_wp] = ipc_get_arg2(call);
        pl050->buf_wp = nidx;
        fibril_condvar_broadcast(&pl050->buf_cv);
        fibril_mutex_unlock(&pl050->buf_lock);
}

static errno_t pl050_init(pl050_t *pl050)
{
        hw_res_list_parsed_t res;
        void *regs;
        errno_t rc;

        fibril_mutex_initialize(&pl050->buf_lock);
        fibril_condvar_initialize(&pl050->buf_cv);
        pl050->buf_rp = pl050->buf_wp = 0;

        pl050->parent_sess = ddf_dev_parent_sess_get(pl050->dev);
        if (pl050->parent_sess == NULL) {
                ddf_msg(LVL_ERROR, "Failed connecitng parent driver.");
                rc = ENOMEM;
                goto error;
        }

        hw_res_list_parsed_init(&res);
        rc = hw_res_get_list_parsed(pl050->parent_sess, &res, 0);
        if (rc != EOK) {
                ddf_msg(LVL_ERROR, "Failed getting resource list.");
                goto error;
        }

        if (res.mem_ranges.count != 1) {
                ddf_msg(LVL_ERROR, "Expected exactly one memory range.");
                rc = EINVAL;
                goto error;
        }

        pl050->iobase = RNGABS(res.mem_ranges.ranges[0]);
        pl050->iosize = RNGSZ(res.mem_ranges.ranges[0]);

        pl050_irq_code.ranges[0].base = pl050->iobase;
        kmi_regs_t *regsphys = (kmi_regs_t *) pl050->iobase;
        pl050_irq_code.cmds[0].addr = &regsphys->stat;
        pl050_irq_code.cmds[3].addr = &regsphys->data;

        if (res.irqs.count != 1) {
                ddf_msg(LVL_ERROR, "Expected exactly one IRQ.");
                rc = EINVAL;
                goto error;
        }

        ddf_msg(LVL_DEBUG, "iobase=%p irq=%d", (void *)pl050->iobase,
            res.irqs.irqs[0]);

        rc = pio_enable((void *)pl050->iobase, sizeof(kmi_regs_t), &regs);
        if (rc != EOK) {
                ddf_msg(LVL_ERROR, "Error enabling PIO");
                goto error;
        }

        pl050->regs = regs;

        cap_irq_handle_t ihandle;
        rc = register_interrupt_handler(pl050->dev,
            res.irqs.irqs[0], pl050_interrupt, (void *)pl050, &pl050_irq_code,
            &ihandle);
        if (rc != EOK) {
                ddf_msg(LVL_ERROR, "Failed registering interrupt handler. (%s)",
                    str_error_name(rc));
                goto error;
        }

        rc = hw_res_enable_interrupt(pl050->parent_sess, res.irqs.irqs[0]);
        if (rc != EOK) {
                ddf_msg(LVL_ERROR, "Failed enabling interrupt: %s", str_error(rc));
                goto error;
        }

        pio_write_8(&pl050->regs->cr,
            BIT_V(uint8_t, kmi_cr_enable) |
            BIT_V(uint8_t, kmi_cr_rxintr));

        return EOK;
error:
        return rc;
}

static errno_t pl050_read(chardev_srv_t *srv, void *buffer, size_t size,
    size_t *nread, chardev_flags_t flags)
{
        pl050_t *pl050 = (pl050_t *)srv->srvs->sarg;
        uint8_t *bp = buffer;
        size_t left;

        fibril_mutex_lock(&pl050->buf_lock);

        left = size;
        while (left > 0) {
                while ((flags & chardev_f_nonblock) == 0 &&
                    left == size && pl050->buf_rp == pl050->buf_wp)
                        fibril_condvar_wait(&pl050->buf_cv, &pl050->buf_lock);
                if (pl050->buf_rp == pl050->buf_wp)
                        break;
                *bp++ = pl050->buffer[pl050->buf_rp];
                --left;
                pl050->buf_rp = (pl050->buf_rp + 1) % buffer_size;
        }

        fibril_mutex_unlock(&pl050->buf_lock);

        *nread = size - left;
        return EOK;
}

static errno_t pl050_write(chardev_srv_t *srv, const void *data, size_t size,
    size_t *nwritten)
{
        pl050_t *pl050 = (pl050_t *)srv->srvs->sarg;
        uint8_t *dp = (uint8_t *)data;
        uint8_t status;
        size_t i;

        ddf_msg(LVL_NOTE, "%s/pl050_write(%zu bytes)", pl050->name, size);
        for (i = 0; i < size; i++) {
                while (true) {
                        status = pio_read_8(&pl050->regs->stat);
                        if ((status & BIT_V(uint8_t, kmi_stat_txempty)) != 0)
                                break;
                }
                pio_write_8(&pl050->regs->data, dp[i]);
        }
        ddf_msg(LVL_NOTE, "%s/pl050_write() success", pl050->name);

        *nwritten = size;
        return EOK;
}

void pl050_char_conn(ipc_call_t *icall, void *arg)
{
        pl050_t *pl050 = pl050_from_fun((ddf_fun_t *)arg);

        chardev_conn(icall, &pl050->cds);
}

/** Add device. */
static errno_t pl050_dev_add(ddf_dev_t *dev)
{
        ddf_fun_t *fun_a;
        pl050_t *pl050 = NULL;
        const char *mname;
        errno_t rc;

        ddf_msg(LVL_DEBUG, "pl050_dev_add()");

        pl050 = ddf_dev_data_alloc(dev, sizeof(pl050_t));
        if (pl050 == NULL) {
                ddf_msg(LVL_ERROR, "Failed allocating soft state.\n");
                rc = ENOMEM;
                goto error;
        }

        pl050->name = (char *)ddf_dev_get_name(dev);
        if (pl050->name == NULL) {
                rc = ENOMEM;
                goto error;
        }

        fun_a = ddf_fun_create(dev, fun_inner, "a");
        if (fun_a == NULL) {
                ddf_msg(LVL_ERROR, "Failed creating function 'a'.");
                rc = ENOMEM;
                goto error;
        }

        pl050->fun_a = fun_a;
        pl050->dev = dev;

        rc = pl050_init(pl050);
        if (rc != EOK)
                goto error;

        if (str_cmp(pl050->name, "kbd") == 0)
                mname = "char/atkbd";
        else
                mname = "char/ps2mouse";

        rc = ddf_fun_add_match_id(fun_a, mname, 10);
        if (rc != EOK) {
                ddf_msg(LVL_ERROR, "Failed adding match IDs to function %s",
                    "char/xtkbd");
                goto error;
        }

        chardev_srvs_init(&pl050->cds);
        pl050->cds.ops = &pl050_chardev_ops;
        pl050->cds.sarg = pl050;

        ddf_fun_set_conn_handler(fun_a, pl050_char_conn);

        rc = ddf_fun_bind(fun_a);
        if (rc != EOK) {
                ddf_msg(LVL_ERROR, "Failed binding function 'a': %s", str_error(rc));
                ddf_fun_destroy(fun_a);
                goto error;
        }

        ddf_msg(LVL_DEBUG, "Device added.");
        return EOK;
error:
        if (pl050 != NULL)
                free(pl050->name);
        return rc;
}

static errno_t pl050_fun_online(ddf_fun_t *fun)
{
        ddf_msg(LVL_DEBUG, "pl050_fun_online()");
        return ddf_fun_online(fun);
}

static errno_t pl050_fun_offline(ddf_fun_t *fun)
{
        ddf_msg(LVL_DEBUG, "pl050_fun_offline()");
        return ddf_fun_offline(fun);
}

int main(int argc, char *argv[])
{
        errno_t rc;

        printf(NAME ": HelenOS pl050 serial device driver\n");
        rc = ddf_log_init(NAME);
        if (rc != EOK) {
                printf(NAME ": Error connecting logging service.");
                return 1;
        }

        return ddf_driver_main(&pl050_driver);
}

/** @}
 */

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