/*
 * interface.c - contains everything related to the user interface
 *
 * Some code is based on isapnp_proc.c (c) Jaroslav Kysela <perex@suse.cz>
 * Copyright 2002 Adam Belay <ambx1@neo.rr.com>
 *
 */

#include <linux/pnp.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/types.h>
#include <linux/stat.h>

#include "base.h"

struct pnp_info_buffer {
	char *buffer;		/* pointer to begin of buffer */
	char *curr;		/* current position in buffer */
	unsigned long size;	/* current size */
	unsigned long len;	/* total length of buffer */
	int stop;		/* stop flag */
	int error;		/* error code */
};

typedef struct pnp_info_buffer pnp_info_buffer_t;

int pnp_printf(pnp_info_buffer_t * buffer, char *fmt,...)
{
	va_list args;
	int res;
	char sbuffer[512];

	if (buffer->stop || buffer->error)
		return 0;
	va_start(args, fmt);
	res = vsprintf(sbuffer, fmt, args);
	va_end(args);
	if (buffer->size + res >= buffer->len) {
		buffer->stop = 1;
		return 0;
	}
	strcpy(buffer->curr, sbuffer);
	buffer->curr += res;
	buffer->size += res;
	return res;
}

static void pnp_print_port(pnp_info_buffer_t *buffer, char *space, struct pnp_port *port)
{
	pnp_printf(buffer, "%sport 0x%x-0x%x, align 0x%x, size 0x%x, %i-bit address decoding\n",
			space, port->min, port->max, port->align ? (port->align-1) : 0, port->size,
			port->flags & PNP_PORT_FLAG_16BITADDR ? 16 : 10);
}

static void pnp_print_irq(pnp_info_buffer_t *buffer, char *space, struct pnp_irq *irq)
{
	int first = 1, i;

	pnp_printf(buffer, "%sirq ", space);
	for (i = 0; i < 16; i++)
		if (irq->map & (1<<i)) {
			if (!first) {
				pnp_printf(buffer, ",");
			} else {
				first = 0;
			}
			if (i == 2 || i == 9)
				pnp_printf(buffer, "2/9");
			else
				pnp_printf(buffer, "%i", i);
		}
	if (!irq->map)
		pnp_printf(buffer, "<none>");
	if (irq->flags & IORESOURCE_IRQ_HIGHEDGE)
		pnp_printf(buffer, " High-Edge");
	if (irq->flags & IORESOURCE_IRQ_LOWEDGE)
		pnp_printf(buffer, " Low-Edge");
	if (irq->flags & IORESOURCE_IRQ_HIGHLEVEL)
		pnp_printf(buffer, " High-Level");
	if (irq->flags & IORESOURCE_IRQ_LOWLEVEL)
		pnp_printf(buffer, " Low-Level");
	pnp_printf(buffer, "\n");
}

static void pnp_print_dma(pnp_info_buffer_t *buffer, char *space, struct pnp_dma *dma)
{
	int first = 1, i;
	char *s;

	pnp_printf(buffer, "%sdma ", space);
	for (i = 0; i < 8; i++)
		if (dma->map & (1<<i)) {
			if (!first) {
				pnp_printf(buffer, ",");
			} else {
				first = 0;
			}
			pnp_printf(buffer, "%i", i);
		}
	if (!dma->map)
		pnp_printf(buffer, "<none>");
	switch (dma->flags & IORESOURCE_DMA_TYPE_MASK) {
	case IORESOURCE_DMA_8BIT:
		s = "8-bit";
		break;
	case IORESOURCE_DMA_8AND16BIT:
		s = "8-bit&16-bit";
		break;
	default:
		s = "16-bit";
	}
	pnp_printf(buffer, " %s", s);
	if (dma->flags & IORESOURCE_DMA_MASTER)
		pnp_printf(buffer, " master");
	if (dma->flags & IORESOURCE_DMA_BYTE)
		pnp_printf(buffer, " byte-count");
	if (dma->flags & IORESOURCE_DMA_WORD)
		pnp_printf(buffer, " word-count");
	switch (dma->flags & IORESOURCE_DMA_SPEED_MASK) {
	case IORESOURCE_DMA_TYPEA:
		s = "type-A";
		break;
	case IORESOURCE_DMA_TYPEB:
		s = "type-B";
		break;
	case IORESOURCE_DMA_TYPEF:
		s = "type-F";
		break;
	default:
		s = "compatible";
		break;
	}
	pnp_printf(buffer, " %s\n", s);
}

