/*
 *  linux/drivers/char/hcdp_serial.c
 *
 *  Copyright (C) 2002  Hewlett-Packard Co.
 *  Copyright (C) 2002  Khalid Aziz <khalid_aziz@hp.com>
 *
 *  Parse the EFI HCDP table to locate serial console and debug ports
 *  and initialize them
 *
 */
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <linux/pm.h>
#include <linux/init.h>
#include <linux/serial.h>
#include <linux/serialP.h>
#include <linux/efi.h>
#include <asm/serial.h>
#include <asm/io.h>
#include <linux/hcdp_serial.h>

#undef SERIAL_DEBUG_HCDP

extern struct serial_state rs_table[];
extern int serial_nr_ports;

/*
 * Parse the HCDP table to find descriptions for headless console and 
 * debug serial ports and add them to rs_table[]. A pointer to HCDP
 * table is passed as parameter. This function should be called 
 * before serial_console_init() is called to make sure the HCDP serial 
 * console will be available for use. IA-64 kernel calls this function
 * from setup_arch() after the EFI and ACPI tables have been parsed.
 */
void __init setup_serial_hcdp(void *tablep) 
{
	hcdp_t hcdp;
	hcdp_dev_t *hcdp_dev;
	struct serial_struct serial_req;
	unsigned long iobase;
	int global_sys_irq;
	int i, nr;
	int shift_once = 1;

#ifdef SERIAL_DEBUG_HCDP
	printk("Entering setup_serial_hcdp()\n");
#endif

	/* Verify we have a valid table pointer */
	if (tablep == NULL) {
		return;
	}

	/*
	 * We do not trust firmware to give us a table starting at an
	 * aligned address. Make a local copy of the HCDP table with 
	 * aligned structures.
	 */
	memcpy(&hcdp, tablep, sizeof(hcdp));

	/*
	 * Perform a sanity check on the table. Table should have a 
	 * signature of "HCDP" and it should be atleast 82 bytes
	 * long to have any useful information.
	 */
	if ((strncmp(hcdp.signature, HCDP_SIGNATURE, 
					HCDP_SIG_LEN) != 0)) {
		return;
	}
	if (hcdp.len < 82) {
		return;
	}

#ifdef SERIAL_DEBUG_HCDP
	printk("setup_serial_hcdp(): table pointer = 0x%p\n", tablep);
	printk("                     sig = '%c%c%c%c'\n",
			hcdp.signature[0],
			hcdp.signature[1],
			hcdp.signature[2],
			hcdp.signature[3]);
	printk("                     length = %d\n", hcdp.len);
	printk("                     Rev = %d\n", hcdp.rev);
	printk("                     OEM ID = %c%c%c%c%c%c\n", 
			hcdp.oemid[0], hcdp.oemid[1], hcdp.oemid[2],
			hcdp.oemid[3], hcdp.oemid[4], hcdp.oemid[5]);
	printk("                     Number of entries = %d\n", hcdp.num_entries);
#endif

	/*
	 * Parse each device entry
	 */
	for (nr=0; nr<hcdp.num_entries; nr++) {
		hcdp_dev = &(hcdp.hcdp_dev[nr]);

		/*
		 * We will parse only the primary console device
		 * which is the first entry for these devices. We will
		 * ignore rest of the entries for the same type device that
		 * has already been parsed and initialized
		 */
		if (hcdp_dev->type != HCDP_DEV_CONSOLE)
			continue;

		iobase = (u64)(hcdp_dev->base_addr.addrhi)<<32 | hcdp_dev->base_addr.addrlo;
		global_sys_irq = hcdp_dev->global_int;
#ifdef SERIAL_DEBUG_HCDP
		printk("                 type = %s\n", 
			((hcdp_dev->type == HCDP_DEV_CONSOLE)?"Headless Console":((hcdp_dev->type == HCDP_DEV_DEBUG)?"Debug port":"Huh????")));
		printk("                 Base address space = %s\n", ((hcdp_dev->base_addr.space_id == ACPI_MEM_SPACE)?"Memory Space":((hcdp_dev->base_addr.space_id == ACPI_IO_SPACE)?"I/O space":"PCI space")));
		printk("                 Base address = 0x%p\n", iobase);
		printk("                 Global System Int = %d\n", global_sys_irq);
		printk("                 Baud rate = %d\n", hcdp_dev->baud);
		printk("                 Bits = %d\n", hcdp_dev->bits);
		printk("                 Clock rate = %d\n", hcdp_dev->clock_rate);
		if (hcdp_dev->base_addr.space_id == ACPI_PCICONF_SPACE) {
			printk("                     PCI serial port:\n");
			printk("                         Bus %d, Device %d, Vendor ID 0x%x, Dev ID 0x%x\n",
			hcdp_dev->pci_bus, hcdp_dev->pci_dev,
			hcdp_dev->pci_vendor_id, hcdp_dev->pci_dev_id);
		}
#endif


		/* 
	 	* Now build a serial_req structure to update the entry in
	 	* rs_table for the headless console port.
	 	*/
		if (hcdp_dev->clock_rate)
			serial_req.baud_base = hcdp_dev->clock_rate;
		else
			serial_req.baud_base = DEFAULT_BAUD_BASE;
		/*
	 	* Check if this is an I/O mapped address or a memory mapped address
	 	*/
		if (hcdp_dev->base_addr.space_id == ACPI_MEM_SPACE) {
			serial_req.port = 0;
			serial_req.port_high = 0;
			serial_req.iomem_base = (void *)ioremap(iobase, 64);
			serial_req.io_type = SERIAL_IO_MEM;
		}
		else if (hcdp_dev->base_addr.space_id == ACPI_IO_SPACE) {
			serial_req.port = (unsigned long) iobase & 0xffffffff;
			serial_req.port_high = (unsigned long)(((u64)iobase) >> 32);
			serial_req.iomem_base = NULL;
			serial_req.io_type = SERIAL_IO_PORT;
		}
		else if (hcdp_dev->base_addr.space_id == ACPI_PCICONF_SPACE) {
			printk("WARNING: No support for PCI serial console\n");
			return;
		}

		/*
		 * Check if HCDP defines a port already in rs_table
		 */
		for (i = 0; i < serial_nr_ports; i++) {
			if ((rs_table[i].port == serial_req.port) &&
				(rs_table[i].iomem_base==serial_req.iomem_base))
				break;
		}
		if (i == serial_nr_ports) {
			/*
			 * We have reserved a slot for HCDP defined console
			 * port at HCDP_SERIAL_CONSOLE_PORT in rs_table
			 * which is not 0. This means using this slot would
			 * put the console at a device other than ttyS0. 
			 * Users expect to see the console at ttyS0. Now 
			 * that we have determined HCDP does describe a 
			 * serial console and it is not one of the compiled
			 * in ports, let us move the entries in rs_table
			 * up by a slot towards HCDP_SERIAL_CONSOLE_PORT to 
			 * make room for the HCDP console at ttyS0. We may go
			 * through this loop more than once if 
			 * early_serial_setup() fails. Make sure we shift the
			 * entries in rs_table only once.
			 */
			if (shift_once) {
				int j;

				for (j=HCDP_SERIAL_CONSOLE_PORT; j>0; j--)
					memcpy(rs_table+j, rs_table+j-1, 
						sizeof(struct serial_state));
				shift_once = 0;
			}
			serial_req.line = 0;
		}
		else
			serial_req.line = i;

		/*
	 	* If the table does not have IRQ information, use 0 for IRQ. 
	 	* This will force rs_init() to probe for IRQ. 
	 	*/
		serial_req.irq = global_sys_irq;
		if (global_sys_irq == 0) {
			serial_req.flags = ASYNC_SKIP_TEST|ASYNC_BOOT_AUTOCONF;
		}
		else {
			serial_req.flags = ASYNC_SKIP_TEST|ASYNC_BOOT_AUTOCONF|
						ASYNC_AUTO_IRQ;
		}

		serial_req.xmit_fifo_size = serial_req.custom_divisor = 0;
		serial_req.close_delay = serial_req.hub6 = serial_req.closing_wait = 0;
		serial_req.iomem_reg_shift = 0;
		if (early_serial_setup(&serial_req) < 0) {
			printk("setup_serial_hcdp(): early_serial_setup() for HCDP serial console port failed. Will try any additional consoles in HCDP.\n");
			continue;
		}
		else
			if (hcdp_dev->type == HCDP_DEV_CONSOLE)
				break;
#ifdef SERIAL_DEBUG_HCDP
		printk("\n");
#endif
	}

#ifdef SERIAL_DEBUG_HCDP
	printk("Leaving setup_serial_hcdp()\n");
#endif
}
