/*
* HIL MLC state machine and serio interface driver
*
* Copyright (c) 2001 Brian S. Julin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL").
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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
*
* References:
* HP-HIL Technical Reference Manual. Hewlett Packard Product No. 45918A
*
*
* Driver theory of operation:
*
* Some access methods and an ISR is defined by the sub-driver
* (e.g. hp_sdc_mlc.c). These methods are expected to provide a
* few bits of logic in addition to raw access to the HIL MLC,
* specifically, the ISR, which is entirely registered by the
* sub-driver and invoked directly, must check for record
* termination or packet match, at which point a semaphore must
* be cleared and then the hil_mlcs_tasklet must be scheduled.
*
* The hil_mlcs_tasklet processes the state machine for all MLCs
* each time it runs, checking each MLC's progress at the current
* node in the state machine, and moving the MLC to subsequent nodes
* in the state machine when appropriate. It will reschedule
* itself if output is pending. (This rescheduling should be replaced
* at some point with a sub-driver-specific mechanism.)
*
* A timer task prods the tasklet once per second to prevent
* hangups when attached devices do not return expected data
* and to initiate probes of the loop for new devices.
*/
#include <linux/hil_mlc.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/list.h>
MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>");
MODULE_DESCRIPTION("HIL MLC serio");
MODULE_LICENSE("Dual BSD/GPL");
EXPORT_SYMBOL(hil_mlc_register);
EXPORT_SYMBOL(hil_mlc_unregister);
#define PREFIX "HIL MLC: "
static LIST_HEAD(hil_mlcs);
static DEFINE_RWLOCK(hil_mlcs_lock);
static struct timer_list hil_mlcs_kicker;
static int hil_mlcs_probe;
static void hil_mlcs_process(unsigned long unused);
DECLARE_TASKLET_DISABLED(hil_mlcs_tasklet, hil_mlcs_process, 0);
/* #define HIL_MLC_DEBUG */
/********************** Device info/instance management **********************/
static void hil_mlc_clear_di_map(hil_mlc *mlc, int val)
{
int j;
for (j = val; j < 7 ; j++)
mlc->di_map[j] = -1;
}
static void hil_mlc_clear_di_scratch(hil_mlc *mlc)
{
memset(&mlc->di_scratch, 0, sizeof(mlc->di_scratch));
}
static void hil_mlc_copy_di_scratch(hil_mlc *mlc, int idx)
{
memcpy(&mlc->di[idx], &mlc->di_scratch, sizeof(mlc->di_scratch));
}
static int hil_mlc_match_di_scratch(hil_mlc *mlc)
{
int idx;
for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) {
int j, found = 0;
/* In-use slots are not eligible. */
for (j = 0; j < 7 ; j++)
if (mlc->di_map[j] == idx)
found++;
if (found)
continue;
if (!memcmp(mlc->di + idx, &mlc->di_scratch,
sizeof(mlc->di_scratch)))
break;
}
return idx >= HIL_MLC_DEVMEM ? -1 : idx;
}
static int hil_mlc_find_free_di(hil_mlc *mlc)
{
int idx;
/* TODO: Pick all-zero slots first, failing that,
* randomize the slot picked among those eligible.
*/
for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) {
int j, found = 0;
for (j = 0; j < 7 ; j++)
if (mlc->di_map[j] == idx)
found++;
if (!found)
break;
}
return idx; /* Note: It is guaranteed at least one above will match */
}
static inline void hil_mlc_clean_serio_map(hil_mlc *mlc)
{
int idx;
for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) {
int j, found = 0;
for (j = 0; j < 7 ; j++)
if (mlc->di_map[j] == idx)
found++;
if (!found)
mlc->serio_map[idx].di_revmap = -1;
}
}
static void hil_mlc_send_polls(hil_mlc *mlc)
{
int did, i, cnt;
struct serio *serio;
struct serio_driver *drv;
i = cnt = 0;
did = (mlc->ipacket[0] & HIL_PKT_ADDR_MASK) >> 8;
serio = did ? mlc->