static void pnp_print_mem(pnp_info_buffer_t *buffer, char *space, struct pnp_mem *mem)
{
	char *s;

	pnp_printf(buffer, "%sMemory 0x%x-0x%x, align 0x%x, size 0x%x",
			space, mem->min, mem->max, mem->align, mem->size);
	if (mem->flags & IORESOURCE_MEM_WRITEABLE)
		pnp_printf(buffer, ", writeable");
	if (mem->flags & IORESOURCE_MEM_CACHEABLE)
		pnp_printf(buffer, ", cacheable");
	if (mem->flags & IORESOURCE_MEM_RANGELENGTH)
		pnp_printf(buffer, ", range-length");
	if (mem->flags & IORESOURCE_MEM_SHADOWABLE)
		pnp_printf(buffer, ", shadowable");
	if (mem->flags & IORESOURCE_MEM_EXPANSIONROM)
		pnp_printf(buffer, ", expansion ROM");
	switch (mem->flags & IORESOURCE_MEM_TYPE_MASK) {
	case IORESOURCE_MEM_8BIT:
		s = "8-bit";
		break;
	case IORESOURCE_MEM_8AND16BIT:
		s = "8-bit&16-bit";
		break;
	default:
		s = "16-bit";
	}
	pnp_printf(buffer, ", %s\n", s);
}

static void pnp_print_mem32(pnp_info_buffer_t *buffer, char *space, struct pnp_mem32 *mem32)
{
	int first = 1, i;

	pnp_printf(buffer, "%s32-bit memory ", space);
	for (i = 0; i < 17; i++) {
		if (first) {
			first = 0;
		} else {
			pnp_printf(buffer, ":");
		}
		pnp_printf(buffer, "%02x", mem32->data[i]);
	}
}

static void pnp_print_resources(pnp_info_buffer_t *buffer, char *space, struct pnp_resources *res, int dep)
{
	char *s;
	struct pnp_port *port;
	struct pnp_irq *irq;
	struct pnp_dma *dma;
	struct pnp_mem *mem;
	struct pnp_mem32 *mem32;

	switch (res->priority) {
	case PNP_RES_PRIORITY_PREFERRED:
		s = "preferred";
		break;
	case PNP_RES_PRIORITY_ACCEPTABLE:
		s = "acceptable";
		break;
	case PNP_RES_PRIORITY_FUNCTIONAL:
		s = "functional";
		break;
	default:
		s = "invalid";
	}
	if (dep > 0)
		pnp_printf(buffer, "Dependent: %02i - Priority %s\n",dep, s);
	for (port = res->port; port; port = port->next)
		pnp_print_port(buffer, space, port);
	for (irq = res->irq; irq; irq = irq->next)
		pnp_print_irq(buffer, space, irq);
	for (dma = res->dma; dma; dma = dma->next)
		pnp_print_dma(buffer, space, dma);
	for (mem = res->mem; mem; mem = mem->next)
		pnp_print_mem(buffer, space, mem);
	for (mem32 = res->mem32; mem32; mem32 = mem32->next)
		pnp_print_mem32(buffer, space, mem32);
}

static ssize_t pnp_show_possible_resources(struct device *dmdev, char *buf, size_t count, loff_t off)
{
	struct pnp_dev *dev = to_pnp_dev(dmdev);
	struct pnp_resources * res = dev->res;
	int dep = 0;
	pnp_info_buffer_t *buffer;
	if (off)
		return 0;
	buffer = (pnp_info_buffer_t *) pnp_alloc(sizeof(pnp_info_buffer_t));
	if (!buffer)
		return -ENOMEM;
	buffer->len = PAGE_SIZE;
	buffer->buffer = buf;
	buffer->curr = buffer->buffer;
	while (res){
		if (dep == 0)
			pnp_print_resources(buffer, "", res, dep);
		else
			pnp_print_resources(buffer, "   ", res, dep);
		res = res->dep;
		dep++;
	}
	return (buffer->curr - buf);
}

