/*
 *  Misc and compatibility things
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "../include/driver.h"

int snd_task_name(struct task_struct *task, char *name, int size)
{
	int idx;

	snd_debug_check(task == NULL || name == NULL || size < 2, -EINVAL);
	for (idx = 0; idx < sizeof(task->comm) && idx + 1 < size; idx++)
		name[idx] = task->comm[idx];
	name[idx] = '\0';
	return 0;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0) && defined(CONFIG_PCI)

/*
 *  Registration of PCI drivers and handling of hot-pluggable devices.
 */

static LIST_HEAD(pci_drivers);

struct pci_driver_mapping {
	struct pci_dev *dev;
	struct pci_driver *drv;
	void *driver_data;
};

#define PCI_MAX_MAPPINGS 64
static struct pci_driver_mapping drvmap [PCI_MAX_MAPPINGS] = { { NULL, } , };


void * snd_pci_compat_get_driver_data (struct pci_dev *dev)
{
	int i;
	
	for (i = 0; i < PCI_MAX_MAPPINGS; i++)
		if (drvmap[i].dev == dev)
			return drvmap[i].driver_data;
	
	return NULL;
}


void snd_pci_compat_set_driver_data (struct pci_dev *dev, void *driver_data)
{
	int i;
	
	for (i = 0; i < PCI_MAX_MAPPINGS; i++)
		if (drvmap[i].dev == dev) {
			drvmap[i].driver_data = driver_data;
			return;
		} 
}


const struct pci_device_id * snd_pci_compat_match_device(const struct pci_device_id *ids, struct pci_dev *dev)
{
	u16 subsystem_vendor, subsystem_device;

	pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &subsystem_vendor);
	pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &subsystem_device);

	while (ids->vendor || ids->subvendor || ids->class_mask) {
		if ((ids->vendor == PCI_ANY_ID || ids->vendor == dev->vendor) &&
		    (ids->device == PCI_ANY_ID || ids->device == dev->device) &&
		    (ids->subvendor == PCI_ANY_ID || ids->subvendor == subsystem_vendor) &&
		    (ids->subdevice == PCI_ANY_ID || ids->subdevice == subsystem_device) &&
		    !((ids->class ^ dev->class) & ids->class_mask))
			return ids;
		ids++;
	}
	return NULL;
}

static int snd_pci_announce_device(struct pci_driver *drv, struct pci_dev *dev)
{
        int i;
	const struct pci_device_id *id;

	if (drv->id_table) {
		id = snd_pci_compat_match_device(drv->id_table, dev);
		if (!id)
			return 0;
	} else {
		id = NULL;
	}
        for (i = 0; i < PCI_MAX_MAPPINGS; i++) {
	        if (drvmap[i].dev == NULL) {
	                drvmap[i].dev = dev;
	                drvmap[i].drv = drv;
	                break;
	        }
        }
        if (i >= PCI_MAX_MAPPINGS)
		return 0;
	if (drv->probe(dev, id) < 0) {
                 drvmap[i].dev = NULL;
	         return 0;
	}
	return 1;
}

int snd_pci_compat_register_driver(struct pci_driver *drv)
{
	struct pci_dev *dev;
	int count = 0, found, i;

	list_add_tail(&drv->node, &pci_drivers);
	pci_for_each_dev(dev) {
		found = 0;
		for (i = 0; i < PCI_MAX_MAPPINGS; i++)
			if (drvmap[i].dev == dev) {
				found = 1;
				break;
			}
		if (!found)
			count += snd_pci_announce_device(drv, dev);
	}
	return count;
}

void snd_pci_compat_unregister_driver(struct pci_driver *drv)
{
	struct pci_dev *dev;
	int i;

	list_del(&drv->node);
	pci_for_each_dev(dev) {
	        for (i = 0; i < PCI_MAX_MAPPINGS; i++) {
		        if (drvmap[i].dev == dev && drvmap[i].drv == drv) {
			        if (drv->remove)
				        drv->remove(dev);
			        drvmap[i].dev = NULL;
			        break;
		        }
	        }
	}
}