static DEVICE_ATTR(possible,S_IRUGO,pnp_show_possible_resources,NULL);

static ssize_t pnp_show_current_resources(struct device *dmdev, char *buf, size_t count, loff_t off)
{
	struct pnp_dev *dev = to_pnp_dev(dmdev);
	char *str = buf;
	int i;
	if (off)
		return 0;
	if (!dev->active){
		str += sprintf(str,"DISABLED\n");
		goto done;
	}
	for (i = 0; i < DEVICE_COUNT_RESOURCE; i++) {
			if (dev->resource[i].flags & IORESOURCE_IO){
				str += sprintf(str,"io");
				str += sprintf(str," 0x%lx-0x%lx \n",
					       dev->resource[i].start,
					       dev->resource[i].end);
			}
			if (dev->resource[i].flags & IORESOURCE_MEM){
				str += sprintf(str,"mem");
				str += sprintf(str," 0x%lx-0x%lx \n",
					       dev->resource[i].start,
					       dev->resource[i].end);
			}
	}
	for (i = 0; i < DEVICE_COUNT_IRQ && dev->irq_resource[i].flags
			 & IORESOURCE_IRQ; i++) {
			str += sprintf(str,"irq");
			str += sprintf(str," %ld \n", dev->irq_resource[i].start);
	}
	for (i = 0; i < DEVICE_COUNT_DMA && dev->dma_resource[i].flags
			 & IORESOURCE_DMA; i++) {
			str += sprintf(str,"dma");
			str += sprintf(str," %ld \n", dev->dma_resource[i].start);
	}
	done:
	return (str - buf);
}

static ssize_t
pnp_set_current_resources(struct device * dmdev, const char * buf, size_t count, loff_t off)
{
	struct pnp_dev *dev = to_pnp_dev(dmdev);
	char	command[20];
	int	num_args;
	int	error = 0;
	int	depnum;
	if (off)
		return 0;
	num_args = sscanf(buf,"%10s %i",command,&depnum);
	if (!num_args)
		goto done;
	if (!strnicmp(command,"lock",4)) {
		if (dev->active) {
			dev->lock_resources = 1;
		} else {
			error = -EINVAL;
		}
		goto done;
	}
	if (!strnicmp(command,"unlock",6)) {
		if (dev->lock_resources) {
			dev->lock_resources = 0;
		} else {
			error = -EINVAL;
		}
		goto done;
	}
	if (!strnicmp(command,"disable",7)) {
		error = pnp_disable_dev(dev);
		goto done;
	}
	if (!strnicmp(command,"auto",4)) {
		error = pnp_activate_dev(dev,NULL);
		goto done;
	}
	if (!strnicmp(command,"manual",6)) {
		if (num_args != 2)
			goto done;
		error = pnp_raw_set_dev(dev,depnum,NULL);
		goto done;
	}
 done:
	return error < 0 ? error : count;
}

static DEVICE_ATTR(resources,S_IRUGO | S_IWUSR,
		   pnp_show_current_resources,pnp_set_current_resources);

static ssize_t pnp_show_current_ids(struct device *dmdev, char *buf, size_t count, loff_t off)
{
	char *str = buf;
	struct pnp_dev *dev = to_pnp_dev(dmdev);
	struct pnp_id * pos = dev->id;
	if (off)
		return 0;
	while (pos) {
		str += sprintf(str,"%s\n", pos->id);
		pos = pos->next;
	}
	return (str - buf);
}

static DEVICE_ATTR(id,S_IRUGO,pnp_show_current_ids,NULL);

int pnp_interface_attach_device(struct pnp_dev *dev)
{
	device_create_file(&dev->dev,&dev_attr_possible);
	device_create_file(&dev->dev,&dev_attr_resources);
	device_create_file(&dev->dev,&dev_attr_id);
	return 0;
}