unsigned long snd_pci_compat_get_size (struct pci_dev *dev, int n_base)
{
	u32 l, sz;
	int reg = PCI_BASE_ADDRESS_0 + (n_base << 2);

	pci_read_config_dword (dev, reg, &l);
	if (l == 0xffffffff)
		return 0;

	pci_write_config_dword (dev, reg, ~0);
	pci_read_config_dword (dev, reg, &sz);
	pci_write_config_dword (dev, reg, l);

	if (!sz || sz == 0xffffffff)
		return 0;
	if ((l & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_MEMORY) {
		sz = ~(sz & PCI_BASE_ADDRESS_MEM_MASK);
	} else {
		sz = ~(sz & PCI_BASE_ADDRESS_IO_MASK) & 0xffff;
	}
	
	return sz;
}

int snd_pci_compat_get_flags (struct pci_dev *dev, int n_base)
{
	unsigned long foo = dev->base_address[n_base] & PCI_BASE_ADDRESS_SPACE;
	int flags = 0;
	
	if (foo == 0)
		flags |= IORESOURCE_MEM;
	if (foo == 1)
		flags |= IORESOURCE_IO;
	
	return flags;
}

/*
 *  Set power management state of a device.  For transitions from state D3
 *  it isn't as straightforward as one could assume since many devices forget
 *  their configuration space during wakeup.  Returns old power state.
 */
int snd_pci_compat_set_power_state(struct pci_dev *dev, int new_state)
{
	u32 base[5], romaddr;
	u16 pci_command, pwr_command;
	u8  pci_latency, pci_cacheline;
	int i, old_state;
	int pm = snd_pci_compat_find_capability(dev, PCI_CAP_ID_PM);

	if (!pm)
		return 0;
	pci_read_config_word(dev, pm + PCI_PM_CTRL, &pwr_command);
	old_state = pwr_command & PCI_PM_CTRL_STATE_MASK;
	if (old_state == new_state)
		return old_state;
	if (old_state == 3) {
		pci_read_config_word(dev, PCI_COMMAND, &pci_command);
		pci_write_config_word(dev, PCI_COMMAND, pci_command & ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY));
		for (i = 0; i < 5; i++)
			pci_read_config_dword(dev, PCI_BASE_ADDRESS_0 + i*4, &base[i]);
		pci_read_config_dword(dev, PCI_ROM_ADDRESS, &romaddr);
		pci_read_config_byte(dev, PCI_LATENCY_TIMER, &pci_latency);
		pci_read_config_byte(dev, PCI_CACHE_LINE_SIZE, &pci_cacheline);
		pci_write_config_word(dev, pm + PCI_PM_CTRL, new_state);
		for (i = 0; i < 5; i++)
			pci_write_config_dword(dev, PCI_BASE_ADDRESS_0 + i*4, base[i]);
		pci_write_config_dword(dev, PCI_ROM_ADDRESS, romaddr);
		pci_write_config_byte(dev, PCI_INTERRUPT_LINE, dev->irq);
		pci_write_config_byte(dev, PCI_CACHE_LINE_SIZE, pci_cacheline);
		pci_write_config_byte(dev, PCI_LATENCY_TIMER, pci_latency);
		pci_write_config_word(dev, PCI_COMMAND, pci_command);
	} else
		pci_write_config_word(dev, pm + PCI_PM_CTRL, (pwr_command & ~PCI_PM_CTRL_STATE_MASK) | new_state);
	return old_state;
}

/*
 *  Initialize device before it's used by a driver. Ask low-level code
 *  to enable I/O and memory. Wake up the device if it was suspended.
 *  Beware, this function can fail.
 */
int snd_pci_compat_enable_device(struct pci_dev *dev)
{
	u16 pci_command;

#if 0
	pci_compat_set_power_state(dev, 0);
#endif
	pci_read_config_word(dev, PCI_COMMAND, &pci_command);
	pci_write_config_word(dev, PCI_COMMAND, pci_command | (PCI_COMMAND_IO | PCI_COMMAND_MEMORY));
	return 0;
}

int snd_pci_compat_find_capability(struct pci_dev *dev, int cap)
{
	u16 status;
	u8 pos, id;
	int ttl = 48;

	pci_read_config_word(dev, PCI_STATUS, &status);
	if (!(status & PCI_STATUS_CAP_LIST))
		return 0;
	pci_read_config_byte(dev, PCI_CAPABILITY_LIST, &pos);
	while (ttl-- && pos >= 0x40) {
		pos &= ~3;
		pci_read_config_byte(dev, pos + PCI_CAP_LIST_ID, &id);
		if (id == 0xff)
			break;
		if (id == cap)
			return pos;
		pci_read_config_byte(dev, pos + PCI_CAP_LIST_NEXT, &pos);
	}
	return 0;
}

#endif /* kernel version < 2.3.0 && CONFIG_PCM */

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0) && defined(CONFIG_APM)

#include <linux/apm_bios.h>

static spinlock_t pm_devs_lock = SPIN_LOCK_UNLOCKED;
static LIST_HEAD(pm_devs);

static int snd_apm_callback(apm_event_t ev)
{
	struct list_head *entry;
	pm_request_t rqst;
	void *data;
	int status;
	
	switch (ev) {
	case APM_SYS_SUSPEND:
	case APM_USER_SUSPEND:
	case APM_CRITICAL_SUSPEND:
		rqst = PM_SUSPEND;
		data = (void *)3;
		break;
	case APM_NORMAL_RESUME:
	case APM_CRITICAL_RESUME:
	case APM_STANDBY_RESUME:		/* ??? */
		rqst = PM_RESUME;
		data = (void *)0;
		break;
	default:
		return 0;
	}
	for (entry = pm_devs.next; entry != &pm_devs; entry = entry->next) {
		struct pm_dev *dev = list_entry(entry, struct pm_dev, entry);
		if ((status = pm_send(dev, rqst, data)))
			return status;
	}
	return 0;
}

int pm_init(void)
{
	if (apm_register_callback(snd_apm_callback))
		snd_printk("apm_register_callback failure!\n");
	return 0;
}

void pm_done(void)
{
	apm_unregister_callback(snd_apm_callback);
}

struct pm_dev *pm_register(pm_dev_t type,
			   unsigned long id,
			   pm_callback callback)
{
	struct pm_dev *dev = kmalloc(sizeof(struct pm_dev), GFP_KERNEL);

	if (dev) {
		unsigned long flags;
		
		memset(dev, 0, sizeof(*dev));
		dev->type = type;
		dev->id = id;
		dev->callback = callback;
		
		spin_lock_irqsave(&pm_devs_lock, flags);
		list_add(&dev->entry, &pm_devs);
		spin_unlock_irqrestore(&pm_devs_lock, flags);
	}
	return dev;
}

void pm_unregister(struct pm_dev *dev)
{
	if (dev) {
		unsigned long flags;
		
		spin_lock_irqsave(&pm_devs_lock, flags);
		list_del(&dev->entry);
		spin_unlock_irqrestore(&pm_devs_lock, flags);

		kfree(dev);
	}
}

int pm_send(struct pm_dev *dev, pm_request_t rqst, void *data)
{
	int status = 0;
	int prev_state, next_state;
	
	switch (rqst) {
	case PM_SUSPEND:
	case PM_RESUME:
		prev_state = dev->state;
		next_state = (int) data;
		if (prev_state != next_state) {
			if (dev->callback)
				status = (*dev->callback)(dev, rqst, data);
			if (!status) {
				dev->state = next_state;
				dev->prev_state = prev_state;
			}
		} else {
			dev->prev_state = prev_state;
		}
		break;
	default:
		if (dev->callback)
			status = (*dev->callback)(dev, rqst, data);
		break;
	}
	return status;
}

#endif /* kernel version < 2.3.0 && CONFIG_APM */
