diff options
Diffstat (limited to 'drivers')
139 files changed, 14827 insertions, 629 deletions
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 94b8d820c51..610d2cc02cf 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -328,7 +328,7 @@ config ACPI_CONTAINER config ACPI_HOTPLUG_MEMORY tristate "Memory Hotplug" depends on ACPI - depends on MEMORY_HOTPLUG || X86_64 + depends on MEMORY_HOTPLUG default n help This driver adds supports for ACPI Memory Hotplug. This driver diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c index e0a95ba7237..1012284ff4f 100644 --- a/drivers/acpi/acpi_memhotplug.c +++ b/drivers/acpi/acpi_memhotplug.c @@ -57,6 +57,7 @@ MODULE_LICENSE("GPL"); static int acpi_memory_device_add(struct acpi_device *device); static int acpi_memory_device_remove(struct acpi_device *device, int type); +static int acpi_memory_device_start(struct acpi_device *device); static struct acpi_driver acpi_memory_device_driver = { .name = ACPI_MEMORY_DEVICE_DRIVER_NAME, @@ -65,48 +66,79 @@ static struct acpi_driver acpi_memory_device_driver = { .ops = { .add = acpi_memory_device_add, .remove = acpi_memory_device_remove, + .start = acpi_memory_device_start, }, }; +struct acpi_memory_info { + struct list_head list; + u64 start_addr; /* Memory Range start physical addr */ + u64 length; /* Memory Range length */ + unsigned short caching; /* memory cache attribute */ + unsigned short write_protect; /* memory read/write attribute */ + unsigned int enabled:1; +}; + struct acpi_memory_device { acpi_handle handle; unsigned int state; /* State of the memory device */ - unsigned short caching; /* memory cache attribute */ - unsigned short write_protect; /* memory read/write attribute */ - u64 start_addr; /* Memory Range start physical addr */ - u64 length; /* Memory Range length */ + struct list_head res_list; }; +static acpi_status +acpi_memory_get_resource(struct acpi_resource *resource, void *context) +{ + struct acpi_memory_device *mem_device = context; + struct acpi_resource_address64 address64; + struct acpi_memory_info *info, *new; + acpi_status status; + + status = acpi_resource_to_address64(resource, &address64); + if (ACPI_FAILURE(status) || + (address64.resource_type != ACPI_MEMORY_RANGE)) + return AE_OK; + + list_for_each_entry(info, &mem_device->res_list, list) { + /* Can we combine the resource range information? */ + if ((info->caching == address64.info.mem.caching) && + (info->write_protect == address64.info.mem.write_protect) && + (info->start_addr + info->length == address64.minimum)) { + info->length += address64.address_length; + return AE_OK; + } + } + + new = kzalloc(sizeof(struct acpi_memory_info), GFP_KERNEL); + if (!new) + return AE_ERROR; + + INIT_LIST_HEAD(&new->list); + new->caching = address64.info.mem.caching; + new->write_protect = address64.info.mem.write_protect; + new->start_addr = address64.minimum; + new->length = address64.address_length; + list_add_tail(&new->list, &mem_device->res_list); + + return AE_OK; +} + static int acpi_memory_get_device_resources(struct acpi_memory_device *mem_device) { acpi_status status; - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - struct acpi_resource *resource = NULL; - struct acpi_resource_address64 address64; + struct acpi_memory_info *info, *n; ACPI_FUNCTION_TRACE("acpi_memory_get_device_resources"); - /* Get the range from the _CRS */ - status = acpi_get_current_resources(mem_device->handle, &buffer); - if (ACPI_FAILURE(status)) - return_VALUE(-EINVAL); - - resource = (struct acpi_resource *)buffer.pointer; - status = acpi_resource_to_address64(resource, &address64); - if (ACPI_SUCCESS(status)) { - if (address64.resource_type == ACPI_MEMORY_RANGE) { - /* Populate the structure */ - mem_device->caching = address64.info.mem.caching; - mem_device->write_protect = - address64.info.mem.write_protect; - mem_device->start_addr = address64.minimum; - mem_device->length = address64.address_length; - } + status = acpi_walk_resources(mem_device->handle, METHOD_NAME__CRS, + acpi_memory_get_resource, mem_device); + if (ACPI_FAILURE(status)) { + list_for_each_entry_safe(info, n, &mem_device->res_list, list) + kfree(info); + return -EINVAL; } - acpi_os_free(buffer.pointer); - return_VALUE(0); + return 0; } static int @@ -181,7 +213,9 @@ static int acpi_memory_check_device(struct acpi_memory_device *mem_device) static int acpi_memory_enable_device(struct acpi_memory_device *mem_device) { - int result; + int result, num_enabled = 0; + struct acpi_memory_info *info; + int node; ACPI_FUNCTION_TRACE("acpi_memory_enable_device"); @@ -194,15 +228,35 @@ static int acpi_memory_enable_device(struct acpi_memory_device *mem_device) return result; } + node = acpi_get_node(mem_device->handle); /* * Tell the VM there is more memory here... * Note: Assume that this function returns zero on success + * We don't have memory-hot-add rollback function,now. + * (i.e. memory-hot-remove function) */ - result = add_memory(mem_device->start_addr, mem_device->length); - if (result) { + list_for_each_entry(info, &mem_device->res_list, list) { + u64 start_pfn, end_pfn; + + start_pfn = info->start_addr >> PAGE_SHIFT; + end_pfn = (info->start_addr + info->length - 1) >> PAGE_SHIFT; + + if (pfn_valid(start_pfn) || pfn_valid(end_pfn)) { + /* already enabled. try next area */ + num_enabled++; + continue; + } + + result = add_memory(node, info->start_addr, info->length); + if (result) + continue; + info->enabled = 1; + num_enabled++; + } + if (!num_enabled) { ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "\nadd_memory failed\n")); mem_device->state = MEMORY_INVALID_STATE; - return result; + return -EINVAL; } return result; @@ -246,8 +300,7 @@ static int acpi_memory_powerdown_device(struct acpi_memory_device *mem_device) static int acpi_memory_disable_device(struct acpi_memory_device *mem_device) { int result; - u64 start = mem_device->start_addr; - u64 len = mem_device->length; + struct acpi_memory_info *info, *n; ACPI_FUNCTION_TRACE("acpi_memory_disable_device"); @@ -255,10 +308,13 @@ static int acpi_memory_disable_device(struct acpi_memory_device *mem_device) * Ask the VM to offline this memory range. * Note: Assume that this function returns zero on success */ - result = remove_memory(start, len); - if (result) { - ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "Hot-Remove failed.\n")); - return_VALUE(result); + list_for_each_entry_safe(info, n, &mem_device->res_list, list) { + if (info->enabled) { + result = remove_memory(info->start_addr, info->length); + if (result) + return result; + } + kfree(info); } /* Power-off and eject the device */ @@ -356,6 +412,7 @@ static int acpi_memory_device_add(struct acpi_device *device) return_VALUE(-ENOMEM); memset(mem_device, 0, sizeof(struct acpi_memory_device)); + INIT_LIST_HEAD(&mem_device->res_list); mem_device->handle = device->handle; sprintf(acpi_device_name(device), "%s", ACPI_MEMORY_DEVICE_NAME); sprintf(acpi_device_class(device), "%s", ACPI_MEMORY_DEVICE_CLASS); @@ -391,6 +448,25 @@ static int acpi_memory_device_remove(struct acpi_device *device, int type) return_VALUE(0); } +static int acpi_memory_device_start (struct acpi_device *device) +{ + struct acpi_memory_device *mem_device; + int result = 0; + + ACPI_FUNCTION_TRACE("acpi_memory_device_start"); + + mem_device = acpi_driver_data(device); + + if (!acpi_memory_check_device(mem_device)) { + /* call add_memory func */ + result = acpi_memory_enable_device(mem_device); + if (result) + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Error in acpi_memory_enable_device\n")); + } + return_VALUE(result); +} + /* * Helper function to check for memory device */ diff --git a/drivers/acpi/numa.c b/drivers/acpi/numa.c index e2c1a16078c..13d6d5bdea2 100644 --- a/drivers/acpi/numa.c +++ b/drivers/acpi/numa.c @@ -254,5 +254,18 @@ int acpi_get_pxm(acpi_handle h) } while (ACPI_SUCCESS(status)); return -1; } - EXPORT_SYMBOL(acpi_get_pxm); + +int acpi_get_node(acpi_handle *handle) +{ + int pxm, node = -1; + + ACPI_FUNCTION_TRACE("acpi_get_node"); + + pxm = acpi_get_pxm(handle); + if (pxm >= 0) + node = acpi_map_pxm_to_node(pxm); + + return_VALUE(node); +} +EXPORT_SYMBOL(acpi_get_node); diff --git a/drivers/atm/firestream.c b/drivers/atm/firestream.c index f2eeaf9dc56..1bca86edf57 100644 --- a/drivers/atm/firestream.c +++ b/drivers/atm/firestream.c @@ -33,6 +33,7 @@ #include <linux/kernel.h> #include <linux/mm.h> #include <linux/pci.h> +#include <linux/poison.h> #include <linux/errno.h> #include <linux/atm.h> #include <linux/atmdev.h> @@ -754,7 +755,7 @@ static void process_txdone_queue (struct fs_dev *dev, struct queue *q) fs_kfree_skb (skb); fs_dprintk (FS_DEBUG_ALLOC, "Free trans-d: %p\n", td); - memset (td, 0x12, sizeof (struct FS_BPENTRY)); + memset (td, ATM_POISON_FREE, sizeof(struct FS_BPENTRY)); kfree (td); break; default: diff --git a/drivers/base/cpu.c b/drivers/base/cpu.c index dd712b24ec9..4bef76a2f3f 100644 --- a/drivers/base/cpu.c +++ b/drivers/base/cpu.c @@ -8,6 +8,7 @@ #include <linux/cpu.h> #include <linux/topology.h> #include <linux/device.h> +#include <linux/node.h> #include "base.h" @@ -57,13 +58,12 @@ static void __devinit register_cpu_control(struct cpu *cpu) { sysdev_create_file(&cpu->sysdev, &attr_online); } -void unregister_cpu(struct cpu *cpu, struct node *root) +void unregister_cpu(struct cpu *cpu) { int logical_cpu = cpu->sysdev.id; - if (root) - sysfs_remove_link(&root->sysdev.kobj, - kobject_name(&cpu->sysdev.kobj)); + unregister_cpu_under_node(logical_cpu, cpu_to_node(logical_cpu)); + sysdev_remove_file(&cpu->sysdev, &attr_online); sysdev_unregister(&cpu->sysdev); @@ -109,23 +109,21 @@ static SYSDEV_ATTR(crash_notes, 0400, show_crash_notes, NULL); * * Initialize and register the CPU device. */ -int __devinit register_cpu(struct cpu *cpu, int num, struct node *root) +int __devinit register_cpu(struct cpu *cpu, int num) { int error; - cpu->node_id = cpu_to_node(num); cpu->sysdev.id = num; cpu->sysdev.cls = &cpu_sysdev_class; error = sysdev_register(&cpu->sysdev); - if (!error && root) - error = sysfs_create_link(&root->sysdev.kobj, - &cpu->sysdev.kobj, - kobject_name(&cpu->sysdev.kobj)); + if (!error && !cpu->no_control) register_cpu_control(cpu); if (!error) cpu_sys_devices[num] = &cpu->sysdev; + if (!error) + register_cpu_under_node(num, cpu_to_node(num)); #ifdef CONFIG_KEXEC if (!error) @@ -145,5 +143,13 @@ EXPORT_SYMBOL_GPL(get_cpu_sysdev); int __init cpu_dev_init(void) { - return sysdev_class_register(&cpu_sysdev_class); + int err; + + err = sysdev_class_register(&cpu_sysdev_class); +#if defined(CONFIG_SCHED_MC) || defined(CONFIG_SCHED_SMT) + if (!err) + err = sched_create_sysfs_power_savings_entries(&cpu_sysdev_class); +#endif + + return err; } diff --git a/drivers/base/dmapool.c b/drivers/base/dmapool.c index e2f64f91ed0..33c5cce1560 100644 --- a/drivers/base/dmapool.c +++ b/drivers/base/dmapool.c @@ -7,6 +7,7 @@ #include <linux/dmapool.h> #include <linux/slab.h> #include <linux/module.h> +#include <linux/poison.h> /* * Pool allocator ... wraps the dma_alloc_coherent page allocator, so @@ -35,8 +36,6 @@ struct dma_page { /* cacheable header for 'allocation' bytes */ }; #define POOL_TIMEOUT_JIFFIES ((100 /* msec */ * HZ) / 1000) -#define POOL_POISON_FREED 0xa7 /* !inuse */ -#define POOL_POISON_ALLOCATED 0xa9 /* !initted */ static DECLARE_MUTEX (pools_lock); diff --git a/drivers/base/memory.c b/drivers/base/memory.c index dd547af4681..c6b7d9c4b65 100644 --- a/drivers/base/memory.c +++ b/drivers/base/memory.c @@ -306,11 +306,13 @@ static ssize_t memory_probe_store(struct class *class, const char *buf, size_t count) { u64 phys_addr; + int nid; int ret; phys_addr = simple_strtoull(buf, NULL, 0); - ret = add_memory(phys_addr, PAGES_PER_SECTION << PAGE_SHIFT); + nid = memory_add_physaddr_to_nid(phys_addr); + ret = add_memory(nid, phys_addr, PAGES_PER_SECTION << PAGE_SHIFT); if (ret) count = ret; diff --git a/drivers/base/node.c b/drivers/base/node.c index c80c3aeed00..eae2bdc183b 100644 --- a/drivers/base/node.c +++ b/drivers/base/node.c @@ -11,6 +11,7 @@ #include <linux/cpumask.h> #include <linux/topology.h> #include <linux/nodemask.h> +#include <linux/cpu.h> static struct sysdev_class node_class = { set_kset_name("node"), @@ -190,6 +191,66 @@ void unregister_node(struct node *node) sysdev_unregister(&node->sysdev); } +struct node node_devices[MAX_NUMNODES]; + +/* + * register cpu under node + */ +int register_cpu_under_node(unsigned int cpu, unsigned int nid) +{ + if (node_online(nid)) { + struct sys_device *obj = get_cpu_sysdev(cpu); + if (!obj) + return 0; + return sysfs_create_link(&node_devices[nid].sysdev.kobj, + &obj->kobj, + kobject_name(&obj->kobj)); + } + + return 0; +} + +int unregister_cpu_under_node(unsigned int cpu, unsigned int nid) +{ + if (node_online(nid)) { + struct sys_device *obj = get_cpu_sysdev(cpu); + if (obj) + sysfs_remove_link(&node_devices[nid].sysdev.kobj, + kobject_name(&obj->kobj)); + } + return 0; +} + +int register_one_node(int nid) +{ + int error = 0; + int cpu; + + if (node_online(nid)) { + int p_node = parent_node(nid); + struct node *parent = NULL; + + if (p_node != nid) + parent = &node_devices[p_node]; + + error = register_node(&node_devices[nid], nid, parent); + + /* link cpu under this node */ + for_each_present_cpu(cpu) { + if (cpu_to_node(cpu) == nid) + register_cpu_under_node(cpu, nid); + } + } + + return error; + +} + +void unregister_one_node(int nid) +{ + unregister_node(&node_devices[nid]); +} + static int __init register_node_type(void) { return sysdev_class_register(&node_class); diff --git a/drivers/base/topology.c b/drivers/base/topology.c index 8c52421cbc5..c2d62163238 100644 --- a/drivers/base/topology.c +++ b/drivers/base/topology.c @@ -107,7 +107,7 @@ static int __cpuinit topology_remove_dev(struct sys_device * sys_dev) return 0; } -static int topology_cpu_callback(struct notifier_block *nfb, +static int __cpuinit topology_cpu_callback(struct notifier_block *nfb, unsigned long action, void *hcpu) { unsigned int cpu = (unsigned long)hcpu; @@ -125,7 +125,7 @@ static int topology_cpu_callback(struct notifier_block *nfb, return NOTIFY_OK; } -static struct notifier_block topology_cpu_notifier = +static struct notifier_block __cpuinitdata topology_cpu_notifier = { .notifier_call = topology_cpu_callback, }; diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index 3610c572955..410d70cb76f 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -939,12 +939,35 @@ config MWAVE config SCx200_GPIO tristate "NatSemi SCx200 GPIO Support" depends on SCx200 + select NSC_GPIO help Give userspace access to the GPIO pins on the National Semiconductor SCx200 processors. If compiled as a module, it will be called scx200_gpio. +config PC8736x_GPIO + tristate "NatSemi PC8736x GPIO Support" + depends on X86 + default SCx200_GPIO # mostly N + select NSC_GPIO # needed for support routines + help + Give userspace access to the GPIO pins on the National + Semiconductor PC-8736x (x=[03456]) SuperIO chip. The chip + has multiple functional units, inc several managed by + hwmon/pc87360 driver. Tested with PC-87366 + + If compiled as a module, it will be called pc8736x_gpio. + +config NSC_GPIO + tristate "NatSemi Base GPIO Support" + # selected by SCx200_GPIO and PC8736x_GPIO + # what about 2 selectors differing: m != y + help + Common support used (and needed) by scx200_gpio and + pc8736x_gpio drivers. If those drivers are built as + modules, this one will be too, named nsc_gpio + config CS5535_GPIO tristate "AMD CS5535/CS5536 GPIO (Geode Companion Device)" depends on X86_32 diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 524105597ea..6e0f4469d8b 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -82,6 +82,8 @@ obj-$(CONFIG_PPDEV) += ppdev.o obj-$(CONFIG_NWBUTTON) += nwbutton.o obj-$(CONFIG_NWFLASH) += nwflash.o obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o +obj-$(CONFIG_PC8736x_GPIO) += pc8736x_gpio.o +obj-$(CONFIG_NSC_GPIO) += nsc_gpio.o obj-$(CONFIG_CS5535_GPIO) += cs5535_gpio.o obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o obj-$(CONFIG_TANBAC_TB0219) += tb0219.o diff --git a/drivers/char/agp/sgi-agp.c b/drivers/char/agp/sgi-agp.c index cfa7922cb43..d73be4c2db8 100644 --- a/drivers/char/agp/sgi-agp.c +++ b/drivers/char/agp/sgi-agp.c @@ -329,9 +329,8 @@ static int __devinit agp_sgi_init(void) static void __devexit agp_sgi_cleanup(void) { - if (sgi_tioca_agp_bridges) - kfree(sgi_tioca_agp_bridges); - sgi_tioca_agp_bridges=NULL; + kfree(sgi_tioca_agp_bridges); + sgi_tioca_agp_bridges = NULL; } module_init(agp_sgi_init); diff --git a/drivers/char/drm/drm_memory_debug.h b/drivers/char/drm/drm_memory_debug.h index 6543b9a14c4..d117cc99719 100644 --- a/drivers/char/drm/drm_memory_debug.h +++ b/drivers/char/drm/drm_memory_debug.h @@ -43,7 +43,7 @@ typedef struct drm_mem_stats { unsigned long bytes_freed; } drm_mem_stats_t; -static spinlock_t drm_mem_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(drm_mem_lock); static unsigned long drm_ram_available = 0; /* In pages */ static unsigned long drm_ram_used = 0; static drm_mem_stats_t drm_mem_stats[] = diff --git a/drivers/char/drm/via_dmablit.c b/drivers/char/drm/via_dmablit.c index b7f17457b42..78a81a4a99c 100644 --- a/drivers/char/drm/via_dmablit.c +++ b/drivers/char/drm/via_dmablit.c @@ -557,7 +557,7 @@ via_init_dmablit(drm_device_t *dev) blitq->num_outstanding = 0; blitq->is_active = 0; blitq->aborting = 0; - blitq->blit_lock = SPIN_LOCK_UNLOCKED; + spin_lock_init(&blitq->blit_lock); for (j=0; j<VIA_NUM_BLIT_SLOTS; ++j) { DRM_INIT_WAITQUEUE(blitq->blit_queue + j); } diff --git a/drivers/char/epca.c b/drivers/char/epca.c index 9cad8501d62..dc0602ae850 100644 --- a/drivers/char/epca.c +++ b/drivers/char/epca.c @@ -80,7 +80,7 @@ static int invalid_lilo_config; /* The ISA boards do window flipping into the same spaces so its only sane with a single lock. It's still pretty efficient */ -static spinlock_t epca_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(epca_lock); /* ----------------------------------------------------------------------- MAXBOARDS is typically 12, but ISA and EISA cards are restricted to diff --git a/drivers/char/hvcs.c b/drivers/char/hvcs.c index 8d97b391129..afa26b65dac 100644 --- a/drivers/char/hvcs.c +++ b/drivers/char/hvcs.c @@ -1320,11 +1320,12 @@ static struct tty_operations hvcs_ops = { static int hvcs_alloc_index_list(int n) { int i; + hvcs_index_list = kmalloc(n * sizeof(hvcs_index_count),GFP_KERNEL); if (!hvcs_index_list) return -ENOMEM; hvcs_index_count = n; - for(i = 0; i < hvcs_index_count; i++) + for (i = 0; i < hvcs_index_count; i++) hvcs_index_list[i] = -1; return 0; } @@ -1332,11 +1333,9 @@ static int hvcs_alloc_index_list(int n) static void hvcs_free_index_list(void) { /* Paranoia check to be thorough. */ - if (hvcs_index_list) { - kfree(hvcs_index_list); - hvcs_index_list = NULL; - hvcs_index_count = 0; - } + kfree(hvcs_index_list); + hvcs_index_list = NULL; + hvcs_index_count = 0; } static int __init hvcs_module_init(void) diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c index b03ddab1bef..83ed6ae466a 100644 --- a/drivers/char/ipmi/ipmi_msghandler.c +++ b/drivers/char/ipmi/ipmi_msghandler.c @@ -57,8 +57,7 @@ static int ipmi_init_msghandler(void); static int initialized = 0; #ifdef CONFIG_PROC_FS -struct proc_dir_entry *proc_ipmi_root = NULL; -EXPORT_SYMBOL(proc_ipmi_root); +static struct proc_dir_entry *proc_ipmi_root = NULL; #endif /* CONFIG_PROC_FS */ #define MAX_EVENTS_IN_QUEUE 25 diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c index 02a7dd7a8a5..101c14b9b26 100644 --- a/drivers/char/ipmi/ipmi_si_intf.c +++ b/drivers/char/ipmi/ipmi_si_intf.c @@ -809,7 +809,7 @@ static int ipmi_thread(void *data) /* do nothing */ } else if (smi_result == SI_SM_CALL_WITH_DELAY) - udelay(1); + schedule(); else schedule_timeout_interruptible(1); } diff --git a/drivers/char/moxa.c b/drivers/char/moxa.c index f43c2e04ead..01247cccb89 100644 --- a/drivers/char/moxa.c +++ b/drivers/char/moxa.c @@ -301,7 +301,7 @@ static struct tty_operations moxa_ops = { .tiocmset = moxa_tiocmset, }; -static spinlock_t moxa_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(moxa_lock); #ifdef CONFIG_PCI static int moxa_get_PCI_conf(struct pci_dev *p, int board_type, moxa_board_conf * board) diff --git a/drivers/char/nsc_gpio.c b/drivers/char/nsc_gpio.c new file mode 100644 index 00000000000..5b91e4e2564 --- /dev/null +++ b/drivers/char/nsc_gpio.c @@ -0,0 +1,142 @@ +/* linux/drivers/char/nsc_gpio.c + + National Semiconductor common GPIO device-file/VFS methods. + Allows a user space process to control the GPIO pins. + + Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> + Copyright (c) 2005 Jim Cromie <jim.cromie@gmail.com> +*/ + +#include <linux/config.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/nsc_gpio.h> +#include <linux/platform_device.h> +#include <asm/uaccess.h> +#include <asm/io.h> + +#define NAME "nsc_gpio" + +void nsc_gpio_dump(struct nsc_gpio_ops *amp, unsigned index) +{ + /* retrieve current config w/o changing it */ + u32 config = amp->gpio_config(index, ~0, 0); + + /* user requested via 'v' command, so its INFO */ + dev_info(amp->dev, "io%02u: 0x%04x %s %s %s %s %s %s %s\tio:%d/%d\n", + index, config, + (config & 1) ? "OE" : "TS", /* output-enabled/tristate */ + (config & 2) ? "PP" : "OD", /* push pull / open drain */ + (config & 4) ? "PUE" : "PUD", /* pull up enabled/disabled */ + (config & 8) ? "LOCKED" : "", /* locked / unlocked */ + (config & 16) ? "LEVEL" : "EDGE",/* level/edge input */ + (config & 32) ? "HI" : "LO", /* trigger on rise/fall edge */ + (config & 64) ? "DEBOUNCE" : "", /* debounce */ + + amp->gpio_get(index), amp->gpio_current(index)); +} + +ssize_t nsc_gpio_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + unsigned m = iminor(file->f_dentry->d_inode); + struct nsc_gpio_ops *amp = file->private_data; + struct device *dev = amp->dev; + size_t i; + int err = 0; + + for (i = 0; i < len; ++i) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + switch (c) { + case '0': + amp->gpio_set(m, 0); + break; + case '1': + amp->gpio_set(m, 1); + break; + case 'O': + dev_dbg(dev, "GPIO%d output enabled\n", m); + amp->gpio_config(m, ~1, 1); + break; + case 'o': + dev_dbg(dev, "GPIO%d output disabled\n", m); + amp->gpio_config(m, ~1, 0); + break; + case 'T': + dev_dbg(dev, "GPIO%d output is push pull\n", + m); + amp->gpio_config(m, ~2, 2); + break; + case 't': + dev_dbg(dev, "GPIO%d output is open drain\n", + m); + amp->gpio_config(m, ~2, 0); + break; + case 'P': + dev_dbg(dev, "GPIO%d pull up enabled\n", m); + amp->gpio_config(m, ~4, 4); + break; + case 'p': + dev_dbg(dev, "GPIO%d pull up disabled\n", m); + amp->gpio_config(m, ~4, 0); + break; + case 'v': + /* View Current pin settings */ + amp->gpio_dump(amp, m); + break; + case '\n': + /* end of settings string, do nothing */ + break; + default: + dev_err(dev, "io%2d bad setting: chr<0x%2x>\n", + m, (int)c); + err++; + } + } + if (err) + return -EINVAL; /* full string handled, report error */ + + return len; +} + +ssize_t nsc_gpio_read(struct file *file, char __user * buf, + size_t len, loff_t * ppos) +{ + unsigned m = iminor(file->f_dentry->d_inode); + int value; + struct nsc_gpio_ops *amp = file->private_data; + + value = amp->gpio_get(m); + if (put_user(value ? '1' : '0', buf)) + return -EFAULT; + + return 1; +} + +/* common file-ops routines for both scx200_gpio and pc87360_gpio */ +EXPORT_SYMBOL(nsc_gpio_write); +EXPORT_SYMBOL(nsc_gpio_read); +EXPORT_SYMBOL(nsc_gpio_dump); + +static int __init nsc_gpio_init(void) +{ + printk(KERN_DEBUG NAME " initializing\n"); + return 0; +} + +static void __exit nsc_gpio_cleanup(void) +{ + printk(KERN_DEBUG NAME " cleanup\n"); +} + +module_init(nsc_gpio_init); +module_exit(nsc_gpio_cleanup); + +MODULE_AUTHOR("Jim Cromie <jim.cromie@gmail.com>"); +MODULE_DESCRIPTION("NatSemi GPIO Common Methods"); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/pc8736x_gpio.c b/drivers/char/pc8736x_gpio.c new file mode 100644 index 00000000000..1c706ccfdbb --- /dev/null +++ b/drivers/char/pc8736x_gpio.c @@ -0,0 +1,340 @@ +/* linux/drivers/char/pc8736x_gpio.c + + National Semiconductor PC8736x GPIO driver. Allows a user space + process to play with the GPIO pins. + + Copyright (c) 2005 Jim Cromie <jim.cromie@gmail.com> + + adapted from linux/drivers/char/scx200_gpio.c + Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>, +*/ + +#include <linux/config.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/mutex.h> +#include <linux/nsc_gpio.h> +#include <linux/platform_device.h> +#include <asm/uaccess.h> + +#define DEVNAME "pc8736x_gpio" + +MODULE_AUTHOR("Jim Cromie <jim.cromie@gmail.com>"); +MODULE_DESCRIPTION("NatSemi PC-8736x GPIO Pin Driver"); +MODULE_LICENSE("GPL"); + +static int major; /* default to dynamic major */ +module_param(major, int, 0); +MODULE_PARM_DESC(major, "Major device number"); + +static DEFINE_MUTEX(pc8736x_gpio_config_lock); +static unsigned pc8736x_gpio_base; +static u8 pc8736x_gpio_shadow[4]; + +#define SIO_BASE1 0x2E /* 1st command-reg to check */ +#define SIO_BASE2 0x4E /* alt command-reg to check */ +#define SIO_BASE_OFFSET 0x20 + +#define SIO_SID 0x20 /* SuperI/O ID Register */ +#define SIO_SID_VALUE 0xe9 /* Expected value in SuperI/O ID Register */ + +#define SIO_CF1 0x21 /* chip config, bit0 is chip enable */ + +#define PC8736X_GPIO_SIZE 16 + +#define SIO_UNIT_SEL 0x7 /* unit select reg */ +#define SIO_UNIT_ACT 0x30 /* unit enable */ +#define SIO_GPIO_UNIT 0x7 /* unit number of GPIO */ +#define SIO_VLM_UNIT 0x0D +#define SIO_TMS_UNIT 0x0E + +/* config-space addrs to read/write each unit's runtime addr */ +#define SIO_BASE_HADDR 0x60 +#define SIO_BASE_LADDR 0x61 + +/* GPIO config-space pin-control addresses */ +#define SIO_GPIO_PIN_SELECT 0xF0 +#define SIO_GPIO_PIN_CONFIG 0xF1 +#define SIO_GPIO_PIN_EVENT 0xF2 + +static unsigned char superio_cmd = 0; +static unsigned char selected_device = 0xFF; /* bogus start val */ + +/* GPIO port runtime access, functionality */ +static int port_offset[] = { 0, 4, 8, 10 }; /* non-uniform offsets ! */ +/* static int event_capable[] = { 1, 1, 0, 0 }; ports 2,3 are hobbled */ + +#define PORT_OUT 0 +#define PORT_IN 1 +#define PORT_EVT_EN 2 +#define PORT_EVT_STST 3 + +static struct platform_device *pdev; /* use in dev_*() */ + +static inline void superio_outb(int addr, int val) +{ + outb_p(addr, superio_cmd); + outb_p(val, superio_cmd + 1); +} + +static inline int superio_inb(int addr) +{ + outb_p(addr, superio_cmd); + return inb_p(superio_cmd + 1); +} + +static int pc8736x_superio_present(void) +{ + /* try the 2 possible values, read a hardware reg to verify */ + superio_cmd = SIO_BASE1; + if (superio_inb(SIO_SID) == SIO_SID_VALUE) + return superio_cmd; + + superio_cmd = SIO_BASE2; + if (superio_inb(SIO_SID) == SIO_SID_VALUE) + return superio_cmd; + + return 0; +} + +static void device_select(unsigned devldn) +{ + superio_outb(SIO_UNIT_SEL, devldn); + selected_device = devldn; +} + +static void select_pin(unsigned iminor) +{ + /* select GPIO port/pin from device minor number */ + device_select(SIO_GPIO_UNIT); + superio_outb(SIO_GPIO_PIN_SELECT, + ((iminor << 1) & 0xF0) | (iminor & 0x7)); +} + +static inline u32 pc8736x_gpio_configure_fn(unsigned index, u32 mask, u32 bits, + u32 func_slct) +{ + u32 config, new_config; + + mutex_lock(&pc8736x_gpio_config_lock); + + device_select(SIO_GPIO_UNIT); + select_pin(index); + + /* read current config value */ + config = superio_inb(func_slct); + + /* set new config */ + new_config = (config & mask) | bits; + superio_outb(func_slct, new_config); + + mutex_unlock(&pc8736x_gpio_config_lock); + + return config; +} + +static u32 pc8736x_gpio_configure(unsigned index, u32 mask, u32 bits) +{ + return pc8736x_gpio_configure_fn(index, mask, bits, + SIO_GPIO_PIN_CONFIG); +} + +static int pc8736x_gpio_get(unsigned minor) +{ + int port, bit, val; + + port = minor >> 3; + bit = minor & 7; + val = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_IN); + val >>= bit; + val &= 1; + + dev_dbg(&pdev->dev, "_gpio_get(%d from %x bit %d) == val %d\n", + minor, pc8736x_gpio_base + port_offset[port] + PORT_IN, bit, + val); + + return val; +} + +static void pc8736x_gpio_set(unsigned minor, int val) +{ + int port, bit, curval; + + minor &= 0x1f; + port = minor >> 3; + bit = minor & 7; + curval = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_OUT); + + dev_dbg(&pdev->dev, "addr:%x cur:%x bit-pos:%d cur-bit:%x + new:%d -> bit-new:%d\n", + pc8736x_gpio_base + port_offset[port] + PORT_OUT, + curval, bit, (curval & ~(1 << bit)), val, (val << bit)); + + val = (curval & ~(1 << bit)) | (val << bit); + + dev_dbg(&pdev->dev, "gpio_set(minor:%d port:%d bit:%d)" + " %2x -> %2x\n", minor, port, bit, curval, val); + + outb_p(val, pc8736x_gpio_base + port_offset[port] + PORT_OUT); + + curval = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_OUT); + val = inb_p(pc8736x_gpio_base + port_offset[port] + PORT_IN); + + dev_dbg(&pdev->dev, "wrote %x, read: %x\n", curval, val); + pc8736x_gpio_shadow[port] = val; +} + +static void pc8736x_gpio_set_high(unsigned index) +{ + pc8736x_gpio_set(index, 1); +} + +static void pc8736x_gpio_set_low(unsigned index) +{ + pc8736x_gpio_set(index, 0); +} + +static int pc8736x_gpio_current(unsigned minor) +{ + int port, bit; + minor &= 0x1f; + port = minor >> 3; + bit = minor & 7; + return ((pc8736x_gpio_shadow[port] >> bit) & 0x01); +} + +static void pc8736x_gpio_change(unsigned index) +{ + pc8736x_gpio_set(index, !pc8736x_gpio_current(index)); +} + +static struct nsc_gpio_ops pc8736x_access = { + .owner = THIS_MODULE, + .gpio_config = pc8736x_gpio_configure, + .gpio_dump = nsc_gpio_dump, + .gpio_get = pc8736x_gpio_get, + .gpio_set = pc8736x_gpio_set, + .gpio_set_high = pc8736x_gpio_set_high, + .gpio_set_low = pc8736x_gpio_set_low, + .gpio_change = pc8736x_gpio_change, + .gpio_current = pc8736x_gpio_current +}; + +static int pc8736x_gpio_open(struct inode *inode, struct file *file) +{ + unsigned m = iminor(inode); + file->private_data = &pc8736x_access; + + dev_dbg(&pdev->dev, "open %d\n", m); + + if (m > 63) + return -EINVAL; + return nonseekable_open(inode, file); +} + +static struct file_operations pc8736x_gpio_fops = { + .owner = THIS_MODULE, + .open = pc8736x_gpio_open, + .write = nsc_gpio_write, + .read = nsc_gpio_read, +}; + +static void __init pc8736x_init_shadow(void) +{ + int port; + + /* read the current values driven on the GPIO signals */ + for (port = 0; port < 4; ++port) + pc8736x_gpio_shadow[port] + = inb_p(pc8736x_gpio_base + port_offset[port] + + PORT_OUT); + +} + +static int __init pc8736x_gpio_init(void) +{ + int rc = 0; + + pdev = platform_device_alloc(DEVNAME, 0); + if (!pdev) + return -ENOMEM; + + rc = platform_device_add(pdev); + if (rc) { + rc = -ENODEV; + goto undo_platform_dev_alloc; + } + dev_info(&pdev->dev, "NatSemi pc8736x GPIO Driver Initializing\n"); + + if (!pc8736x_superio_present()) { + rc = -ENODEV; + dev_err(&pdev->dev, "no device found\n"); + goto undo_platform_dev_add; + } + pc8736x_access.dev = &pdev->dev; + + /* Verify that chip and it's GPIO unit are both enabled. + My BIOS does this, so I take minimum action here + */ + rc = superio_inb(SIO_CF1); + if (!(rc & 0x01)) { + rc = -ENODEV; + dev_err(&pdev->dev, "device not enabled\n"); + goto undo_platform_dev_add; + } + device_select(SIO_GPIO_UNIT); + if (!superio_inb(SIO_UNIT_ACT)) { + rc = -ENODEV; + dev_err(&pdev->dev, "GPIO unit not enabled\n"); + goto undo_platform_dev_add; + } + + /* read the GPIO unit base addr that chip responds to */ + pc8736x_gpio_base = (superio_inb(SIO_BASE_HADDR) << 8 + | superio_inb(SIO_BASE_LADDR)); + + if (!request_region(pc8736x_gpio_base, 16, DEVNAME)) { + rc = -ENODEV; + dev_err(&pdev->dev, "GPIO ioport %x busy\n", + pc8736x_gpio_base); + goto undo_platform_dev_add; + } + dev_info(&pdev->dev, "GPIO ioport %x reserved\n", pc8736x_gpio_base); + + rc = register_chrdev(major, DEVNAME, &pc8736x_gpio_fops); + if (rc < 0) { + dev_err(&pdev->dev, "register-chrdev failed: %d\n", rc); + goto undo_platform_dev_add; + } + if (!major) { + major = rc; + dev_dbg(&pdev->dev, "got dynamic major %d\n", major); + } + + pc8736x_init_shadow(); + return 0; + +undo_platform_dev_add: + platform_device_put(pdev); +undo_platform_dev_alloc: + kfree(pdev); + return rc; +} + +static void __exit pc8736x_gpio_cleanup(void) +{ + dev_dbg(&pdev->dev, " cleanup\n"); + + release_region(pc8736x_gpio_base, 16); + + unregister_chrdev(major, DEVNAME); +} + +EXPORT_SYMBOL(pc8736x_access); + +module_init(pc8736x_gpio_init); +module_exit(pc8736x_gpio_cleanup); diff --git a/drivers/char/scx200_gpio.c b/drivers/char/scx200_gpio.c index 664a6e97eb1..5a280a33040 100644 --- a/drivers/char/scx200_gpio.c +++ b/drivers/char/scx200_gpio.c @@ -1,4 +1,4 @@ -/* linux/drivers/char/scx200_gpio.c +/* linux/drivers/char/scx200_gpio.c National Semiconductor SCx200 GPIO driver. Allows a user space process to play with the GPIO pins. @@ -6,17 +6,26 @@ Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> */ #include <linux/config.h> +#include <linux/device.h> #include <linux/fs.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/init.h> +#include <linux/platform_device.h> #include <asm/uaccess.h> #include <asm/io.h> +#include <linux/types.h> +#include <linux/cdev.h> + #include <linux/scx200_gpio.h> +#include <linux/nsc_gpio.h> #define NAME "scx200_gpio" +#define DEVNAME NAME + +static struct platform_device *pdev; MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>"); MODULE_DESCRIPTION("NatSemi SCx200 GPIO Pin Driver"); @@ -26,70 +35,23 @@ static int major = 0; /* default to dynamic major */ module_param(major, int, 0); MODULE_PARM_DESC(major, "Major device number"); -static ssize_t scx200_gpio_write(struct file *file, const char __user *data, - size_t len, loff_t *ppos) -{ - unsigned m = iminor(file->f_dentry->d_inode); - size_t i; - - for (i = 0; i < len; ++i) { - char c; - if (get_user(c, data+i)) - return -EFAULT; - switch (c) - { - case '0': - scx200_gpio_set(m, 0); - break; - case '1': - scx200_gpio_set(m, 1); - break; - case 'O': - printk(KERN_INFO NAME ": GPIO%d output enabled\n", m); - scx200_gpio_configure(m, ~1, 1); - break; - case 'o': - printk(KERN_INFO NAME ": GPIO%d output disabled\n", m); - scx200_gpio_configure(m, ~1, 0); - break; - case 'T': - printk(KERN_INFO NAME ": GPIO%d output is push pull\n", m); - scx200_gpio_configure(m, ~2, 2); - break; - case 't': - printk(KERN_INFO NAME ": GPIO%d output is open drain\n", m); - scx200_gpio_configure(m, ~2, 0); - break; - case 'P': - printk(KERN_INFO NAME ": GPIO%d pull up enabled\n", m); - scx200_gpio_configure(m, ~4, 4); - break; - case 'p': - printk(KERN_INFO NAME ": GPIO%d pull up disabled\n", m); - scx200_gpio_configure(m, ~4, 0); - break; - } - } - - return len; -} - -static ssize_t scx200_gpio_read(struct file *file, char __user *buf, - size_t len, loff_t *ppos) -{ - unsigned m = iminor(file->f_dentry->d_inode); - int value; - - value = scx200_gpio_get(m); - if (put_user(value ? '1' : '0', buf)) - return -EFAULT; - - return 1; -} +struct nsc_gpio_ops scx200_access = { + .owner = THIS_MODULE, + .gpio_config = scx200_gpio_configure, + .gpio_dump = nsc_gpio_dump, + .gpio_get = scx200_gpio_get, + .gpio_set = scx200_gpio_set, + .gpio_set_high = scx200_gpio_set_high, + .gpio_set_low = scx200_gpio_set_low, + .gpio_change = scx200_gpio_change, + .gpio_current = scx200_gpio_current +}; static int scx200_gpio_open(struct inode *inode, struct file *file) { unsigned m = iminor(inode); + file->private_data = &scx200_access; + if (m > 63) return -EINVAL; return nonseekable_open(inode, file); @@ -103,47 +65,81 @@ static int scx200_gpio_release(struct inode *inode, struct file *file) static struct file_operations scx200_gpio_fops = { .owner = THIS_MODULE, - .write = scx200_gpio_write, - .read = scx200_gpio_read, + .write = nsc_gpio_write, + .read = nsc_gpio_read, .open = scx200_gpio_open, .release = scx200_gpio_release, }; +struct cdev *scx200_devices; +static int num_pins = 32; + static int __init scx200_gpio_init(void) { - int r; - - printk(KERN_DEBUG NAME ": NatSemi SCx200 GPIO Driver\n"); + int rc, i; + dev_t dev = MKDEV(major, 0); if (!scx200_gpio_present()) { - printk(KERN_ERR NAME ": no SCx200 gpio pins available\n"); + printk(KERN_ERR NAME ": no SCx200 gpio present\n"); return -ENODEV; } - r = register_chrdev(major, NAME, &scx200_gpio_fops); - if (r < 0) { - printk(KERN_ERR NAME ": unable to register character device\n"); - return r; + /* support dev_dbg() with pdev->dev */ + pdev = platform_device_alloc(DEVNAME, 0); + if (!pdev) + return -ENOMEM; + + rc = platform_device_add(pdev); + if (rc) + goto undo_malloc; + + /* nsc_gpio uses dev_dbg(), so needs this */ + scx200_access.dev = &pdev->dev; + + if (major) + rc = register_chrdev_region(dev, num_pins, "scx200_gpio"); + else { + rc = alloc_chrdev_region(&dev, 0, num_pins, "scx200_gpio"); + major = MAJOR(dev); } - if (!major) { - major = r; - printk(KERN_DEBUG NAME ": got dynamic major %d\n", major); + if (rc < 0) { + dev_err(&pdev->dev, "SCx200 chrdev_region err: %d\n", rc); + goto undo_platform_device_add; + } + scx200_devices = kzalloc(num_pins * sizeof(struct cdev), GFP_KERNEL); + if (!scx200_devices) { + rc = -ENOMEM; + goto undo_chrdev_region; + } + for (i = 0; i < num_pins; i++) { + struct cdev *cdev = &scx200_devices[i]; + cdev_init(cdev, &scx200_gpio_fops); + cdev->owner = THIS_MODULE; + rc = cdev_add(cdev, MKDEV(major, i), 1); + /* tolerate 'minor' errors */ + if (rc) + dev_err(&pdev->dev, "Error %d on minor %d", rc, i); } - return 0; + return 0; /* succeed */ + +undo_chrdev_region: + unregister_chrdev_region(dev, num_pins); +undo_platform_device_add: + platform_device_put(pdev); +undo_malloc: + kfree(pdev); + return rc; } static void __exit scx200_gpio_cleanup(void) { - unregister_chrdev(major, NAME); + kfree(scx200_devices); + unregister_chrdev_region(MKDEV(major, 0), num_pins); + platform_device_put(pdev); + platform_device_unregister(pdev); + /* kfree(pdev); */ } module_init(scx200_gpio_init); module_exit(scx200_gpio_cleanup); - -/* - Local variables: - compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules" - c-basic-offset: 8 - End: -*/ diff --git a/drivers/char/specialix.c b/drivers/char/specialix.c index 1b5330299e3..d2d6b01dcd0 100644 --- a/drivers/char/specialix.c +++ b/drivers/char/specialix.c @@ -2477,7 +2477,7 @@ static int __init specialix_init(void) #endif for (i = 0; i < SX_NBOARD; i++) - sx_board[i].lock = SPIN_LOCK_UNLOCKED; + spin_lock_init(&sx_board[i].lock); if (sx_init_drivers()) { func_exit(); diff --git a/drivers/char/stallion.c b/drivers/char/stallion.c index a9c5a7230f8..bf361a5ba70 100644 --- a/drivers/char/stallion.c +++ b/drivers/char/stallion.c @@ -141,15 +141,6 @@ static char *stl_drvversion = "5.6.0"; static struct tty_driver *stl_serial; /* - * We will need to allocate a temporary write buffer for chars that - * come direct from user space. The problem is that a copy from user - * space might cause a page fault (typically on a system that is - * swapping!). All ports will share one buffer - since if the system - * is already swapping a shared buffer won't make things any worse. - */ -static char *stl_tmpwritebuf; - -/* * Define a local default termios struct. All ports will be created * with this termios initially. Basically all it defines is a raw port * at 9600, 8 data bits, 1 stop bit. @@ -363,6 +354,14 @@ static unsigned char stl_vecmap[] = { }; /* + * Lock ordering is that you may not take stallion_lock holding + * brd_lock. + */ + +static spinlock_t brd_lock; /* Guard the board mapping */ +static spinlock_t stallion_lock; /* Guard the tty driver */ + +/* * Set up enable and disable macros for the ECH boards. They require * the secondary io address space to be activated and deactivated. * This way all ECH boards can share their secondary io region. @@ -725,17 +724,7 @@ static struct class *stallion_class; static int __init stallion_module_init(void) { - unsigned long flags; - -#ifdef DEBUG - printk("init_module()\n"); -#endif - - save_flags(flags); - cli(); stl_init(); - restore_flags(flags); - return 0; } @@ -746,7 +735,6 @@ static void __exit stallion_module_exit(void) stlbrd_t *brdp; stlpanel_t *panelp; stlport_t *portp; - unsigned long flags; int i, j, k; #ifdef DEBUG @@ -756,9 +744,6 @@ static void __exit stallion_module_exit(void) printk(KERN_INFO "Unloading %s: version %s\n", stl_drvtitle, stl_drvversion); - save_flags(flags); - cli(); - /* * Free up all allocated resources used by the ports. This includes * memory and interrupts. As part of this process we will also do @@ -770,7 +755,6 @@ static void __exit stallion_module_exit(void) if (i) { printk("STALLION: failed to un-register tty driver, " "errno=%d\n", -i); - restore_flags(flags); return; } for (i = 0; i < 4; i++) { @@ -783,8 +767,6 @@ static void __exit stallion_module_exit(void) "errno=%d\n", -i); class_destroy(stallion_class); - kfree(stl_tmpwritebuf); - for (i = 0; (i < stl_nrbrds); i++) { if ((brdp = stl_brds[i]) == (stlbrd_t *) NULL) continue; @@ -814,8 +796,6 @@ static void __exit stallion_module_exit(void) kfree(brdp); stl_brds[i] = (stlbrd_t *) NULL; } - - restore_flags(flags); } module_init(stallion_module_init); @@ -948,7 +928,7 @@ static stlbrd_t *stl_allocbrd(void) brdp = kzalloc(sizeof(stlbrd_t), GFP_KERNEL); if (!brdp) { - printk("STALLION: failed to allocate memory (size=%d)\n", + printk("STALLION: failed to allocate memory (size=%Zd)\n", sizeof(stlbrd_t)); return NULL; } @@ -1066,16 +1046,17 @@ static int stl_waitcarrier(stlport_t *portp, struct file *filp) rc = 0; doclocal = 0; + spin_lock_irqsave(&stallion_lock, flags); + if (portp->tty->termios->c_cflag & CLOCAL) doclocal++; - save_flags(flags); - cli(); portp->openwaitcnt++; if (! tty_hung_up_p(filp)) portp->refcount--; for (;;) { + /* Takes brd_lock internally */ stl_setsignals(portp, 1, 1); if (tty_hung_up_p(filp) || ((portp->flags & ASYNC_INITIALIZED) == 0)) { @@ -1093,13 +1074,14 @@ static int stl_waitcarrier(stlport_t *portp, struct file *filp) rc = -ERESTARTSYS; break; } + /* FIXME */ interruptible_sleep_on(&portp->open_wait); } if (! tty_hung_up_p(filp)) portp->refcount++; portp->openwaitcnt--; - restore_flags(flags); + spin_unlock_irqrestore(&stallion_lock, flags); return rc; } @@ -1119,16 +1101,15 @@ static void stl_close(struct tty_struct *tty, struct file *filp) if (portp == (stlport_t *) NULL) return; - save_flags(flags); - cli(); + spin_lock_irqsave(&stallion_lock, flags); if (tty_hung_up_p(filp)) { - restore_flags(flags); + spin_unlock_irqrestore(&stallion_lock, flags); return; } if ((tty->count == 1) && (portp->refcount != 1)) portp->refcount = 1; if (portp->refcount-- > 1) { - restore_flags(flags); + spin_unlock_irqrestore(&stallion_lock, flags); return; } @@ -1142,11 +1123,18 @@ static void stl_close(struct tty_struct *tty, struct file *filp) * (The sc26198 has no "end-of-data" interrupt only empty FIFO) */ tty->closing = 1; + + spin_unlock_irqrestore(&stallion_lock, flags); + if (portp->closing_wait != ASYNC_CLOSING_WAIT_NONE) tty_wait_until_sent(tty, portp->closing_wait); stl_waituntilsent(tty, (HZ / 2)); + + spin_lock_irqsave(&stallion_lock, flags); portp->flags &= ~ASYNC_INITIALIZED; + spin_unlock_irqrestore(&stallion_lock, flags); + stl_disableintrs(portp); if (tty->termios->c_cflag & HUPCL) stl_setsignals(portp, 0, 0); @@ -1173,7 +1161,6 @@ static void stl_close(struct tty_struct *tty, struct file *filp) portp->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING); wake_up_interruptible(&portp->close_wait); - restore_flags(flags); } /*****************************************************************************/ @@ -1195,9 +1182,6 @@ static int stl_write(struct tty_struct *tty, const unsigned char *buf, int count (int) tty, (int) buf, count); #endif - if ((tty == (struct tty_struct *) NULL) || - (stl_tmpwritebuf == (char *) NULL)) - return 0; portp = tty->driver_data; if (portp == (stlport_t *) NULL) return 0; @@ -1302,11 +1286,6 @@ static void stl_flushchars(struct tty_struct *tty) if (portp->tx.buf == (char *) NULL) return; -#if 0 - if (tty->stopped || tty->hw_stopped || - (portp->tx.head == portp->tx.tail)) - return; -#endif stl_startrxtx(portp, -1, 1); } @@ -1977,12 +1956,14 @@ static int stl_eiointr(stlbrd_t *brdp) unsigned int iobase; int handled = 0; + spin_lock(&brd_lock); panelp = brdp->panels[0]; iobase = panelp->iobase; while (inb(brdp->iostatus) & EIO_INTRPEND) { handled = 1; (* panelp->isr)(panelp, iobase); } + spin_unlock(&brd_lock); return handled; } @@ -2168,7 +2149,7 @@ static int __init stl_initports(stlbrd_t *brdp, stlpanel_t *panelp) portp = kzalloc(sizeof(stlport_t), GFP_KERNEL); if (!portp) { printk("STALLION: failed to allocate memory " - "(size=%d)\n", sizeof(stlport_t)); + "(size=%Zd)\n", sizeof(stlport_t)); break; } @@ -2304,7 +2285,7 @@ static inline int stl_initeio(stlbrd_t *brdp) panelp = kzalloc(sizeof(stlpanel_t), GFP_KERNEL); if (!panelp) { printk(KERN_WARNING "STALLION: failed to allocate memory " - "(size=%d)\n", sizeof(stlpanel_t)); + "(size=%Zd)\n", sizeof(stlpanel_t)); return -ENOMEM; } @@ -2478,7 +2459,7 @@ static inline int stl_initech(stlbrd_t *brdp) panelp = kzalloc(sizeof(stlpanel_t), GFP_KERNEL); if (!panelp) { printk("STALLION: failed to allocate memory " - "(size=%d)\n", sizeof(stlpanel_t)); + "(size=%Zd)\n", sizeof(stlpanel_t)); break; } panelp->magic = STL_PANELMAGIC; @@ -2879,8 +2860,7 @@ static int stl_getportstats(stlport_t *portp, comstats_t __user *cp) portp->stats.lflags = 0; portp->stats.rxbuffered = 0; - save_flags(flags); - cli(); + spin_lock_irqsave(&stallion_lock, flags); if (portp->tty != (struct tty_struct *) NULL) { if (portp->tty->driver_data == portp) { portp->stats.ttystate = portp->tty->flags; @@ -2894,7 +2874,7 @@ static int stl_getportstats(stlport_t *portp, comstats_t __user *cp) } } } - restore_flags(flags); + spin_unlock_irqrestore(&stallion_lock, flags); head = portp->tx.head; tail = portp->tx.tail; @@ -3056,14 +3036,6 @@ static int __init stl_init(void) return -1; /* - * Allocate a temporary write buffer. - */ - stl_tmpwritebuf = kmalloc(STL_TXBUFSIZE, GFP_KERNEL); - if (!stl_tmpwritebuf) - printk("STALLION: failed to allocate memory (size=%d)\n", - STL_TXBUFSIZE); - -/* * Set up a character driver for per board stuff. This is mainly used * to do stats ioctls on the ports. */ @@ -3147,11 +3119,13 @@ static int stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp) unsigned int gfrcr; int chipmask, i, j; int nrchips, uartaddr, ioaddr; + unsigned long flags; #ifdef DEBUG printk("stl_panelinit(brdp=%x,panelp=%x)\n", (int) brdp, (int) panelp); #endif + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(panelp->brdnr, panelp->pagenr); /* @@ -3189,6 +3163,7 @@ static int stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp) } BRDDISABLE(panelp->brdnr); + spin_unlock_irqrestore(&brd_lock, flags); return chipmask; } @@ -3200,6 +3175,7 @@ static int stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp) static void stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp) { + unsigned long flags; #ifdef DEBUG printk("stl_cd1400portinit(brdp=%x,panelp=%x,portp=%x)\n", (int) brdp, (int) panelp, (int) portp); @@ -3209,6 +3185,7 @@ static void stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *po (portp == (stlport_t *) NULL)) return; + spin_lock_irqsave(&brd_lock, flags); portp->ioaddr = panelp->iobase + (((brdp->brdtype == BRD_ECHPCI) || (portp->portnr < 8)) ? 0 : EREG_BANKSIZE); portp->uartaddr = (portp->portnr & 0x04) << 5; @@ -3219,6 +3196,7 @@ static void stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *po stl_cd1400setreg(portp, LIVR, (portp->portnr << 3)); portp->hwid = stl_cd1400getreg(portp, GFRCR); BRDDISABLE(portp->brdnr); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -3428,8 +3406,7 @@ static void stl_cd1400setport(stlport_t *portp, struct termios *tiosp) tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP]); #endif - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_cd1400setreg(portp, CAR, (portp->portnr & 0x3)); srer = stl_cd1400getreg(portp, SRER); @@ -3466,7 +3443,7 @@ static void stl_cd1400setport(stlport_t *portp, struct termios *tiosp) portp->sigs &= ~TIOCM_CD; stl_cd1400setreg(portp, SRER, ((srer & ~sreroff) | sreron)); BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -3492,8 +3469,7 @@ static void stl_cd1400setsignals(stlport_t *portp, int dtr, int rts) if (rts > 0) msvr2 = MSVR2_RTS; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); if (rts >= 0) @@ -3501,7 +3477,7 @@ static void stl_cd1400setsignals(stlport_t *portp, int dtr, int rts) if (dtr >= 0) stl_cd1400setreg(portp, MSVR1, msvr1); BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -3520,14 +3496,13 @@ static int stl_cd1400getsignals(stlport_t *portp) printk("stl_cd1400getsignals(portp=%x)\n", (int) portp); #endif - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); msvr1 = stl_cd1400getreg(portp, MSVR1); msvr2 = stl_cd1400getreg(portp, MSVR2); BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); sigs = 0; sigs |= (msvr1 & MSVR1_DCD) ? TIOCM_CD : 0; @@ -3569,15 +3544,14 @@ static void stl_cd1400enablerxtx(stlport_t *portp, int rx, int tx) else if (rx > 0) ccr |= CCR_RXENABLE; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); stl_cd1400ccrwait(portp); stl_cd1400setreg(portp, CCR, ccr); stl_cd1400ccrwait(portp); BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -3609,8 +3583,7 @@ static void stl_cd1400startrxtx(stlport_t *portp, int rx, int tx) else if (rx > 0) sreron |= SRER_RXDATA; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); stl_cd1400setreg(portp, SRER, @@ -3618,7 +3591,7 @@ static void stl_cd1400startrxtx(stlport_t *portp, int rx, int tx) BRDDISABLE(portp->brdnr); if (tx > 0) set_bit(ASYI_TXBUSY, &portp->istate); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -3634,13 +3607,12 @@ static void stl_cd1400disableintrs(stlport_t *portp) #ifdef DEBUG printk("stl_cd1400disableintrs(portp=%x)\n", (int) portp); #endif - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); stl_cd1400setreg(portp, SRER, 0); BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -3653,8 +3625,7 @@ static void stl_cd1400sendbreak(stlport_t *portp, int len) printk("stl_cd1400sendbreak(portp=%x,len=%d)\n", (int) portp, len); #endif - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); stl_cd1400setreg(portp, SRER, @@ -3664,7 +3635,7 @@ static void stl_cd1400sendbreak(stlport_t *portp, int len) portp->brklen = len; if (len == 1) portp->stats.txbreaks++; - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -3688,8 +3659,7 @@ static void stl_cd1400flowctrl(stlport_t *portp, int state) if (tty == (struct tty_struct *) NULL) return; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); @@ -3729,7 +3699,7 @@ static void stl_cd1400flowctrl(stlport_t *portp, int state) } BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -3753,8 +3723,7 @@ static void stl_cd1400sendflow(stlport_t *portp, int state) if (tty == (struct tty_struct *) NULL) return; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); if (state) { @@ -3769,7 +3738,7 @@ static void stl_cd1400sendflow(stlport_t *portp, int state) stl_cd1400ccrwait(portp); } BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -3785,8 +3754,7 @@ static void stl_cd1400flush(stlport_t *portp) if (portp == (stlport_t *) NULL) return; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03)); stl_cd1400ccrwait(portp); @@ -3794,7 +3762,7 @@ static void stl_cd1400flush(stlport_t *portp) stl_cd1400ccrwait(portp); portp->tx.tail = portp->tx.head; BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -3833,6 +3801,7 @@ static void stl_cd1400eiointr(stlpanel_t *panelp, unsigned int iobase) (int) panelp, iobase); #endif + spin_lock(&brd_lock); outb(SVRR, iobase); svrtype = inb(iobase + EREG_DATA); if (panelp->nrports > 4) { @@ -3846,6 +3815,8 @@ static void stl_cd1400eiointr(stlpanel_t *panelp, unsigned int iobase) stl_cd1400txisr(panelp, iobase); else if (svrtype & SVRR_MDM) stl_cd1400mdmisr(panelp, iobase); + + spin_unlock(&brd_lock); } /*****************************************************************************/ @@ -4433,8 +4404,7 @@ static void stl_sc26198setport(stlport_t *portp, struct termios *tiosp) tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP]); #endif - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_sc26198setreg(portp, IMR, 0); stl_sc26198updatereg(portp, MR0, mr0); @@ -4461,7 +4431,7 @@ static void stl_sc26198setport(stlport_t *portp, struct termios *tiosp) portp->imr = (portp->imr & ~imroff) | imron; stl_sc26198setreg(portp, IMR, portp->imr); BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -4491,13 +4461,12 @@ static void stl_sc26198setsignals(stlport_t *portp, int dtr, int rts) else if (rts > 0) iopioron |= IPR_RTS; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_sc26198setreg(portp, IOPIOR, ((stl_sc26198getreg(portp, IOPIOR) & ~iopioroff) | iopioron)); BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -4516,12 +4485,11 @@ static int stl_sc26198getsignals(stlport_t *portp) printk("stl_sc26198getsignals(portp=%x)\n", (int) portp); #endif - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); ipr = stl_sc26198getreg(portp, IPR); BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); sigs = 0; sigs |= (ipr & IPR_DCD) ? 0 : TIOCM_CD; @@ -4558,13 +4526,12 @@ static void stl_sc26198enablerxtx(stlport_t *portp, int rx, int tx) else if (rx > 0) ccr |= CR_RXENABLE; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_sc26198setreg(portp, SCCR, ccr); BRDDISABLE(portp->brdnr); portp->crenable = ccr; - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -4593,15 +4560,14 @@ static void stl_sc26198startrxtx(stlport_t *portp, int rx, int tx) else if (rx > 0) imr |= IR_RXRDY | IR_RXBREAK | IR_RXWATCHDOG; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_sc26198setreg(portp, IMR, imr); BRDDISABLE(portp->brdnr); portp->imr = imr; if (tx > 0) set_bit(ASYI_TXBUSY, &portp->istate); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -4618,13 +4584,12 @@ static void stl_sc26198disableintrs(stlport_t *portp) printk("stl_sc26198disableintrs(portp=%x)\n", (int) portp); #endif - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); portp->imr = 0; stl_sc26198setreg(portp, IMR, 0); BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -4637,8 +4602,7 @@ static void stl_sc26198sendbreak(stlport_t *portp, int len) printk("stl_sc26198sendbreak(portp=%x,len=%d)\n", (int) portp, len); #endif - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); if (len == 1) { stl_sc26198setreg(portp, SCCR, CR_TXSTARTBREAK); @@ -4647,7 +4611,7 @@ static void stl_sc26198sendbreak(stlport_t *portp, int len) stl_sc26198setreg(portp, SCCR, CR_TXSTOPBREAK); } BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -4672,8 +4636,7 @@ static void stl_sc26198flowctrl(stlport_t *portp, int state) if (tty == (struct tty_struct *) NULL) return; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); if (state) { @@ -4719,7 +4682,7 @@ static void stl_sc26198flowctrl(stlport_t *portp, int state) } BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -4744,8 +4707,7 @@ static void stl_sc26198sendflow(stlport_t *portp, int state) if (tty == (struct tty_struct *) NULL) return; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); if (state) { mr0 = stl_sc26198getreg(portp, MR0); @@ -4765,7 +4727,7 @@ static void stl_sc26198sendflow(stlport_t *portp, int state) stl_sc26198setreg(portp, MR0, mr0); } BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -4781,14 +4743,13 @@ static void stl_sc26198flush(stlport_t *portp) if (portp == (stlport_t *) NULL) return; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); stl_sc26198setreg(portp, SCCR, CR_TXRESET); stl_sc26198setreg(portp, SCCR, portp->crenable); BRDDISABLE(portp->brdnr); portp->tx.tail = portp->tx.head; - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); } /*****************************************************************************/ @@ -4815,12 +4776,11 @@ static int stl_sc26198datastate(stlport_t *portp) if (test_bit(ASYI_TXBUSY, &portp->istate)) return 1; - save_flags(flags); - cli(); + spin_lock_irqsave(&brd_lock, flags); BRDENABLE(portp->brdnr, portp->pagenr); sr = stl_sc26198getreg(portp, SR); BRDDISABLE(portp->brdnr); - restore_flags(flags); + spin_unlock_irqrestore(&brd_lock, flags); return (sr & SR_TXEMPTY) ? 0 : 1; } @@ -4878,6 +4838,8 @@ static void stl_sc26198intr(stlpanel_t *panelp, unsigned int iobase) stlport_t *portp; unsigned int iack; + spin_lock(&brd_lock); + /* * Work around bug in sc26198 chip... Cannot have A6 address * line of UART high, else iack will be returned as 0. @@ -4893,6 +4855,8 @@ static void stl_sc26198intr(stlpanel_t *panelp, unsigned int iobase) stl_sc26198txisr(portp); else stl_sc26198otherisr(portp, iack); + + spin_unlock(&brd_lock); } /*****************************************************************************/ diff --git a/drivers/char/sx.c b/drivers/char/sx.c index 3b474723027..76b9107f7f8 100644 --- a/drivers/char/sx.c +++ b/drivers/char/sx.c @@ -2320,7 +2320,7 @@ static int sx_init_portstructs (int nboards, int nports) #ifdef NEW_WRITE_LOCKING port->gs.port_write_mutex = MUTEX; #endif - port->gs.driver_lock = SPIN_LOCK_UNLOCKED; + spin_lock_init(&port->gs.driver_lock); /* * Initializing wait queue */ diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c index 8b2a5996986..bd74e82d8a7 100644 --- a/drivers/char/tty_io.c +++ b/drivers/char/tty_io.c @@ -2621,10 +2621,9 @@ int tty_ioctl(struct inode * inode, struct file * file, tty->driver->break_ctl(tty, 0); return 0; case TCSBRK: /* SVID version: non-zero arg --> no break */ - /* - * XXX is the above comment correct, or the - * code below correct? Is this ioctl used at - * all by anyone? + /* non-zero arg means wait for all output data + * to be sent (performed above) but don't send break. + * This is used by the tcdrain() termios function. */ if (!arg) return send_break(tty, 250); diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index 44d1eca83a7..35e0b9ceecf 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -1497,6 +1497,7 @@ int cpufreq_update_policy(unsigned int cpu) } EXPORT_SYMBOL(cpufreq_update_policy); +#ifdef CONFIG_HOTPLUG_CPU static int cpufreq_cpu_callback(struct notifier_block *nfb, unsigned long action, void *hcpu) { @@ -1532,10 +1533,11 @@ static int cpufreq_cpu_callback(struct notifier_block *nfb, return NOTIFY_OK; } -static struct notifier_block cpufreq_cpu_notifier = +static struct notifier_block __cpuinitdata cpufreq_cpu_notifier = { .notifier_call = cpufreq_cpu_callback, }; +#endif /* CONFIG_HOTPLUG_CPU */ /********************************************************************* * REGISTER / UNREGISTER CPUFREQ DRIVER * @@ -1596,7 +1598,7 @@ int cpufreq_register_driver(struct cpufreq_driver *driver_data) } if (!ret) { - register_cpu_notifier(&cpufreq_cpu_notifier); + register_hotcpu_notifier(&cpufreq_cpu_notifier); dprintk("driver %s up and running\n", driver_data->name); cpufreq_debug_enable_ratelimit(); } @@ -1628,7 +1630,7 @@ int cpufreq_unregister_driver(struct cpufreq_driver *driver) dprintk("unregistering driver %s\n", driver->name); sysdev_driver_unregister(&cpu_sysdev_class, &cpufreq_sysdev_driver); - unregister_cpu_notifier(&cpufreq_cpu_notifier); + unregister_hotcpu_notifier(&cpufreq_cpu_notifier); spin_lock_irqsave(&cpufreq_driver_lock, flags); cpufreq_driver = NULL; diff --git a/drivers/cpufreq/cpufreq_stats.c b/drivers/cpufreq/cpufreq_stats.c index c576c0b3f45..145061b8472 100644 --- a/drivers/cpufreq/cpufreq_stats.c +++ b/drivers/cpufreq/cpufreq_stats.c @@ -350,7 +350,7 @@ __init cpufreq_stats_init(void) return ret; } - register_cpu_notifier(&cpufreq_stat_cpu_notifier); + register_hotcpu_notifier(&cpufreq_stat_cpu_notifier); lock_cpu_hotplug(); for_each_online_cpu(cpu) { cpufreq_stat_cpu_callback(&cpufreq_stat_cpu_notifier, CPU_ONLINE, @@ -368,7 +368,7 @@ __exit cpufreq_stats_exit(void) CPUFREQ_POLICY_NOTIFIER); cpufreq_unregister_notifier(¬ifier_trans_block, CPUFREQ_TRANSITION_NOTIFIER); - unregister_cpu_notifier(&cpufreq_stat_cpu_notifier); + unregister_hotcpu_notifier(&cpufreq_stat_cpu_notifier); lock_cpu_hotplug(); for_each_online_cpu(cpu) { cpufreq_stat_cpu_callback(&cpufreq_stat_cpu_notifier, CPU_DEAD, diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c index 3e0d04d5a80..8b46ef7d9ff 100644 --- a/drivers/i2c/busses/i2c-i801.c +++ b/drivers/i2c/busses/i2c-i801.c @@ -488,7 +488,7 @@ static int __devinit i801_probe(struct pci_dev *dev, const struct pci_device_id dev_err(&dev->dev, "SMBus base address uninitialized, " "upgrade BIOS\n"); err = -ENODEV; - goto exit_disable; + goto exit; } err = pci_request_region(dev, SMBBAR, i801_driver.name); @@ -496,7 +496,7 @@ static int __devinit i801_probe(struct pci_dev *dev, const struct pci_device_id dev_err(&dev->dev, "Failed to request SMBus region " "0x%lx-0x%lx\n", i801_smba, pci_resource_end(dev, SMBBAR)); - goto exit_disable; + goto exit; } pci_read_config_byte(I801_dev, SMBHSTCFG, &temp); @@ -520,11 +520,12 @@ static int __devinit i801_probe(struct pci_dev *dev, const struct pci_device_id err = i2c_add_adapter(&i801_adapter); if (err) { dev_err(&dev->dev, "Failed to add SMBus adapter\n"); - goto exit_disable; + goto exit_release; } + return 0; -exit_disable: - pci_disable_device(dev); +exit_release: + pci_release_region(dev, SMBBAR); exit: return err; } @@ -533,7 +534,10 @@ static void __devexit i801_remove(struct pci_dev *dev) { i2c_del_adapter(&i801_adapter); pci_release_region(dev, SMBBAR); - pci_disable_device(dev); + /* + * do not call pci_disable_device(dev) since it can cause hard hangs on + * some systems during power-off (eg. Fujitsu-Siemens Lifebook E8010) + */ } static struct pci_driver i801_driver = { diff --git a/drivers/ide/pci/amd74xx.c b/drivers/ide/pci/amd74xx.c index 6e9dbf4d807..85007cb12c5 100644 --- a/drivers/ide/pci/amd74xx.c +++ b/drivers/ide/pci/amd74xx.c @@ -75,6 +75,7 @@ static struct amd_ide_chip { { PCI_DEVICE_ID_NVIDIA_NFORCE_MCP51_IDE, 0x50, AMD_UDMA_133 }, { PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_IDE, 0x50, AMD_UDMA_133 }, { PCI_DEVICE_ID_NVIDIA_NFORCE_MCP61_IDE, 0x50, AMD_UDMA_133 }, + { PCI_DEVICE_ID_NVIDIA_NFORCE_MCP65_IDE, 0x50, AMD_UDMA_133 }, { PCI_DEVICE_ID_AMD_CS5536_IDE, 0x40, AMD_UDMA_100 }, { 0 } }; @@ -490,7 +491,8 @@ static ide_pci_device_t amd74xx_chipsets[] __devinitdata = { /* 15 */ DECLARE_NV_DEV("NFORCE-MCP51"), /* 16 */ DECLARE_NV_DEV("NFORCE-MCP55"), /* 17 */ DECLARE_NV_DEV("NFORCE-MCP61"), - /* 18 */ DECLARE_AMD_DEV("AMD5536"), + /* 18 */ DECLARE_NV_DEV("NFORCE-MCP65"), + /* 19 */ DECLARE_AMD_DEV("AMD5536"), }; static int __devinit amd74xx_probe(struct pci_dev *dev, const struct pci_device_id *id) @@ -528,7 +530,8 @@ static struct pci_device_id amd74xx_pci_tbl[] = { { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP51_IDE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 15 }, { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_IDE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 16 }, { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP61_IDE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 17 }, - { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_IDE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 18 }, + { PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP65_IDE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 18 }, + { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_IDE, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 19 }, { 0, }, }; MODULE_DEVICE_TABLE(pci, amd74xx_pci_tbl); diff --git a/drivers/input/input.c b/drivers/input/input.c index de2e7546b49..a90486f5e49 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -998,12 +998,13 @@ void input_unregister_device(struct input_dev *dev) sysfs_remove_group(&dev->cdev.kobj, &input_dev_caps_attr_group); sysfs_remove_group(&dev->cdev.kobj, &input_dev_id_attr_group); sysfs_remove_group(&dev->cdev.kobj, &input_dev_attr_group); - class_device_unregister(&dev->cdev); mutex_lock(&dev->mutex); dev->name = dev->phys = dev->uniq = NULL; mutex_unlock(&dev->mutex); + class_device_unregister(&dev->cdev); + input_wakeup_procfs_readers(); } EXPORT_SYMBOL(input_unregister_device); diff --git a/drivers/isdn/gigaset/common.c b/drivers/isdn/gigaset/common.c index acb7e265678..2a56bf33a67 100644 --- a/drivers/isdn/gigaset/common.c +++ b/drivers/isdn/gigaset/common.c @@ -981,7 +981,7 @@ exit: EXPORT_SYMBOL_GPL(gigaset_stop); static LIST_HEAD(drivers); -static spinlock_t driver_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(driver_lock); struct cardstate *gigaset_get_cs_by_id(int id) { diff --git a/drivers/isdn/i4l/isdn_tty.c b/drivers/isdn/i4l/isdn_tty.c index 2ac90242d26..433389daedb 100644 --- a/drivers/isdn/i4l/isdn_tty.c +++ b/drivers/isdn/i4l/isdn_tty.c @@ -82,7 +82,7 @@ isdn_tty_try_read(modem_info * info, struct sk_buff *skb) int l = skb->len; unsigned char *dp = skb->data; while (--l) { - if (*skb->data == DLE) + if (*dp == DLE) tty_insert_flip_char(tty, DLE, 0); tty_insert_flip_char(tty, *dp++, 0); } diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index fe6541326c7..9b015f9af35 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -18,7 +18,7 @@ #include <linux/leds.h> #include "leds.h" -rwlock_t leds_list_lock = RW_LOCK_UNLOCKED; +DEFINE_RWLOCK(leds_list_lock); LIST_HEAD(leds_list); EXPORT_SYMBOL_GPL(leds_list); diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 5e2cd8be119..1b1ce652396 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -26,7 +26,7 @@ /* * Nests outside led_cdev->trigger_lock */ -static rwlock_t triggers_list_lock = RW_LOCK_UNLOCKED; +static DEFINE_RWLOCK(triggers_list_lock); static LIST_HEAD(trigger_list); ssize_t led_trigger_store(struct class_device *dev, const char *buf, diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index e4290491fa9..6d532f170ce 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -445,6 +445,8 @@ endmenu # encoder / decoder chips menu "V4L USB devices" depends on USB && VIDEO_DEV +source "drivers/media/video/pvrusb2/Kconfig" + source "drivers/media/video/em28xx/Kconfig" config USB_DSBR diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 6c401b46398..353d61cfac1 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_VIDEO_SAA7134) += ir-kbd-i2c.o saa7134/ obj-$(CONFIG_VIDEO_CX88) += cx88/ obj-$(CONFIG_VIDEO_EM28XX) += em28xx/ obj-$(CONFIG_VIDEO_EM28XX) += tvp5150.o +obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/ obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o obj-$(CONFIG_VIDEO_TLV320AIC23B) += tlv320aic23b.o diff --git a/drivers/media/video/cx2341x.c b/drivers/media/video/cx2341x.c index 01b22eab572..65f00fc08fa 100644 --- a/drivers/media/video/cx2341x.c +++ b/drivers/media/video/cx2341x.c @@ -601,7 +601,7 @@ static void cx2341x_calc_audio_properties(struct cx2341x_mpeg_params *params) } int cx2341x_ext_ctrls(struct cx2341x_mpeg_params *params, - struct v4l2_ext_controls *ctrls, int cmd) + struct v4l2_ext_controls *ctrls, unsigned int cmd) { int err = 0; int i; @@ -847,22 +847,22 @@ invalid: return "<invalid>"; } -void cx2341x_log_status(struct cx2341x_mpeg_params *p, int card_id) +void cx2341x_log_status(struct cx2341x_mpeg_params *p, const char *prefix) { int is_mpeg1 = p->video_encoding == V4L2_MPEG_VIDEO_ENCODING_MPEG_1; /* Stream */ - printk(KERN_INFO "cx2341x-%d: Stream: %s\n", - card_id, + printk(KERN_INFO "%s: Stream: %s\n", + prefix, cx2341x_menu_item(p, V4L2_CID_MPEG_STREAM_TYPE)); /* Video */ - printk(KERN_INFO "cx2341x-%d: Video: %dx%d, %d fps\n", - card_id, + printk(KERN_INFO "%s: Video: %dx%d, %d fps\n", + prefix, p->width / (is_mpeg1 ? 2 : 1), p->height / (is_mpeg1 ? 2 : 1), p->is_50hz ? 25 : 30); - printk(KERN_INFO "cx2341x-%d: Video: %s, %s, %s, %d", - card_id, + printk(KERN_INFO "%s: Video: %s, %s, %s, %d", + prefix, cx2341x_menu_item(p, V4L2_CID_MPEG_VIDEO_ENCODING), cx2341x_menu_item(p, V4L2_CID_MPEG_VIDEO_ASPECT), cx2341x_menu_item(p, V4L2_CID_MPEG_VIDEO_BITRATE_MODE), @@ -871,19 +871,19 @@ void cx2341x_log_status(struct cx2341x_mpeg_params *p, int card_id) printk(", Peak %d", p->video_bitrate_peak); } printk("\n"); - printk(KERN_INFO "cx2341x-%d: Video: GOP Size %d, %d B-Frames, %sGOP Closure, %s3:2 Pulldown\n", - card_id, + printk(KERN_INFO "%s: Video: GOP Size %d, %d B-Frames, %sGOP Closure, %s3:2 Pulldown\n", + prefix, p->video_gop_size, p->video_b_frames, p->video_gop_closure ? "" : "No ", p->video_pulldown ? "" : "No "); if (p->video_temporal_decimation) { - printk(KERN_INFO "cx2341x-%d: Video: Temporal Decimation %d\n", - card_id, p->video_temporal_decimation); + printk(KERN_INFO "%s: Video: Temporal Decimation %d\n", + prefix, p->video_temporal_decimation); } /* Audio */ - printk(KERN_INFO "cx2341x-%d: Audio: %s, %s, %s, %s", - card_id, + printk(KERN_INFO "%s: Audio: %s, %s, %s, %s", + prefix, cx2341x_menu_item(p, V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ), cx2341x_menu_item(p, V4L2_CID_MPEG_AUDIO_ENCODING), cx2341x_menu_item(p, V4L2_CID_MPEG_AUDIO_L2_BITRATE), @@ -897,18 +897,18 @@ void cx2341x_log_status(struct cx2341x_mpeg_params *p, int card_id) cx2341x_menu_item(p, V4L2_CID_MPEG_AUDIO_CRC)); /* Encoding filters */ - printk(KERN_INFO "cx2341x-%d: Spatial Filter: %s, Luma %s, Chroma %s, %d\n", - card_id, + printk(KERN_INFO "%s: Spatial Filter: %s, Luma %s, Chroma %s, %d\n", + prefix, cx2341x_menu_item(p, V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE), cx2341x_menu_item(p, V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE), cx2341x_menu_item(p, V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE), p->video_spatial_filter); - printk(KERN_INFO "cx2341x-%d: Temporal Filter: %s, %d\n", - card_id, + printk(KERN_INFO "%s: Temporal Filter: %s, %d\n", + prefix, cx2341x_menu_item(p, V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE), p->video_temporal_filter); - printk(KERN_INFO "cx2341x-%d: Median Filter: %s, Luma [%d, %d], Chroma [%d, %d]\n", - card_id, + printk(KERN_INFO "%s: Median Filter: %s, Luma [%d, %d], Chroma [%d, %d]\n", + prefix, cx2341x_menu_item(p, V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE), p->video_luma_median_filter_bottom, p->video_luma_median_filter_top, diff --git a/drivers/media/video/cx88/cx88-blackbird.c b/drivers/media/video/cx88/cx88-blackbird.c index 78df66671ea..4ff81582ec5 100644 --- a/drivers/media/video/cx88/cx88-blackbird.c +++ b/drivers/media/video/cx88/cx88-blackbird.c @@ -853,6 +853,19 @@ static int mpeg_do_ioctl(struct inode *inode, struct file *file, fh->mpegq.field); return 0; } + case VIDIOC_LOG_STATUS: + { + char name[32 + 2]; + + snprintf(name, sizeof(name), "%s/2", core->name); + printk("%s/2: ============ START LOG STATUS ============\n", + core->name); + cx88_call_i2c_clients(core, VIDIOC_LOG_STATUS, 0); + cx2341x_log_status(&dev->params, name); + printk("%s/2: ============= END LOG STATUS =============\n", + core->name); + return 0; + } default: return cx88_do_ioctl(inode, file, 0, dev->core, cmd, arg, mpeg_do_ioctl); diff --git a/drivers/media/video/pvrusb2/Kconfig b/drivers/media/video/pvrusb2/Kconfig new file mode 100644 index 00000000000..7e727fe14b3 --- /dev/null +++ b/drivers/media/video/pvrusb2/Kconfig @@ -0,0 +1,62 @@ +config VIDEO_PVRUSB2 + tristate "Hauppauge WinTV-PVR USB2 support" + depends on VIDEO_V4L2 && USB && I2C && EXPERIMENTAL + select FW_LOADER + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_CX2341X + select VIDEO_SAA711X + select VIDEO_MSP3400 + ---help--- + This is a video4linux driver for Conexant 23416 based + usb2 personal video recorder devices. + + To compile this driver as a module, choose M here: the + module will be called pvrusb2 + +config VIDEO_PVRUSB2_24XXX + bool "Hauppauge WinTV-PVR USB2 support for 24xxx model series" + depends on VIDEO_PVRUSB2 && EXPERIMENTAL + select VIDEO_CX25840 + select VIDEO_WM8775 + ---help--- + This option enables inclusion of additional logic to operate + newer WinTV-PVR USB2 devices whose model number is of the + form "24xxx" (leading prefix of "24" followed by 3 digits). + To see if you may need this option, examine the white + sticker on the underside of your device. Enabling this + option will not harm support for older devices, however it + is a separate option because of the experimental nature of + this new feature. + + If you are in doubt, say N. + + Note: This feature is _very_ experimental. You have been + warned. + +config VIDEO_PVRUSB2_SYSFS + bool "pvrusb2 sysfs support (EXPERIMENTAL)" + default y + depends on VIDEO_PVRUSB2 && SYSFS && EXPERIMENTAL + ---help--- + This option enables the operation of a sysfs based + interface for query and control of the pvrusb2 driver. + + This is not generally needed for v4l applications, + although certain applications are optimized to take + advantage of this feature. + + If you are in doubt, say Y. + + Note: This feature is experimental and subject to change. + +config VIDEO_PVRUSB2_DEBUGIFC + bool "pvrusb2 debug interface" + depends on VIDEO_PVRUSB2_SYSFS + ---help--- + This option enables the inclusion of a debug interface + in the pvrusb2 driver, hosted through sysfs. + + You do not need to select this option unless you plan + on debugging the driver or performing a manual firmware + extraction. diff --git a/drivers/media/video/pvrusb2/Makefile b/drivers/media/video/pvrusb2/Makefile new file mode 100644 index 00000000000..fed603ad0a6 --- /dev/null +++ b/drivers/media/video/pvrusb2/Makefile @@ -0,0 +1,18 @@ +obj-pvrusb2-sysfs-$(CONFIG_VIDEO_PVRUSB2_SYSFS) := pvrusb2-sysfs.o +obj-pvrusb2-debugifc-$(CONFIG_VIDEO_PVRUSB2_DEBUGIFC) := pvrusb2-debugifc.o + +obj-pvrusb2-24xxx-$(CONFIG_VIDEO_PVRUSB2_24XXX) := \ + pvrusb2-cx2584x-v4l.o \ + pvrusb2-wm8775.o + +pvrusb2-objs := pvrusb2-i2c-core.o pvrusb2-i2c-cmd-v4l2.o \ + pvrusb2-audio.o pvrusb2-i2c-chips-v4l2.o \ + pvrusb2-encoder.o pvrusb2-video-v4l.o \ + pvrusb2-eeprom.o pvrusb2-tuner.o pvrusb2-demod.o \ + pvrusb2-main.o pvrusb2-hdw.o pvrusb2-v4l2.o \ + pvrusb2-ctrl.o pvrusb2-std.o \ + pvrusb2-context.o pvrusb2-io.o pvrusb2-ioread.o \ + $(obj-pvrusb2-24xxx-y) \ + $(obj-pvrusb2-sysfs-y) $(obj-pvrusb2-debugifc-y) + +obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2.o diff --git a/drivers/media/video/pvrusb2/pvrusb2-audio.c b/drivers/media/video/pvrusb2/pvrusb2-audio.c new file mode 100644 index 00000000000..313d2dcf9e4 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-audio.c @@ -0,0 +1,204 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-audio.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <linux/videodev2.h> +#include <media/msp3400.h> +#include <media/v4l2-common.h> + +struct pvr2_msp3400_handler { + struct pvr2_hdw *hdw; + struct pvr2_i2c_client *client; + struct pvr2_i2c_handler i2c_handler; + struct pvr2_audio_stat astat; + unsigned long stale_mask; +}; + + +/* This function selects the correct audio input source */ +static void set_stereo(struct pvr2_msp3400_handler *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + struct v4l2_routing route; + + pvr2_trace(PVR2_TRACE_CHIPS,"i2c msp3400 v4l2 set_stereo"); + + if (hdw->input_val == PVR2_CVAL_INPUT_TV) { + struct v4l2_tuner vt; + memset(&vt,0,sizeof(vt)); + vt.audmode = hdw->audiomode_val; + pvr2_i2c_client_cmd(ctxt->client,VIDIOC_S_TUNER,&vt); + } + + route.input = MSP_INPUT_DEFAULT; + route.output = MSP_OUTPUT(MSP_SC_IN_DSP_SCART1); + switch (hdw->input_val) { + case PVR2_CVAL_INPUT_TV: + break; + case PVR2_CVAL_INPUT_RADIO: + /* Assume that msp34xx also handle FM decoding, in which case + we're still using the tuner. */ + /* HV: actually it is more likely to be the SCART2 input if + the ivtv experience is any indication. */ + route.input = MSP_INPUT(MSP_IN_SCART2, MSP_IN_TUNER1, + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART); + break; + case PVR2_CVAL_INPUT_SVIDEO: + case PVR2_CVAL_INPUT_COMPOSITE: + /* SCART 1 input */ + route.input = MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER1, + MSP_DSP_IN_SCART, MSP_DSP_IN_SCART); + break; + } + pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_S_AUDIO_ROUTING,&route); +} + + +static int check_stereo(struct pvr2_msp3400_handler *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + return (hdw->input_dirty || + hdw->audiomode_dirty); +} + + +struct pvr2_msp3400_ops { + void (*update)(struct pvr2_msp3400_handler *); + int (*check)(struct pvr2_msp3400_handler *); +}; + + +static const struct pvr2_msp3400_ops msp3400_ops[] = { + { .update = set_stereo, .check = check_stereo}, +}; + + +static int msp3400_check(struct pvr2_msp3400_handler *ctxt) +{ + unsigned long msk; + unsigned int idx; + + for (idx = 0; idx < sizeof(msp3400_ops)/sizeof(msp3400_ops[0]); + idx++) { + msk = 1 << idx; + if (ctxt->stale_mask & msk) continue; + if (msp3400_ops[idx].check(ctxt)) { + ctxt->stale_mask |= msk; + } + } + return ctxt->stale_mask != 0; +} + + +static void msp3400_update(struct pvr2_msp3400_handler *ctxt) +{ + unsigned long msk; + unsigned int idx; + + for (idx = 0; idx < sizeof(msp3400_ops)/sizeof(msp3400_ops[0]); + idx++) { + msk = 1 << idx; + if (!(ctxt->stale_mask & msk)) continue; + ctxt->stale_mask &= ~msk; + msp3400_ops[idx].update(ctxt); + } +} + + +/* This reads back the current signal type */ +static int get_audio_status(struct pvr2_msp3400_handler *ctxt) +{ + struct v4l2_tuner vt; + int stat; + + memset(&vt,0,sizeof(vt)); + stat = pvr2_i2c_client_cmd(ctxt->client,VIDIOC_G_TUNER,&vt); + if (stat < 0) return stat; + + ctxt->hdw->flag_stereo = (vt.audmode & V4L2_TUNER_MODE_STEREO) != 0; + ctxt->hdw->flag_bilingual = + (vt.audmode & V4L2_TUNER_MODE_LANG2) != 0; + return 0; +} + + +static void pvr2_msp3400_detach(struct pvr2_msp3400_handler *ctxt) +{ + ctxt->client->handler = 0; + ctxt->hdw->audio_stat = 0; + kfree(ctxt); +} + + +static unsigned int pvr2_msp3400_describe(struct pvr2_msp3400_handler *ctxt, + char *buf,unsigned int cnt) +{ + return scnprintf(buf,cnt,"handler: pvrusb2-audio v4l2"); +} + + +const static struct pvr2_i2c_handler_functions msp3400_funcs = { + .detach = (void (*)(void *))pvr2_msp3400_detach, + .check = (int (*)(void *))msp3400_check, + .update = (void (*)(void *))msp3400_update, + .describe = (unsigned int (*)(void *,char *,unsigned int))pvr2_msp3400_describe, +}; + + +int pvr2_i2c_msp3400_setup(struct pvr2_hdw *hdw,struct pvr2_i2c_client *cp) +{ + struct pvr2_msp3400_handler *ctxt; + if (hdw->audio_stat) return 0; + if (cp->handler) return 0; + + ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL); + if (!ctxt) return 0; + memset(ctxt,0,sizeof(*ctxt)); + + ctxt->i2c_handler.func_data = ctxt; + ctxt->i2c_handler.func_table = &msp3400_funcs; + ctxt->client = cp; + ctxt->hdw = hdw; + ctxt->astat.ctxt = ctxt; + ctxt->astat.status = (int (*)(void *))get_audio_status; + ctxt->astat.detach = (void (*)(void *))pvr2_msp3400_detach; + ctxt->stale_mask = (1 << (sizeof(msp3400_ops)/ + sizeof(msp3400_ops[0]))) - 1; + cp->handler = &ctxt->i2c_handler; + hdw->audio_stat = &ctxt->astat; + pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x msp3400 V4L2 handler set up", + cp->client->addr); + return !0; +} + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-audio.h b/drivers/media/video/pvrusb2/pvrusb2-audio.h new file mode 100644 index 00000000000..536339b6884 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-audio.h @@ -0,0 +1,40 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_AUDIO_H +#define __PVRUSB2_AUDIO_H + +#include "pvrusb2-i2c-core.h" + +int pvr2_i2c_msp3400_setup(struct pvr2_hdw *,struct pvr2_i2c_client *); + +#endif /* __PVRUSB2_AUDIO_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-context.c b/drivers/media/video/pvrusb2/pvrusb2-context.c new file mode 100644 index 00000000000..40dc59871a4 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-context.c @@ -0,0 +1,230 @@ +/* + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-context.h" +#include "pvrusb2-io.h" +#include "pvrusb2-ioread.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-debug.h" +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <asm/semaphore.h> + + +static void pvr2_context_destroy(struct pvr2_context *mp) +{ + if (mp->hdw) pvr2_hdw_destroy(mp->hdw); + pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr_main id=%p",mp); + flush_workqueue(mp->workqueue); + destroy_workqueue(mp->workqueue); + kfree(mp); +} + + +static void pvr2_context_trigger_poll(struct pvr2_context *mp) +{ + queue_work(mp->workqueue,&mp->workpoll); +} + + +static void pvr2_context_poll(struct pvr2_context *mp) +{ + pvr2_context_enter(mp); do { + pvr2_hdw_poll(mp->hdw); + } while (0); pvr2_context_exit(mp); +} + + +static void pvr2_context_setup(struct pvr2_context *mp) +{ + pvr2_context_enter(mp); do { + if (!pvr2_hdw_dev_ok(mp->hdw)) break; + pvr2_hdw_setup(mp->hdw); + pvr2_hdw_setup_poll_trigger( + mp->hdw, + (void (*)(void *))pvr2_context_trigger_poll, + mp); + if (!pvr2_hdw_dev_ok(mp->hdw)) break; + if (!pvr2_hdw_init_ok(mp->hdw)) break; + mp->video_stream.stream = pvr2_hdw_get_video_stream(mp->hdw); + if (mp->setup_func) { + mp->setup_func(mp); + } + } while (0); pvr2_context_exit(mp); +} + + +struct pvr2_context *pvr2_context_create( + struct usb_interface *intf, + const struct usb_device_id *devid, + void (*setup_func)(struct pvr2_context *)) +{ + struct pvr2_context *mp = 0; + mp = kmalloc(sizeof(*mp),GFP_KERNEL); + if (!mp) goto done; + memset(mp,0,sizeof(*mp)); + pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr_main id=%p",mp); + mp->setup_func = setup_func; + mutex_init(&mp->mutex); + mp->hdw = pvr2_hdw_create(intf,devid); + if (!mp->hdw) { + pvr2_context_destroy(mp); + mp = 0; + goto done; + } + + mp->workqueue = create_singlethread_workqueue("pvrusb2"); + INIT_WORK(&mp->workinit,(void (*)(void*))pvr2_context_setup,mp); + INIT_WORK(&mp->workpoll,(void (*)(void*))pvr2_context_poll,mp); + queue_work(mp->workqueue,&mp->workinit); + done: + return mp; +} + + +void pvr2_context_enter(struct pvr2_context *mp) +{ + mutex_lock(&mp->mutex); + pvr2_trace(PVR2_TRACE_CREG,"pvr2_context_enter(id=%p)",mp); +} + + +void pvr2_context_exit(struct pvr2_context *mp) +{ + int destroy_flag = 0; + if (!(mp->mc_first || !mp->disconnect_flag)) { + destroy_flag = !0; + } + pvr2_trace(PVR2_TRACE_CREG,"pvr2_context_exit(id=%p) outside",mp); + mutex_unlock(&mp->mutex); + if (destroy_flag) pvr2_context_destroy(mp); +} + + +static void pvr2_context_run_checks(struct pvr2_context *mp) +{ + struct pvr2_channel *ch1,*ch2; + for (ch1 = mp->mc_first; ch1; ch1 = ch2) { + ch2 = ch1->mc_next; + if (ch1->check_func) { + ch1->check_func(ch1); + } + } +} + + +void pvr2_context_disconnect(struct pvr2_context *mp) +{ + pvr2_context_enter(mp); do { + pvr2_hdw_disconnect(mp->hdw); + mp->disconnect_flag = !0; + pvr2_context_run_checks(mp); + } while (0); pvr2_context_exit(mp); +} + + +void pvr2_channel_init(struct pvr2_channel *cp,struct pvr2_context *mp) +{ + cp->hdw = mp->hdw; + cp->mc_head = mp; + cp->mc_next = 0; + cp->mc_prev = mp->mc_last; + if (mp->mc_last) { + mp->mc_last->mc_next = cp; + } else { + mp->mc_first = cp; + } + mp->mc_last = cp; +} + + +static void pvr2_channel_disclaim_stream(struct pvr2_channel *cp) +{ + if (!cp->stream) return; + pvr2_stream_kill(cp->stream->stream); + cp->stream->user = 0; + cp->stream = 0; +} + + +void pvr2_channel_done(struct pvr2_channel *cp) +{ + struct pvr2_context *mp = cp->mc_head; + pvr2_channel_disclaim_stream(cp); + if (cp->mc_next) { + cp->mc_next->mc_prev = cp->mc_prev; + } else { + mp->mc_last = cp->mc_prev; + } + if (cp->mc_prev) { + cp->mc_prev->mc_next = cp->mc_next; + } else { + mp->mc_first = cp->mc_next; + } + cp->hdw = 0; +} + + +int pvr2_channel_claim_stream(struct pvr2_channel *cp, + struct pvr2_context_stream *sp) +{ + int code = 0; + pvr2_context_enter(cp->mc_head); do { + if (sp == cp->stream) break; + if (sp->user) { + code = -EBUSY; + break; + } + pvr2_channel_disclaim_stream(cp); + if (!sp) break; + sp->user = cp; + cp->stream = sp; + } while (0); pvr2_context_exit(cp->mc_head); + return code; +} + + +// This is the marker for the real beginning of a legitimate mpeg2 stream. +static char stream_sync_key[] = { + 0x00, 0x00, 0x01, 0xba, +}; + +struct pvr2_ioread *pvr2_channel_create_mpeg_stream( + struct pvr2_context_stream *sp) +{ + struct pvr2_ioread *cp; + cp = pvr2_ioread_create(); + if (!cp) return 0; + pvr2_ioread_setup(cp,sp->stream); + pvr2_ioread_set_sync_key(cp,stream_sync_key,sizeof(stream_sync_key)); + return cp; +} + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-context.h b/drivers/media/video/pvrusb2/pvrusb2-context.h new file mode 100644 index 00000000000..6327fa1f7e4 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-context.h @@ -0,0 +1,92 @@ +/* + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_BASE_H +#define __PVRUSB2_BASE_H + +#include <linux/mutex.h> +#include <linux/usb.h> +#include <linux/workqueue.h> + +struct pvr2_hdw; /* hardware interface - defined elsewhere */ +struct pvr2_stream; /* stream interface - defined elsewhere */ + +struct pvr2_context; /* All central state */ +struct pvr2_channel; /* One I/O pathway to a user */ +struct pvr2_context_stream; /* Wrapper for a stream */ +struct pvr2_crit_reg; /* Critical region pointer */ +struct pvr2_ioread; /* Low level stream structure */ + +struct pvr2_context_stream { + struct pvr2_channel *user; + struct pvr2_stream *stream; +}; + +struct pvr2_context { + struct pvr2_channel *mc_first; + struct pvr2_channel *mc_last; + struct pvr2_hdw *hdw; + struct pvr2_context_stream video_stream; + struct mutex mutex; + int disconnect_flag; + + /* Called after pvr2_context initialization is complete */ + void (*setup_func)(struct pvr2_context *); + + /* Work queue overhead for out-of-line processing */ + struct workqueue_struct *workqueue; + struct work_struct workinit; + struct work_struct workpoll; +}; + +struct pvr2_channel { + struct pvr2_context *mc_head; + struct pvr2_channel *mc_next; + struct pvr2_channel *mc_prev; + struct pvr2_context_stream *stream; + struct pvr2_hdw *hdw; + void (*check_func)(struct pvr2_channel *); +}; + +void pvr2_context_enter(struct pvr2_context *); +void pvr2_context_exit(struct pvr2_context *); + +struct pvr2_context *pvr2_context_create(struct usb_interface *intf, + const struct usb_device_id *devid, + void (*setup_func)(struct pvr2_context *)); +void pvr2_context_disconnect(struct pvr2_context *); + +void pvr2_channel_init(struct pvr2_channel *,struct pvr2_context *); +void pvr2_channel_done(struct pvr2_channel *); +int pvr2_channel_claim_stream(struct pvr2_channel *, + struct pvr2_context_stream *); +struct pvr2_ioread *pvr2_channel_create_mpeg_stream( + struct pvr2_context_stream *); + + +#endif /* __PVRUSB2_CONTEXT_H */ +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-ctrl.c b/drivers/media/video/pvrusb2/pvrusb2-ctrl.c new file mode 100644 index 00000000000..d5df9fbeba2 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-ctrl.c @@ -0,0 +1,593 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-ctrl.h" +#include "pvrusb2-hdw-internal.h" +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mutex.h> + + +/* Set the given control. */ +int pvr2_ctrl_set_value(struct pvr2_ctrl *cptr,int val) +{ + return pvr2_ctrl_set_mask_value(cptr,~0,val); +} + + +/* Set/clear specific bits of the given control. */ +int pvr2_ctrl_set_mask_value(struct pvr2_ctrl *cptr,int mask,int val) +{ + int ret = 0; + if (!cptr) return -EINVAL; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->set_value != 0) { + if (cptr->info->type == pvr2_ctl_bitmask) { + mask &= cptr->info->def.type_bitmask.valid_bits; + } else if (cptr->info->type == pvr2_ctl_int) { + if (val < cptr->info->def.type_int.min_value) { + break; + } + if (val > cptr->info->def.type_int.max_value) { + break; + } + } else if (cptr->info->type == pvr2_ctl_enum) { + if (val >= cptr->info->def.type_enum.count) { + break; + } + } else if (cptr->info->type != pvr2_ctl_bool) { + break; + } + ret = cptr->info->set_value(cptr,mask,val); + } else { + ret = -EPERM; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Get the current value of the given control. */ +int pvr2_ctrl_get_value(struct pvr2_ctrl *cptr,int *valptr) +{ + int ret = 0; + if (!cptr) return -EINVAL; + LOCK_TAKE(cptr->hdw->big_lock); do { + ret = cptr->info->get_value(cptr,valptr); + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve control's type */ +enum pvr2_ctl_type pvr2_ctrl_get_type(struct pvr2_ctrl *cptr) +{ + if (!cptr) return pvr2_ctl_int; + return cptr->info->type; +} + + +/* Retrieve control's maximum value (int type) */ +int pvr2_ctrl_get_max(struct pvr2_ctrl *cptr) +{ + int ret = 0; + if (!cptr) return 0; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->type == pvr2_ctl_int) { + ret = cptr->info->def.type_int.max_value; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve control's minimum value (int type) */ +int pvr2_ctrl_get_min(struct pvr2_ctrl *cptr) +{ + int ret = 0; + if (!cptr) return 0; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->type == pvr2_ctl_int) { + ret = cptr->info->def.type_int.min_value; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve control's default value (any type) */ +int pvr2_ctrl_get_def(struct pvr2_ctrl *cptr) +{ + int ret = 0; + if (!cptr) return 0; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->type == pvr2_ctl_int) { + ret = cptr->info->default_value; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve control's enumeration count (enum only) */ +int pvr2_ctrl_get_cnt(struct pvr2_ctrl *cptr) +{ + int ret = 0; + if (!cptr) return 0; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->type == pvr2_ctl_enum) { + ret = cptr->info->def.type_enum.count; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve control's valid mask bits (bit mask only) */ +int pvr2_ctrl_get_mask(struct pvr2_ctrl *cptr) +{ + int ret = 0; + if (!cptr) return 0; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->type == pvr2_ctl_bitmask) { + ret = cptr->info->def.type_bitmask.valid_bits; + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Retrieve the control's name */ +const char *pvr2_ctrl_get_name(struct pvr2_ctrl *cptr) +{ + if (!cptr) return 0; + return cptr->info->name; +} + + +/* Retrieve the control's desc */ +const char *pvr2_ctrl_get_desc(struct pvr2_ctrl *cptr) +{ + if (!cptr) return 0; + return cptr->info->desc; +} + + +/* Retrieve a control enumeration or bit mask value */ +int pvr2_ctrl_get_valname(struct pvr2_ctrl *cptr,int val, + char *bptr,unsigned int bmax, + unsigned int *blen) +{ + int ret = -EINVAL; + if (!cptr) return 0; + *blen = 0; + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->type == pvr2_ctl_enum) { + const char **names; + names = cptr->info->def.type_enum.value_names; + if ((val >= 0) && + (val < cptr->info->def.type_enum.count)) { + if (names[val]) { + *blen = scnprintf( + bptr,bmax,"%s", + names[val]); + } else { + *blen = 0; + } + ret = 0; + } + } else if (cptr->info->type == pvr2_ctl_bitmask) { + const char **names; + unsigned int idx; + int msk; + names = cptr->info->def.type_bitmask.bit_names; + val &= cptr->info->def.type_bitmask.valid_bits; + for (idx = 0, msk = 1; val; idx++, msk <<= 1) { + if (val & msk) { + *blen = scnprintf(bptr,bmax,"%s", + names[idx]); + ret = 0; + break; + } + } + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Return V4L ID for this control or zero if none */ +int pvr2_ctrl_get_v4lid(struct pvr2_ctrl *cptr) +{ + if (!cptr) return 0; + return cptr->info->v4l_id; +} + + +unsigned int pvr2_ctrl_get_v4lflags(struct pvr2_ctrl *cptr) +{ + unsigned int flags = 0; + + if (cptr->info->get_v4lflags) { + flags = cptr->info->get_v4lflags(cptr); + } + + if (cptr->info->set_value) { + flags &= ~V4L2_CTRL_FLAG_READ_ONLY; + } else { + flags |= V4L2_CTRL_FLAG_READ_ONLY; + } + + return flags; +} + + +/* Return true if control is writable */ +int pvr2_ctrl_is_writable(struct pvr2_ctrl *cptr) +{ + if (!cptr) return 0; + return cptr->info->set_value != 0; +} + + +/* Return true if control has custom symbolic representation */ +int pvr2_ctrl_has_custom_symbols(struct pvr2_ctrl *cptr) +{ + if (!cptr) return 0; + if (!cptr->info->val_to_sym) return 0; + if (!cptr->info->sym_to_val) return 0; + return !0; +} + + +/* Convert a given mask/val to a custom symbolic value */ +int pvr2_ctrl_custom_value_to_sym(struct pvr2_ctrl *cptr, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len) +{ + if (!cptr) return -EINVAL; + if (!cptr->info->val_to_sym) return -EINVAL; + return cptr->info->val_to_sym(cptr,mask,val,buf,maxlen,len); +} + + +/* Convert a symbolic value to a mask/value pair */ +int pvr2_ctrl_custom_sym_to_value(struct pvr2_ctrl *cptr, + const char *buf,unsigned int len, + int *maskptr,int *valptr) +{ + if (!cptr) return -EINVAL; + if (!cptr->info->sym_to_val) return -EINVAL; + return cptr->info->sym_to_val(cptr,buf,len,maskptr,valptr); +} + + +static unsigned int gen_bitmask_string(int msk,int val,int msk_only, + const char **names, + char *ptr,unsigned int len) +{ + unsigned int idx; + long sm,um; + int spcFl; + unsigned int uc,cnt; + const char *idStr; + + spcFl = 0; + uc = 0; + um = 0; + for (idx = 0, sm = 1; msk; idx++, sm <<= 1) { + if (sm & msk) { + msk &= ~sm; + idStr = names[idx]; + if (idStr) { + cnt = scnprintf(ptr,len,"%s%s%s", + (spcFl ? " " : ""), + (msk_only ? "" : + ((val & sm) ? "+" : "-")), + idStr); + ptr += cnt; len -= cnt; uc += cnt; + spcFl = !0; + } else { + um |= sm; + } + } + } + if (um) { + if (msk_only) { + cnt = scnprintf(ptr,len,"%s0x%lx", + (spcFl ? " " : ""), + um); + ptr += cnt; len -= cnt; uc += cnt; + spcFl = !0; + } else if (um & val) { + cnt = scnprintf(ptr,len,"%s+0x%lx", + (spcFl ? " " : ""), + um & val); + ptr += cnt; len -= cnt; uc += cnt; + spcFl = !0; + } else if (um & ~val) { + cnt = scnprintf(ptr,len,"%s+0x%lx", + (spcFl ? " " : ""), + um & ~val); + ptr += cnt; len -= cnt; uc += cnt; + spcFl = !0; + } + } + return uc; +} + + +static const char *boolNames[] = { + "false", + "true", + "no", + "yes", +}; + + +static int parse_token(const char *ptr,unsigned int len, + int *valptr, + const char **names,unsigned int namecnt) +{ + char buf[33]; + unsigned int slen; + unsigned int idx; + int negfl; + char *p2; + *valptr = 0; + if (!names) namecnt = 0; + for (idx = 0; idx < namecnt; idx++) { + if (!names[idx]) continue; + slen = strlen(names[idx]); + if (slen != len) continue; + if (memcmp(names[idx],ptr,slen)) continue; + *valptr = idx; + return 0; + } + negfl = 0; + if ((*ptr == '-') || (*ptr == '+')) { + negfl = (*ptr == '-'); + ptr++; len--; + } + if (len >= sizeof(buf)) return -EINVAL; + memcpy(buf,ptr,len); + buf[len] = 0; + *valptr = simple_strtol(buf,&p2,0); + if (negfl) *valptr = -(*valptr); + if (*p2) return -EINVAL; + return 1; +} + + +static int parse_mtoken(const char *ptr,unsigned int len, + int *valptr, + const char **names,int valid_bits) +{ + char buf[33]; + unsigned int slen; + unsigned int idx; + char *p2; + int msk; + *valptr = 0; + for (idx = 0, msk = 1; valid_bits; idx++, msk <<= 1) { + if (!msk & valid_bits) continue; + valid_bits &= ~msk; + if (!names[idx]) continue; + slen = strlen(names[idx]); + if (slen != len) continue; + if (memcmp(names[idx],ptr,slen)) continue; + *valptr = msk; + return 0; + } + if (len >= sizeof(buf)) return -EINVAL; + memcpy(buf,ptr,len); + buf[len] = 0; + *valptr = simple_strtol(buf,&p2,0); + if (*p2) return -EINVAL; + return 0; +} + + +static int parse_tlist(const char *ptr,unsigned int len, + int *maskptr,int *valptr, + const char **names,int valid_bits) +{ + unsigned int cnt; + int mask,val,kv,mode,ret; + mask = 0; + val = 0; + ret = 0; + while (len) { + cnt = 0; + while ((cnt < len) && + ((ptr[cnt] <= 32) || + (ptr[cnt] >= 127))) cnt++; + ptr += cnt; + len -= cnt; + mode = 0; + if ((*ptr == '-') || (*ptr == '+')) { + mode = (*ptr == '-') ? -1 : 1; + ptr++; + len--; + } + cnt = 0; + while (cnt < len) { + if (ptr[cnt] <= 32) break; + if (ptr[cnt] >= 127) break; + cnt++; + } + if (!cnt) break; + if (parse_mtoken(ptr,cnt,&kv,names,valid_bits)) { + ret = -EINVAL; + break; + } + ptr += cnt; + len -= cnt; + switch (mode) { + case 0: + mask = valid_bits; + val |= kv; + break; + case -1: + mask |= kv; + val &= ~kv; + break; + case 1: + mask |= kv; + val |= kv; + break; + default: + break; + } + } + *maskptr = mask; + *valptr = val; + return ret; +} + + +/* Convert a symbolic value to a mask/value pair */ +int pvr2_ctrl_sym_to_value(struct pvr2_ctrl *cptr, + const char *ptr,unsigned int len, + int *maskptr,int *valptr) +{ + int ret = -EINVAL; + unsigned int cnt; + + *maskptr = 0; + *valptr = 0; + + cnt = 0; + while ((cnt < len) && ((ptr[cnt] <= 32) || (ptr[cnt] >= 127))) cnt++; + len -= cnt; ptr += cnt; + cnt = 0; + while ((cnt < len) && ((ptr[len-(cnt+1)] <= 32) || + (ptr[len-(cnt+1)] >= 127))) cnt++; + len -= cnt; + + if (!len) return -EINVAL; + + LOCK_TAKE(cptr->hdw->big_lock); do { + if (cptr->info->type == pvr2_ctl_int) { + ret = parse_token(ptr,len,valptr,0,0); + if ((ret >= 0) && + ((*valptr < cptr->info->def.type_int.min_value) || + (*valptr > cptr->info->def.type_int.max_value))) { + ret = -ERANGE; + } + if (maskptr) *maskptr = ~0; + } else if (cptr->info->type == pvr2_ctl_bool) { + ret = parse_token( + ptr,len,valptr,boolNames, + sizeof(boolNames)/sizeof(boolNames[0])); + if (ret == 1) { + *valptr = *valptr ? !0 : 0; + } else if (ret == 0) { + *valptr = (*valptr & 1) ? !0 : 0; + } + if (maskptr) *maskptr = 1; + } else if (cptr->info->type == pvr2_ctl_enum) { + ret = parse_token( + ptr,len,valptr, + cptr->info->def.type_enum.value_names, + cptr->info->def.type_enum.count); + if ((ret >= 0) && + ((*valptr < 0) || + (*valptr >= cptr->info->def.type_enum.count))) { + ret = -ERANGE; + } + if (maskptr) *maskptr = ~0; + } else if (cptr->info->type == pvr2_ctl_bitmask) { + ret = parse_tlist( + ptr,len,maskptr,valptr, + cptr->info->def.type_bitmask.bit_names, + cptr->info->def.type_bitmask.valid_bits); + } + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* Convert a given mask/val to a symbolic value */ +int pvr2_ctrl_value_to_sym_internal(struct pvr2_ctrl *cptr, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len) +{ + int ret = -EINVAL; + + *len = 0; + if (cptr->info->type == pvr2_ctl_int) { + *len = scnprintf(buf,maxlen,"%d",val); + ret = 0; + } else if (cptr->info->type == pvr2_ctl_bool) { + *len = scnprintf(buf,maxlen,"%s",val ? "true" : "false"); + ret = 0; + } else if (cptr->info->type == pvr2_ctl_enum) { + const char **names; + names = cptr->info->def.type_enum.value_names; + if ((val >= 0) && + (val < cptr->info->def.type_enum.count)) { + if (names[val]) { + *len = scnprintf( + buf,maxlen,"%s", + names[val]); + } else { + *len = 0; + } + ret = 0; + } + } else if (cptr->info->type == pvr2_ctl_bitmask) { + *len = gen_bitmask_string( + val & mask & cptr->info->def.type_bitmask.valid_bits, + ~0,!0, + cptr->info->def.type_bitmask.bit_names, + buf,maxlen); + } + return ret; +} + + +/* Convert a given mask/val to a symbolic value */ +int pvr2_ctrl_value_to_sym(struct pvr2_ctrl *cptr, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len) +{ + int ret; + LOCK_TAKE(cptr->hdw->big_lock); do { + ret = pvr2_ctrl_value_to_sym_internal(cptr,mask,val, + buf,maxlen,len); + } while(0); LOCK_GIVE(cptr->hdw->big_lock); + return ret; +} + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-ctrl.h b/drivers/media/video/pvrusb2/pvrusb2-ctrl.h new file mode 100644 index 00000000000..c1680053cd6 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-ctrl.h @@ -0,0 +1,123 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_CTRL_H +#define __PVRUSB2_CTRL_H + +struct pvr2_ctrl; + +enum pvr2_ctl_type { + pvr2_ctl_int = 0, + pvr2_ctl_enum = 1, + pvr2_ctl_bitmask = 2, + pvr2_ctl_bool = 3, +}; + + +/* Set the given control. */ +int pvr2_ctrl_set_value(struct pvr2_ctrl *,int val); + +/* Set/clear specific bits of the given control. */ +int pvr2_ctrl_set_mask_value(struct pvr2_ctrl *,int mask,int val); + +/* Get the current value of the given control. */ +int pvr2_ctrl_get_value(struct pvr2_ctrl *,int *valptr); + +/* Retrieve control's type */ +enum pvr2_ctl_type pvr2_ctrl_get_type(struct pvr2_ctrl *); + +/* Retrieve control's maximum value (int type) */ +int pvr2_ctrl_get_max(struct pvr2_ctrl *); + +/* Retrieve control's minimum value (int type) */ +int pvr2_ctrl_get_min(struct pvr2_ctrl *); + +/* Retrieve control's default value (any type) */ +int pvr2_ctrl_get_def(struct pvr2_ctrl *); + +/* Retrieve control's enumeration count (enum only) */ +int pvr2_ctrl_get_cnt(struct pvr2_ctrl *); + +/* Retrieve control's valid mask bits (bit mask only) */ +int pvr2_ctrl_get_mask(struct pvr2_ctrl *); + +/* Retrieve the control's name */ +const char *pvr2_ctrl_get_name(struct pvr2_ctrl *); + +/* Retrieve the control's desc */ +const char *pvr2_ctrl_get_desc(struct pvr2_ctrl *); + +/* Retrieve a control enumeration or bit mask value */ +int pvr2_ctrl_get_valname(struct pvr2_ctrl *,int,char *,unsigned int, + unsigned int *); + +/* Return true if control is writable */ +int pvr2_ctrl_is_writable(struct pvr2_ctrl *); + +/* Return V4L flags value for control (or zero if there is no v4l control + actually under this control) */ +unsigned int pvr2_ctrl_get_v4lflags(struct pvr2_ctrl *); + +/* Return V4L ID for this control or zero if none */ +int pvr2_ctrl_get_v4lid(struct pvr2_ctrl *); + +/* Return true if control has custom symbolic representation */ +int pvr2_ctrl_has_custom_symbols(struct pvr2_ctrl *); + +/* Convert a given mask/val to a custom symbolic value */ +int pvr2_ctrl_custom_value_to_sym(struct pvr2_ctrl *, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len); + +/* Convert a symbolic value to a mask/value pair */ +int pvr2_ctrl_custom_sym_to_value(struct pvr2_ctrl *, + const char *buf,unsigned int len, + int *maskptr,int *valptr); + +/* Convert a given mask/val to a symbolic value */ +int pvr2_ctrl_value_to_sym(struct pvr2_ctrl *, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len); + +/* Convert a symbolic value to a mask/value pair */ +int pvr2_ctrl_sym_to_value(struct pvr2_ctrl *, + const char *buf,unsigned int len, + int *maskptr,int *valptr); + +/* Convert a given mask/val to a symbolic value - must already be + inside of critical region. */ +int pvr2_ctrl_value_to_sym_internal(struct pvr2_ctrl *, + int mask,int val, + char *buf,unsigned int maxlen, + unsigned int *len); + +#endif /* __PVRUSB2_CTRL_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.c b/drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.c new file mode 100644 index 00000000000..27eadaff75a --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.c @@ -0,0 +1,279 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + + This source file is specifically designed to interface with the + cx2584x, in kernels 2.6.16 or newer. + +*/ + +#include "pvrusb2-cx2584x-v4l.h" +#include "pvrusb2-video-v4l.h" +#include "pvrusb2-i2c-cmd-v4l2.h" + + +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <media/cx25840.h> +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <linux/errno.h> +#include <linux/slab.h> + +struct pvr2_v4l_cx2584x { + struct pvr2_i2c_handler handler; + struct pvr2_decoder_ctrl ctrl; + struct pvr2_i2c_client *client; + struct pvr2_hdw *hdw; + unsigned long stale_mask; +}; + + +static void set_input(struct pvr2_v4l_cx2584x *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + struct v4l2_routing route; + enum cx25840_video_input vid_input; + enum cx25840_audio_input aud_input; + + memset(&route,0,sizeof(route)); + + switch(hdw->input_val) { + case PVR2_CVAL_INPUT_TV: + vid_input = CX25840_COMPOSITE7; + aud_input = CX25840_AUDIO8; + break; + case PVR2_CVAL_INPUT_COMPOSITE: + vid_input = CX25840_COMPOSITE3; + aud_input = CX25840_AUDIO_SERIAL; + break; + case PVR2_CVAL_INPUT_SVIDEO: + vid_input = CX25840_SVIDEO1; + aud_input = CX25840_AUDIO_SERIAL; + break; + case PVR2_CVAL_INPUT_RADIO: + default: + // Just set it to be composite input for now... + vid_input = CX25840_COMPOSITE3; + aud_input = CX25840_AUDIO_SERIAL; + break; + } + + pvr2_trace(PVR2_TRACE_CHIPS,"i2c cx2584x set_input vid=0x%x aud=0x%x", + vid_input,aud_input); + route.input = (u32)vid_input; + pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_S_VIDEO_ROUTING,&route); + route.input = (u32)aud_input; + pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_S_AUDIO_ROUTING,&route); +} + + +static int check_input(struct pvr2_v4l_cx2584x *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + return hdw->input_dirty != 0; +} + + +static void set_audio(struct pvr2_v4l_cx2584x *ctxt) +{ + u32 val; + struct pvr2_hdw *hdw = ctxt->hdw; + + pvr2_trace(PVR2_TRACE_CHIPS,"i2c cx2584x set_audio %d", + hdw->srate_val); + switch (hdw->srate_val) { + default: + case V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000: + val = 48000; + break; + case V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100: + val = 44100; + break; + case V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000: + val = 32000; + break; + } + pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_AUDIO_CLOCK_FREQ,&val); +} + + +static int check_audio(struct pvr2_v4l_cx2584x *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + return hdw->srate_dirty != 0; +} + + +struct pvr2_v4l_cx2584x_ops { + void (*update)(struct pvr2_v4l_cx2584x *); + int (*check)(struct pvr2_v4l_cx2584x *); +}; + + +static const struct pvr2_v4l_cx2584x_ops decoder_ops[] = { + { .update = set_input, .check = check_input}, + { .update = set_audio, .check = check_audio}, +}; + + +static void decoder_detach(struct pvr2_v4l_cx2584x *ctxt) +{ + ctxt->client->handler = 0; + ctxt->hdw->decoder_ctrl = 0; + kfree(ctxt); +} + + +static int decoder_check(struct pvr2_v4l_cx2584x *ctxt) +{ + unsigned long msk; + unsigned int idx; + + for (idx = 0; idx < sizeof(decoder_ops)/sizeof(decoder_ops[0]); + idx++) { + msk = 1 << idx; + if (ctxt->stale_mask & msk) continue; + if (decoder_ops[idx].check(ctxt)) { + ctxt->stale_mask |= msk; + } + } + return ctxt->stale_mask != 0; +} + + +static void decoder_update(struct pvr2_v4l_cx2584x *ctxt) +{ + unsigned long msk; + unsigned int idx; + + for (idx = 0; idx < sizeof(decoder_ops)/sizeof(decoder_ops[0]); + idx++) { + msk = 1 << idx; + if (!(ctxt->stale_mask & msk)) continue; + ctxt->stale_mask &= ~msk; + decoder_ops[idx].update(ctxt); + } +} + + +static void decoder_enable(struct pvr2_v4l_cx2584x *ctxt,int fl) +{ + pvr2_trace(PVR2_TRACE_CHIPS,"i2c cx25840 decoder_enable(%d)",fl); + pvr2_v4l2_cmd_stream(ctxt->client,fl); +} + + +static int decoder_detect(struct pvr2_i2c_client *cp) +{ + int ret; + /* Attempt to query the decoder - let's see if it will answer */ + struct v4l2_queryctrl qc; + + memset(&qc,0,sizeof(qc)); + + qc.id = V4L2_CID_BRIGHTNESS; + + ret = pvr2_i2c_client_cmd(cp,VIDIOC_QUERYCTRL,&qc); + return ret == 0; /* Return true if it answered */ +} + + +static int decoder_is_tuned(struct pvr2_v4l_cx2584x *ctxt) +{ + struct v4l2_tuner vt; + int ret; + + memset(&vt,0,sizeof(vt)); + ret = pvr2_i2c_client_cmd(ctxt->client,VIDIOC_G_TUNER,&vt); + if (ret < 0) return -EINVAL; + return vt.signal ? 1 : 0; +} + + +static unsigned int decoder_describe(struct pvr2_v4l_cx2584x *ctxt, + char *buf,unsigned int cnt) +{ + return scnprintf(buf,cnt,"handler: pvrusb2-cx2584x-v4l"); +} + + +static void decoder_reset(struct pvr2_v4l_cx2584x *ctxt) +{ + int ret; + ret = pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_RESET,0); + pvr2_trace(PVR2_TRACE_CHIPS,"i2c cx25840 decoder_reset (ret=%d)",ret); +} + + +const static struct pvr2_i2c_handler_functions hfuncs = { + .detach = (void (*)(void *))decoder_detach, + .check = (int (*)(void *))decoder_check, + .update = (void (*)(void *))decoder_update, + .describe = (unsigned int (*)(void *,char *,unsigned int))decoder_describe, +}; + + +int pvr2_i2c_cx2584x_v4l_setup(struct pvr2_hdw *hdw, + struct pvr2_i2c_client *cp) +{ + struct pvr2_v4l_cx2584x *ctxt; + + if (hdw->decoder_ctrl) return 0; + if (cp->handler) return 0; + if (!decoder_detect(cp)) return 0; + + ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL); + if (!ctxt) return 0; + memset(ctxt,0,sizeof(*ctxt)); + + ctxt->handler.func_data = ctxt; + ctxt->handler.func_table = &hfuncs; + ctxt->ctrl.ctxt = ctxt; + ctxt->ctrl.detach = (void (*)(void *))decoder_detach; + ctxt->ctrl.enable = (void (*)(void *,int))decoder_enable; + ctxt->ctrl.tuned = (int (*)(void *))decoder_is_tuned; + ctxt->ctrl.force_reset = (void (*)(void*))decoder_reset; + ctxt->client = cp; + ctxt->hdw = hdw; + ctxt->stale_mask = (1 << (sizeof(decoder_ops)/ + sizeof(decoder_ops[0]))) - 1; + hdw->decoder_ctrl = &ctxt->ctrl; + cp->handler = &ctxt->handler; + pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x cx2584x V4L2 handler set up", + cp->client->addr); + return !0; +} + + + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.h b/drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.h new file mode 100644 index 00000000000..54b2844e7a7 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-cx2584x-v4l.h @@ -0,0 +1,53 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_CX2584X_V4L_H +#define __PVRUSB2_CX2584X_V4L_H + +/* + + This module connects the pvrusb2 driver to the I2C chip level + driver which handles combined device audio & video processing. + This interface is used internally by the driver; higher level code + should only interact through the interface provided by + pvrusb2-hdw.h. + +*/ + + + +#include "pvrusb2-i2c-core.h" + +int pvr2_i2c_cx2584x_v4l_setup(struct pvr2_hdw *,struct pvr2_i2c_client *); + + +#endif /* __PVRUSB2_CX2584X_V4L_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-debug.h b/drivers/media/video/pvrusb2/pvrusb2-debug.h new file mode 100644 index 00000000000..d95a8588e4f --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-debug.h @@ -0,0 +1,67 @@ +/* + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_DEBUG_H +#define __PVRUSB2_DEBUG_H + +extern int pvrusb2_debug; + +#define pvr2_trace(msk, fmt, arg...) do {if(msk & pvrusb2_debug) printk(KERN_INFO "pvrusb2: " fmt "\n", ##arg); } while (0) + +/* These are listed in *rough* order of decreasing usefulness and + increasing noise level. */ +#define PVR2_TRACE_INFO (1 << 0) // Normal messages +#define PVR2_TRACE_ERROR_LEGS (1 << 1) // error messages +#define PVR2_TRACE_TOLERANCE (1 << 2) // track tolerance-affected errors +#define PVR2_TRACE_TRAP (1 << 3) // Trap & report misbehavior from app +#define PVR2_TRACE_INIT (1 << 4) // misc initialization steps +#define PVR2_TRACE_START_STOP (1 << 5) // Streaming start / stop +#define PVR2_TRACE_CTL (1 << 6) // commit of control changes +#define PVR2_TRACE_DEBUG (1 << 7) // Temporary debug code +#define PVR2_TRACE_EEPROM (1 << 8) // eeprom parsing / report +#define PVR2_TRACE_STRUCT (1 << 9) // internal struct creation +#define PVR2_TRACE_OPEN_CLOSE (1 << 10) // application open / close +#define PVR2_TRACE_CREG (1 << 11) // Main critical region entry / exit +#define PVR2_TRACE_SYSFS (1 << 12) // Sysfs driven I/O +#define PVR2_TRACE_FIRMWARE (1 << 13) // firmware upload actions +#define PVR2_TRACE_CHIPS (1 << 14) // chip broadcast operation +#define PVR2_TRACE_I2C (1 << 15) // I2C related stuff +#define PVR2_TRACE_I2C_CMD (1 << 16) // Software commands to I2C modules +#define PVR2_TRACE_I2C_CORE (1 << 17) // I2C core debugging +#define PVR2_TRACE_I2C_TRAF (1 << 18) // I2C traffic through the adapter +#define PVR2_TRACE_V4LIOCTL (1 << 19) // v4l ioctl details +#define PVR2_TRACE_ENCODER (1 << 20) // mpeg2 encoder operation +#define PVR2_TRACE_BUF_POOL (1 << 21) // Track buffer pool management +#define PVR2_TRACE_BUF_FLOW (1 << 22) // Track buffer flow in system +#define PVR2_TRACE_DATA_FLOW (1 << 23) // Track data flow +#define PVR2_TRACE_DEBUGIFC (1 << 24) // Debug interface actions +#define PVR2_TRACE_GPIO (1 << 25) // GPIO state bit changes + + +#endif /* __PVRUSB2_HDW_INTERNAL_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-debugifc.c b/drivers/media/video/pvrusb2/pvrusb2-debugifc.c new file mode 100644 index 00000000000..586900e365f --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-debugifc.c @@ -0,0 +1,478 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/string.h> +#include <linux/slab.h> +#include "pvrusb2-debugifc.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-i2c-core.h" + +struct debugifc_mask_item { + const char *name; + unsigned long msk; +}; + +static struct debugifc_mask_item mask_items[] = { + {"ENC_FIRMWARE",(1<<PVR2_SUBSYS_B_ENC_FIRMWARE)}, + {"ENC_CFG",(1<<PVR2_SUBSYS_B_ENC_CFG)}, + {"DIG_RUN",(1<<PVR2_SUBSYS_B_DIGITIZER_RUN)}, + {"USB_RUN",(1<<PVR2_SUBSYS_B_USBSTREAM_RUN)}, + {"ENC_RUN",(1<<PVR2_SUBSYS_B_ENC_RUN)}, +}; + + +static unsigned int debugifc_count_whitespace(const char *buf, + unsigned int count) +{ + unsigned int scnt; + char ch; + + for (scnt = 0; scnt < count; scnt++) { + ch = buf[scnt]; + if (ch == ' ') continue; + if (ch == '\t') continue; + if (ch == '\n') continue; + break; + } + return scnt; +} + + +static unsigned int debugifc_count_nonwhitespace(const char *buf, + unsigned int count) +{ + unsigned int scnt; + char ch; + + for (scnt = 0; scnt < count; scnt++) { + ch = buf[scnt]; + if (ch == ' ') break; + if (ch == '\t') break; + if (ch == '\n') break; + } + return scnt; +} + + +static unsigned int debugifc_isolate_word(const char *buf,unsigned int count, + const char **wstrPtr, + unsigned int *wlenPtr) +{ + const char *wptr; + unsigned int consume_cnt = 0; + unsigned int wlen; + unsigned int scnt; + + wptr = 0; + wlen = 0; + scnt = debugifc_count_whitespace(buf,count); + consume_cnt += scnt; count -= scnt; buf += scnt; + if (!count) goto done; + + scnt = debugifc_count_nonwhitespace(buf,count); + if (!scnt) goto done; + wptr = buf; + wlen = scnt; + consume_cnt += scnt; count -= scnt; buf += scnt; + + done: + *wstrPtr = wptr; + *wlenPtr = wlen; + return consume_cnt; +} + + +static int debugifc_parse_unsigned_number(const char *buf,unsigned int count, + u32 *num_ptr) +{ + u32 result = 0; + u32 val; + int ch; + int radix = 10; + if ((count >= 2) && (buf[0] == '0') && + ((buf[1] == 'x') || (buf[1] == 'X'))) { + radix = 16; + count -= 2; + buf += 2; + } else if ((count >= 1) && (buf[0] == '0')) { + radix = 8; + } + + while (count--) { + ch = *buf++; + if ((ch >= '0') && (ch <= '9')) { + val = ch - '0'; + } else if ((ch >= 'a') && (ch <= 'f')) { + val = ch - 'a' + 10; + } else if ((ch >= 'A') && (ch <= 'F')) { + val = ch - 'A' + 10; + } else { + return -EINVAL; + } + if (val >= radix) return -EINVAL; + result *= radix; + result += val; + } + *num_ptr = result; + return 0; +} + + +static int debugifc_match_keyword(const char *buf,unsigned int count, + const char *keyword) +{ + unsigned int kl; + if (!keyword) return 0; + kl = strlen(keyword); + if (kl != count) return 0; + return !memcmp(buf,keyword,kl); +} + + +static unsigned long debugifc_find_mask(const char *buf,unsigned int count) +{ + struct debugifc_mask_item *mip; + unsigned int idx; + for (idx = 0; idx < sizeof(mask_items)/sizeof(mask_items[0]); idx++) { + mip = mask_items + idx; + if (debugifc_match_keyword(buf,count,mip->name)) { + return mip->msk; + } + } + return 0; +} + + +static int debugifc_print_mask(char *buf,unsigned int sz, + unsigned long msk,unsigned long val) +{ + struct debugifc_mask_item *mip; + unsigned int idx; + int bcnt = 0; + int ccnt; + for (idx = 0; idx < sizeof(mask_items)/sizeof(mask_items[0]); idx++) { + mip = mask_items + idx; + if (!(mip->msk & msk)) continue; + ccnt = scnprintf(buf,sz,"%s%c%s", + (bcnt ? " " : ""), + ((mip->msk & val) ? '+' : '-'), + mip->name); + sz -= ccnt; + buf += ccnt; + bcnt += ccnt; + } + return bcnt; +} + +static unsigned int debugifc_parse_subsys_mask(const char *buf, + unsigned int count, + unsigned long *mskPtr, + unsigned long *valPtr) +{ + const char *wptr; + unsigned int consume_cnt = 0; + unsigned int scnt; + unsigned int wlen; + int mode; + unsigned long m1,msk,val; + + msk = 0; + val = 0; + + while (count) { + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (!scnt) break; + consume_cnt += scnt; count -= scnt; buf += scnt; + if (!wptr) break; + + mode = 0; + if (wlen) switch (wptr[0]) { + case '+': + wptr++; + wlen--; + break; + case '-': + mode = 1; + wptr++; + wlen--; + break; + } + if (!wlen) continue; + m1 = debugifc_find_mask(wptr,wlen); + if (!m1) break; + msk |= m1; + if (!mode) val |= m1; + } + *mskPtr = msk; + *valPtr = val; + return consume_cnt; +} + + +int pvr2_debugifc_print_info(struct pvr2_hdw *hdw,char *buf,unsigned int acnt) +{ + int bcnt = 0; + int ccnt; + struct pvr2_hdw_debug_info dbg; + + pvr2_hdw_get_debug_info(hdw,&dbg); + + ccnt = scnprintf(buf,acnt,"big lock %s; ctl lock %s", + (dbg.big_lock_held ? "held" : "free"), + (dbg.ctl_lock_held ? "held" : "free")); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + if (dbg.ctl_lock_held) { + ccnt = scnprintf(buf,acnt,"; cmd_state=%d cmd_code=%d" + " cmd_wlen=%d cmd_rlen=%d" + " wpend=%d rpend=%d tmout=%d rstatus=%d" + " wstatus=%d", + dbg.cmd_debug_state,dbg.cmd_code, + dbg.cmd_debug_write_len, + dbg.cmd_debug_read_len, + dbg.cmd_debug_write_pend, + dbg.cmd_debug_read_pend, + dbg.cmd_debug_timeout, + dbg.cmd_debug_rstatus, + dbg.cmd_debug_wstatus); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + } + ccnt = scnprintf(buf,acnt,"\n"); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = scnprintf( + buf,acnt,"driver flags: %s %s %s\n", + (dbg.flag_init_ok ? "initialized" : "uninitialized"), + (dbg.flag_ok ? "ok" : "fail"), + (dbg.flag_disconnected ? "disconnected" : "connected")); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = scnprintf(buf,acnt,"Subsystems enabled / configured: "); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = debugifc_print_mask(buf,acnt,dbg.subsys_flags,dbg.subsys_flags); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = scnprintf(buf,acnt,"\n"); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = scnprintf(buf,acnt,"Subsystems disabled / unconfigured: "); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = debugifc_print_mask(buf,acnt,~dbg.subsys_flags,dbg.subsys_flags); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = scnprintf(buf,acnt,"\n"); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + + ccnt = scnprintf(buf,acnt,"Attached I2C modules:\n"); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = pvr2_i2c_report(hdw,buf,acnt); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + + return bcnt; +} + + +int pvr2_debugifc_print_status(struct pvr2_hdw *hdw, + char *buf,unsigned int acnt) +{ + int bcnt = 0; + int ccnt; + unsigned long msk; + int ret; + u32 gpio_dir,gpio_in,gpio_out; + + ret = pvr2_hdw_is_hsm(hdw); + ccnt = scnprintf(buf,acnt,"USB link speed: %s\n", + (ret < 0 ? "FAIL" : (ret ? "high" : "full"))); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + + gpio_dir = 0; gpio_in = 0; gpio_out = 0; + pvr2_hdw_gpio_get_dir(hdw,&gpio_dir); + pvr2_hdw_gpio_get_out(hdw,&gpio_out); + pvr2_hdw_gpio_get_in(hdw,&gpio_in); + ccnt = scnprintf(buf,acnt,"GPIO state: dir=0x%x in=0x%x out=0x%x\n", + gpio_dir,gpio_in,gpio_out); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + + ccnt = scnprintf(buf,acnt,"Streaming is %s\n", + pvr2_hdw_get_streaming(hdw) ? "on" : "off"); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + + msk = pvr2_hdw_subsys_get(hdw); + ccnt = scnprintf(buf,acnt,"Subsystems enabled / configured: "); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = debugifc_print_mask(buf,acnt,msk,msk); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = scnprintf(buf,acnt,"\n"); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = scnprintf(buf,acnt,"Subsystems disabled / unconfigured: "); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = debugifc_print_mask(buf,acnt,~msk,msk); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = scnprintf(buf,acnt,"\n"); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + + msk = pvr2_hdw_subsys_stream_get(hdw); + ccnt = scnprintf(buf,acnt,"Subsystems stopped on stream shutdown: "); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = debugifc_print_mask(buf,acnt,msk,msk); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + ccnt = scnprintf(buf,acnt,"\n"); + bcnt += ccnt; acnt -= ccnt; buf += ccnt; + + return bcnt; +} + + +int pvr2_debugifc_do1cmd(struct pvr2_hdw *hdw,const char *buf, + unsigned int count) +{ + const char *wptr; + unsigned int wlen; + unsigned int scnt; + + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (!scnt) return 0; + count -= scnt; buf += scnt; + if (!wptr) return 0; + + pvr2_trace(PVR2_TRACE_DEBUGIFC,"debugifc cmd: \"%.*s\"",wlen,wptr); + if (debugifc_match_keyword(wptr,wlen,"reset")) { + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (!scnt) return -EINVAL; + count -= scnt; buf += scnt; + if (!wptr) return -EINVAL; + if (debugifc_match_keyword(wptr,wlen,"cpu")) { + pvr2_hdw_cpureset_assert(hdw,!0); + pvr2_hdw_cpureset_assert(hdw,0); + return 0; + } else if (debugifc_match_keyword(wptr,wlen,"bus")) { + pvr2_hdw_device_reset(hdw); + } else if (debugifc_match_keyword(wptr,wlen,"soft")) { + return pvr2_hdw_cmd_powerup(hdw); + } else if (debugifc_match_keyword(wptr,wlen,"deep")) { + return pvr2_hdw_cmd_deep_reset(hdw); + } else if (debugifc_match_keyword(wptr,wlen,"firmware")) { + return pvr2_upload_firmware2(hdw); + } else if (debugifc_match_keyword(wptr,wlen,"decoder")) { + return pvr2_hdw_cmd_decoder_reset(hdw); + } + return -EINVAL; + } else if (debugifc_match_keyword(wptr,wlen,"subsys_flags")) { + unsigned long msk = 0; + unsigned long val = 0; + if (debugifc_parse_subsys_mask(buf,count,&msk,&val) != count) { + pvr2_trace(PVR2_TRACE_DEBUGIFC, + "debugifc parse error on subsys mask"); + return -EINVAL; + } + pvr2_hdw_subsys_bit_chg(hdw,msk,val); + return 0; + } else if (debugifc_match_keyword(wptr,wlen,"stream_flags")) { + unsigned long msk = 0; + unsigned long val = 0; + if (debugifc_parse_subsys_mask(buf,count,&msk,&val) != count) { + pvr2_trace(PVR2_TRACE_DEBUGIFC, + "debugifc parse error on stream mask"); + return -EINVAL; + } + pvr2_hdw_subsys_stream_bit_chg(hdw,msk,val); + return 0; + } else if (debugifc_match_keyword(wptr,wlen,"cpufw")) { + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (!scnt) return -EINVAL; + count -= scnt; buf += scnt; + if (!wptr) return -EINVAL; + if (debugifc_match_keyword(wptr,wlen,"fetch")) { + pvr2_hdw_cpufw_set_enabled(hdw,!0); + return 0; + } else if (debugifc_match_keyword(wptr,wlen,"done")) { + pvr2_hdw_cpufw_set_enabled(hdw,0); + return 0; + } else { + return -EINVAL; + } + } else if (debugifc_match_keyword(wptr,wlen,"gpio")) { + int dir_fl = 0; + int ret; + u32 msk,val; + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (!scnt) return -EINVAL; + count -= scnt; buf += scnt; + if (!wptr) return -EINVAL; + if (debugifc_match_keyword(wptr,wlen,"dir")) { + dir_fl = !0; + } else if (!debugifc_match_keyword(wptr,wlen,"out")) { + return -EINVAL; + } + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (!scnt) return -EINVAL; + count -= scnt; buf += scnt; + if (!wptr) return -EINVAL; + ret = debugifc_parse_unsigned_number(wptr,wlen,&msk); + if (ret) return ret; + scnt = debugifc_isolate_word(buf,count,&wptr,&wlen); + if (wptr) { + ret = debugifc_parse_unsigned_number(wptr,wlen,&val); + if (ret) return ret; + } else { + val = msk; + msk = 0xffffffff; + } + if (dir_fl) { + ret = pvr2_hdw_gpio_chg_dir(hdw,msk,val); + } else { + ret = pvr2_hdw_gpio_chg_out(hdw,msk,val); + } + return ret; + } + pvr2_trace(PVR2_TRACE_DEBUGIFC, + "debugifc failed to recognize cmd: \"%.*s\"",wlen,wptr); + return -EINVAL; +} + + +int pvr2_debugifc_docmd(struct pvr2_hdw *hdw,const char *buf, + unsigned int count) +{ + unsigned int bcnt = 0; + int ret; + + while (count) { + for (bcnt = 0; bcnt < count; bcnt++) { + if (buf[bcnt] == '\n') break; + } + + ret = pvr2_debugifc_do1cmd(hdw,buf,bcnt); + if (ret < 0) return ret; + if (bcnt < count) bcnt++; + buf += bcnt; + count -= bcnt; + } + + return 0; +} + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-debugifc.h b/drivers/media/video/pvrusb2/pvrusb2-debugifc.h new file mode 100644 index 00000000000..990b02d35d3 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-debugifc.h @@ -0,0 +1,53 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_DEBUGIFC_H +#define __PVRUSB2_DEBUGIFC_H + +struct pvr2_hdw; + +/* Non-intrusively print some useful debugging info from inside the + driver. This should work even if the driver appears to be + wedged. */ +int pvr2_debugifc_print_info(struct pvr2_hdw *, + char *buf_ptr,unsigned int buf_size); + +/* Print general status of driver. This will also trigger a probe of + the USB link. Unlike print_info(), this one synchronizes with the + driver so the information should be self-consistent (but it will + hang if the driver is wedged). */ +int pvr2_debugifc_print_status(struct pvr2_hdw *, + char *buf_ptr,unsigned int buf_size); + +/* Parse a string command into a driver action. */ +int pvr2_debugifc_docmd(struct pvr2_hdw *, + const char *buf_ptr,unsigned int buf_size); + +#endif /* __PVRUSB2_DEBUGIFC_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-demod.c b/drivers/media/video/pvrusb2/pvrusb2-demod.c new file mode 100644 index 00000000000..9686569a11f --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-demod.c @@ -0,0 +1,126 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2.h" +#include "pvrusb2-util.h" +#include "pvrusb2-demod.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <linux/videodev2.h> +#include <media/tuner.h> +#include <media/v4l2-common.h> + + +struct pvr2_demod_handler { + struct pvr2_hdw *hdw; + struct pvr2_i2c_client *client; + struct pvr2_i2c_handler i2c_handler; + int type_update_fl; +}; + + +static void set_config(struct pvr2_demod_handler *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + int cfg = 0; + + switch (hdw->tuner_type) { + case TUNER_PHILIPS_FM1216ME_MK3: + case TUNER_PHILIPS_FM1236_MK3: + cfg = TDA9887_PORT1_ACTIVE|TDA9887_PORT2_ACTIVE; + break; + default: + break; + } + pvr2_trace(PVR2_TRACE_CHIPS,"i2c demod set_config(0x%x)",cfg); + pvr2_i2c_client_cmd(ctxt->client,TDA9887_SET_CONFIG,&cfg); + ctxt->type_update_fl = 0; +} + + +static int demod_check(struct pvr2_demod_handler *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + if (hdw->tuner_updated) ctxt->type_update_fl = !0; + return ctxt->type_update_fl != 0; +} + + +static void demod_update(struct pvr2_demod_handler *ctxt) +{ + if (ctxt->type_update_fl) set_config(ctxt); +} + + +static void demod_detach(struct pvr2_demod_handler *ctxt) +{ + ctxt->client->handler = 0; + kfree(ctxt); +} + + +static unsigned int demod_describe(struct pvr2_demod_handler *ctxt,char *buf,unsigned int cnt) +{ + return scnprintf(buf,cnt,"handler: pvrusb2-demod"); +} + + +const static struct pvr2_i2c_handler_functions tuner_funcs = { + .detach = (void (*)(void *))demod_detach, + .check = (int (*)(void *))demod_check, + .update = (void (*)(void *))demod_update, + .describe = (unsigned int (*)(void *,char *,unsigned int))demod_describe, +}; + + +int pvr2_i2c_demod_setup(struct pvr2_hdw *hdw,struct pvr2_i2c_client *cp) +{ + struct pvr2_demod_handler *ctxt; + if (cp->handler) return 0; + + ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL); + if (!ctxt) return 0; + memset(ctxt,0,sizeof(*ctxt)); + + ctxt->i2c_handler.func_data = ctxt; + ctxt->i2c_handler.func_table = &tuner_funcs; + ctxt->type_update_fl = !0; + ctxt->client = cp; + ctxt->hdw = hdw; + cp->handler = &ctxt->i2c_handler; + pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x tda9887 V4L2 handler set up", + cp->client->addr); + return !0; +} + + + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-demod.h b/drivers/media/video/pvrusb2/pvrusb2-demod.h new file mode 100644 index 00000000000..4c4e40ffbf0 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-demod.h @@ -0,0 +1,38 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_DEMOD_H +#define __PVRUSB2_DEMOD_H + +#include "pvrusb2-i2c-core.h" + +int pvr2_i2c_demod_setup(struct pvr2_hdw *,struct pvr2_i2c_client *); + +#endif /* __PVRUSB2_DEMOD_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-eeprom.c b/drivers/media/video/pvrusb2/pvrusb2-eeprom.c new file mode 100644 index 00000000000..94d383ff988 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-eeprom.c @@ -0,0 +1,164 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-eeprom.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" + +#define trace_eeprom(...) pvr2_trace(PVR2_TRACE_EEPROM,__VA_ARGS__) + + + +/* + + Read and analyze data in the eeprom. Use tveeprom to figure out + the packet structure, since this is another Hauppauge device and + internally it has a family resemblence to ivtv-type devices + +*/ + +#include <media/tveeprom.h> + +/* We seem to only be interested in the last 128 bytes of the EEPROM */ +#define EEPROM_SIZE 128 + +/* Grab EEPROM contents, needed for direct method. */ +static u8 *pvr2_eeprom_fetch(struct pvr2_hdw *hdw) +{ + struct i2c_msg msg[2]; + u8 *eeprom; + u8 iadd[2]; + u8 addr; + u16 eepromSize; + unsigned int offs; + int ret; + int mode16 = 0; + unsigned pcnt,tcnt; + eeprom = kmalloc(EEPROM_SIZE,GFP_KERNEL); + if (!eeprom) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to allocate memory" + " required to read eeprom"); + return 0; + } + + trace_eeprom("Value for eeprom addr from controller was 0x%x", + hdw->eeprom_addr); + addr = hdw->eeprom_addr; + /* Seems that if the high bit is set, then the *real* eeprom + address is shifted right now bit position (noticed this in + newer PVR USB2 hardware) */ + if (addr & 0x80) addr >>= 1; + + /* FX2 documentation states that a 16bit-addressed eeprom is + expected if the I2C address is an odd number (yeah, this is + strange but it's what they do) */ + mode16 = (addr & 1); + eepromSize = (mode16 ? 4096 : 256); + trace_eeprom("Examining %d byte eeprom at location 0x%x" + " using %d bit addressing",eepromSize,addr, + mode16 ? 16 : 8); + + msg[0].addr = addr; + msg[0].flags = 0; + msg[0].len = mode16 ? 2 : 1; + msg[0].buf = iadd; + msg[1].addr = addr; + msg[1].flags = I2C_M_RD; + + /* We have to do the actual eeprom data fetch ourselves, because + (1) we're only fetching part of the eeprom, and (2) if we were + getting the whole thing our I2C driver can't grab it in one + pass - which is what tveeprom is otherwise going to attempt */ + memset(eeprom,0,EEPROM_SIZE); + for (tcnt = 0; tcnt < EEPROM_SIZE; tcnt += pcnt) { + pcnt = 16; + if (pcnt + tcnt > EEPROM_SIZE) pcnt = EEPROM_SIZE-tcnt; + offs = tcnt + (eepromSize - EEPROM_SIZE); + if (mode16) { + iadd[0] = offs >> 8; + iadd[1] = offs; + } else { + iadd[0] = offs; + } + msg[1].len = pcnt; + msg[1].buf = eeprom+tcnt; + if ((ret = i2c_transfer( + &hdw->i2c_adap, + msg,sizeof(msg)/sizeof(msg[0]))) != 2) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "eeprom fetch set offs err=%d",ret); + kfree(eeprom); + return 0; + } + } + return eeprom; +} + + +/* Directly call eeprom analysis function within tveeprom. */ +int pvr2_eeprom_analyze(struct pvr2_hdw *hdw) +{ + u8 *eeprom; + struct tveeprom tvdata; + + memset(&tvdata,0,sizeof(tvdata)); + + eeprom = pvr2_eeprom_fetch(hdw); + if (!eeprom) return -EINVAL; + + { + struct i2c_client fake_client; + /* Newer version expects a useless client interface */ + fake_client.addr = hdw->eeprom_addr; + fake_client.adapter = &hdw->i2c_adap; + tveeprom_hauppauge_analog(&fake_client,&tvdata,eeprom); + } + + trace_eeprom("eeprom assumed v4l tveeprom module"); + trace_eeprom("eeprom direct call results:"); + trace_eeprom("has_radio=%d",tvdata.has_radio); + trace_eeprom("tuner_type=%d",tvdata.tuner_type); + trace_eeprom("tuner_formats=0x%x",tvdata.tuner_formats); + trace_eeprom("audio_processor=%d",tvdata.audio_processor); + trace_eeprom("model=%d",tvdata.model); + trace_eeprom("revision=%d",tvdata.revision); + trace_eeprom("serial_number=%d",tvdata.serial_number); + trace_eeprom("rev_str=%s",tvdata.rev_str); + hdw->tuner_type = tvdata.tuner_type; + hdw->serial_number = tvdata.serial_number; + hdw->std_mask_eeprom = tvdata.tuner_formats; + + kfree(eeprom); + + return 0; +} + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-eeprom.h b/drivers/media/video/pvrusb2/pvrusb2-eeprom.h new file mode 100644 index 00000000000..84242975dea --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-eeprom.h @@ -0,0 +1,40 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_EEPROM_H +#define __PVRUSB2_EEPROM_H + +struct pvr2_hdw; + +int pvr2_eeprom_analyze(struct pvr2_hdw *); + +#endif /* __PVRUSB2_EEPROM_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-encoder.c b/drivers/media/video/pvrusb2/pvrusb2-encoder.c new file mode 100644 index 00000000000..2cc31695b43 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-encoder.c @@ -0,0 +1,418 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/device.h> // for linux/firmware.h +#include <linux/firmware.h> +#include "pvrusb2-util.h" +#include "pvrusb2-encoder.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" + + + +/* Firmware mailbox flags - definitions found from ivtv */ +#define IVTV_MBOX_FIRMWARE_DONE 0x00000004 +#define IVTV_MBOX_DRIVER_DONE 0x00000002 +#define IVTV_MBOX_DRIVER_BUSY 0x00000001 + + +static int pvr2_encoder_write_words(struct pvr2_hdw *hdw, + const u32 *data, unsigned int dlen) +{ + unsigned int idx; + int ret; + unsigned int offs = 0; + unsigned int chunkCnt; + + /* + + Format: First byte must be 0x01. Remaining 32 bit words are + spread out into chunks of 7 bytes each, little-endian ordered, + offset at zero within each 2 blank bytes following and a + single byte that is 0x44 plus the offset of the word. Repeat + request for additional words, with offset adjusted + accordingly. + + */ + while (dlen) { + chunkCnt = 8; + if (chunkCnt > dlen) chunkCnt = dlen; + memset(hdw->cmd_buffer,0,sizeof(hdw->cmd_buffer)); + hdw->cmd_buffer[0] = 0x01; + for (idx = 0; idx < chunkCnt; idx++) { + hdw->cmd_buffer[1+(idx*7)+6] = 0x44 + idx + offs; + PVR2_DECOMPOSE_LE(hdw->cmd_buffer, 1+(idx*7), + data[idx]); + } + ret = pvr2_send_request(hdw, + hdw->cmd_buffer,1+(chunkCnt*7), + 0,0); + if (ret) return ret; + data += chunkCnt; + dlen -= chunkCnt; + offs += chunkCnt; + } + + return 0; +} + + +static int pvr2_encoder_read_words(struct pvr2_hdw *hdw,int statusFl, + u32 *data, unsigned int dlen) +{ + unsigned int idx; + int ret; + unsigned int offs = 0; + unsigned int chunkCnt; + + /* + + Format: First byte must be 0x02 (status check) or 0x28 (read + back block of 32 bit words). Next 6 bytes must be zero, + followed by a single byte of 0x44+offset for portion to be + read. Returned data is packed set of 32 bits words that were + read. + + */ + + while (dlen) { + chunkCnt = 16; + if (chunkCnt > dlen) chunkCnt = dlen; + memset(hdw->cmd_buffer,0,sizeof(hdw->cmd_buffer)); + hdw->cmd_buffer[0] = statusFl ? 0x02 : 0x28; + hdw->cmd_buffer[7] = 0x44 + offs; + ret = pvr2_send_request(hdw, + hdw->cmd_buffer,8, + hdw->cmd_buffer,chunkCnt * 4); + if (ret) return ret; + + for (idx = 0; idx < chunkCnt; idx++) { + data[idx] = PVR2_COMPOSE_LE(hdw->cmd_buffer,idx*4); + } + data += chunkCnt; + dlen -= chunkCnt; + offs += chunkCnt; + } + + return 0; +} + + +/* This prototype is set up to be compatible with the + cx2341x_mbox_func prototype in cx2341x.h, which should be in + kernels 2.6.18 or later. We do this so that we can enable + cx2341x.ko to write to our encoder (by handing it a pointer to this + function). For earlier kernels this doesn't really matter. */ +static int pvr2_encoder_cmd(void *ctxt, + int cmd, + int arg_cnt_send, + int arg_cnt_recv, + u32 *argp) +{ + unsigned int poll_count; + int ret = 0; + unsigned int idx; + /* These sizes look to be limited by the FX2 firmware implementation */ + u32 wrData[16]; + u32 rdData[16]; + struct pvr2_hdw *hdw = (struct pvr2_hdw *)ctxt; + + + /* + + The encoder seems to speak entirely using blocks 32 bit words. + In ivtv driver terms, this is a mailbox which we populate with + data and watch what the hardware does with it. The first word + is a set of flags used to control the transaction, the second + word is the command to execute, the third byte is zero (ivtv + driver suggests that this is some kind of return value), and + the fourth byte is a specified timeout (windows driver always + uses 0x00060000 except for one case when it is zero). All + successive words are the argument words for the command. + + First, write out the entire set of words, with the first word + being zero. + + Next, write out just the first word again, but set it to + IVTV_MBOX_DRIVER_DONE | IVTV_DRIVER_BUSY this time (which + probably means "go"). + + Next, read back 16 words as status. Check the first word, + which should have IVTV_MBOX_FIRMWARE_DONE set. If however + that bit is not set, then the command isn't done so repeat the + read. + + Next, read back 32 words and compare with the original + arugments. Hopefully they will match. + + Finally, write out just the first word again, but set it to + 0x0 this time (which probably means "idle"). + + */ + + if (arg_cnt_send > (sizeof(wrData)/sizeof(wrData[0]))-4) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Failed to write cx23416 command" + " - too many input arguments" + " (was given %u limit %u)", + arg_cnt_send, + (unsigned int)(sizeof(wrData)/sizeof(wrData[0])) - 4); + return -EINVAL; + } + + if (arg_cnt_recv > (sizeof(rdData)/sizeof(rdData[0]))-4) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Failed to write cx23416 command" + " - too many return arguments" + " (was given %u limit %u)", + arg_cnt_recv, + (unsigned int)(sizeof(rdData)/sizeof(rdData[0])) - 4); + return -EINVAL; + } + + + LOCK_TAKE(hdw->ctl_lock); do { + + wrData[0] = 0; + wrData[1] = cmd; + wrData[2] = 0; + wrData[3] = 0x00060000; + for (idx = 0; idx < arg_cnt_send; idx++) { + wrData[idx+4] = argp[idx]; + } + for (; idx < (sizeof(wrData)/sizeof(wrData[0]))-4; idx++) { + wrData[idx+4] = 0; + } + + ret = pvr2_encoder_write_words(hdw,wrData,idx); + if (ret) break; + wrData[0] = IVTV_MBOX_DRIVER_DONE|IVTV_MBOX_DRIVER_BUSY; + ret = pvr2_encoder_write_words(hdw,wrData,1); + if (ret) break; + poll_count = 0; + while (1) { + if (poll_count < 10000000) poll_count++; + ret = pvr2_encoder_read_words(hdw,!0,rdData,1); + if (ret) break; + if (rdData[0] & IVTV_MBOX_FIRMWARE_DONE) { + break; + } + if (poll_count == 100) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "***WARNING*** device's encoder" + " appears to be stuck" + " (status=0%08x)",rdData[0]); + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Encoder command: 0x%02x",cmd); + for (idx = 4; idx < arg_cnt_send; idx++) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Encoder arg%d: 0x%08x", + idx-3,wrData[idx]); + } + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Giving up waiting." + " It is likely that" + " this is a bad idea..."); + ret = -EBUSY; + break; + } + } + if (ret) break; + wrData[0] = 0x7; + ret = pvr2_encoder_read_words( + hdw,0,rdData, + sizeof(rdData)/sizeof(rdData[0])); + if (ret) break; + for (idx = 0; idx < arg_cnt_recv; idx++) { + argp[idx] = rdData[idx+4]; + } + + wrData[0] = 0x0; + ret = pvr2_encoder_write_words(hdw,wrData,1); + if (ret) break; + + } while(0); LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + + +static int pvr2_encoder_vcmd(struct pvr2_hdw *hdw, int cmd, + int args, ...) +{ + va_list vl; + unsigned int idx; + u32 data[12]; + + if (args > sizeof(data)/sizeof(data[0])) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Failed to write cx23416 command" + " - too many arguments" + " (was given %u limit %u)", + args,(unsigned int)(sizeof(data)/sizeof(data[0]))); + return -EINVAL; + } + + va_start(vl, args); + for (idx = 0; idx < args; idx++) { + data[idx] = va_arg(vl, u32); + } + va_end(vl); + + return pvr2_encoder_cmd(hdw,cmd,args,0,data); +} + +int pvr2_encoder_configure(struct pvr2_hdw *hdw) +{ + int ret; + pvr2_trace(PVR2_TRACE_ENCODER,"pvr2_encoder_configure" + " (cx2341x module)"); + hdw->enc_ctl_state.port = CX2341X_PORT_STREAMING; + hdw->enc_ctl_state.width = hdw->res_hor_val; + hdw->enc_ctl_state.height = hdw->res_ver_val; + hdw->enc_ctl_state.is_50hz = ((hdw->std_mask_cur & + (V4L2_STD_NTSC|V4L2_STD_PAL_M)) ? + 0 : 1); + + ret = 0; + + if (!ret) ret = pvr2_encoder_vcmd( + hdw,CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, + 0xf0, 0xf0); + + /* setup firmware to notify us about some events (don't know why...) */ + if (!ret) ret = pvr2_encoder_vcmd( + hdw,CX2341X_ENC_SET_EVENT_NOTIFICATION, 4, + 0, 0, 0x10000000, 0xffffffff); + + if (!ret) ret = pvr2_encoder_vcmd( + hdw,CX2341X_ENC_SET_VBI_LINE, 5, + 0xffffffff,0,0,0,0); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to configure cx32416"); + return ret; + } + + ret = cx2341x_update(hdw,pvr2_encoder_cmd, + (hdw->enc_cur_valid ? &hdw->enc_cur_state : 0), + &hdw->enc_ctl_state); + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Error from cx2341x module code=%d",ret); + return ret; + } + + ret = 0; + + if (!ret) ret = pvr2_encoder_vcmd( + hdw, CX2341X_ENC_INITIALIZE_INPUT, 0); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to initialize cx32416 video input"); + return ret; + } + + hdw->subsys_enabled_mask |= (1<<PVR2_SUBSYS_B_ENC_CFG); + memcpy(&hdw->enc_cur_state,&hdw->enc_ctl_state, + sizeof(struct cx2341x_mpeg_params)); + hdw->enc_cur_valid = !0; + return 0; +} + + +int pvr2_encoder_start(struct pvr2_hdw *hdw) +{ + int status; + + /* unmask some interrupts */ + pvr2_write_register(hdw, 0x0048, 0xbfffffff); + + /* change some GPIO data */ + pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000481); + pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000000); + + if (hdw->config == pvr2_config_vbi) { + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2, + 0x01,0x14); + } else if (hdw->config == pvr2_config_mpeg) { + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2, + 0,0x13); + } else { + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2, + 0,0x13); + } + if (!status) { + hdw->subsys_enabled_mask |= (1<<PVR2_SUBSYS_B_ENC_RUN); + } + return status; +} + +int pvr2_encoder_stop(struct pvr2_hdw *hdw) +{ + int status; + + /* mask all interrupts */ + pvr2_write_register(hdw, 0x0048, 0xffffffff); + + if (hdw->config == pvr2_config_vbi) { + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3, + 0x01,0x01,0x14); + } else if (hdw->config == pvr2_config_mpeg) { + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3, + 0x01,0,0x13); + } else { + status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3, + 0x01,0,0x13); + } + + /* change some GPIO data */ + /* Note: Bit d7 of dir appears to control the LED. So we shut it + off here. */ + pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000401); + pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000000); + + if (!status) { + hdw->subsys_enabled_mask &= ~(1<<PVR2_SUBSYS_B_ENC_RUN); + } + return status; +} + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-encoder.h b/drivers/media/video/pvrusb2/pvrusb2-encoder.h new file mode 100644 index 00000000000..01b5a0b89c0 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-encoder.h @@ -0,0 +1,42 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_ENCODER_H +#define __PVRUSB2_ENCODER_H + +struct pvr2_hdw; + +int pvr2_encoder_configure(struct pvr2_hdw *); +int pvr2_encoder_start(struct pvr2_hdw *); +int pvr2_encoder_stop(struct pvr2_hdw *); + +#endif /* __PVRUSB2_ENCODER_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h b/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h new file mode 100644 index 00000000000..ba2afbfe32c --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-hdw-internal.h @@ -0,0 +1,384 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_HDW_INTERNAL_H +#define __PVRUSB2_HDW_INTERNAL_H + +/* + + This header sets up all the internal structures and definitions needed to + track and coordinate the driver's interaction with the hardware. ONLY + source files which actually implement part of that whole circus should be + including this header. Higher levels, like the external layers to the + various public APIs (V4L, sysfs, etc) should NOT ever include this + private, internal header. This means that pvrusb2-hdw, pvrusb2-encoder, + etc will include this, but pvrusb2-v4l should not. + +*/ + +#include <linux/config.h> +#include <linux/videodev2.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include "pvrusb2-hdw.h" +#include "pvrusb2-io.h" +#include <media/cx2341x.h> + +/* Legal values for the SRATE state variable */ +#define PVR2_CVAL_SRATE_48 0 +#define PVR2_CVAL_SRATE_44_1 1 + +/* Legal values for the AUDIOBITRATE state variable */ +#define PVR2_CVAL_AUDIOBITRATE_384 0 +#define PVR2_CVAL_AUDIOBITRATE_320 1 +#define PVR2_CVAL_AUDIOBITRATE_256 2 +#define PVR2_CVAL_AUDIOBITRATE_224 3 +#define PVR2_CVAL_AUDIOBITRATE_192 4 +#define PVR2_CVAL_AUDIOBITRATE_160 5 +#define PVR2_CVAL_AUDIOBITRATE_128 6 +#define PVR2_CVAL_AUDIOBITRATE_112 7 +#define PVR2_CVAL_AUDIOBITRATE_96 8 +#define PVR2_CVAL_AUDIOBITRATE_80 9 +#define PVR2_CVAL_AUDIOBITRATE_64 10 +#define PVR2_CVAL_AUDIOBITRATE_56 11 +#define PVR2_CVAL_AUDIOBITRATE_48 12 +#define PVR2_CVAL_AUDIOBITRATE_32 13 +#define PVR2_CVAL_AUDIOBITRATE_VBR 14 + +/* Legal values for the AUDIOEMPHASIS state variable */ +#define PVR2_CVAL_AUDIOEMPHASIS_NONE 0 +#define PVR2_CVAL_AUDIOEMPHASIS_50_15 1 +#define PVR2_CVAL_AUDIOEMPHASIS_CCITT 2 + +/* Legal values for PVR2_CID_HSM */ +#define PVR2_CVAL_HSM_FAIL 0 +#define PVR2_CVAL_HSM_FULL 1 +#define PVR2_CVAL_HSM_HIGH 2 + +#define PVR2_VID_ENDPOINT 0x84 +#define PVR2_UNK_ENDPOINT 0x86 /* maybe raw yuv ? */ +#define PVR2_VBI_ENDPOINT 0x88 + +#define PVR2_CTL_BUFFSIZE 64 + +#define FREQTABLE_SIZE 500 + +#define LOCK_TAKE(x) do { mutex_lock(&x##_mutex); x##_held = !0; } while (0) +#define LOCK_GIVE(x) do { x##_held = 0; mutex_unlock(&x##_mutex); } while (0) + +struct pvr2_decoder; + +typedef int (*pvr2_ctlf_is_dirty)(struct pvr2_ctrl *); +typedef void (*pvr2_ctlf_clear_dirty)(struct pvr2_ctrl *); +typedef int (*pvr2_ctlf_get_value)(struct pvr2_ctrl *,int *); +typedef int (*pvr2_ctlf_set_value)(struct pvr2_ctrl *,int msk,int val); +typedef int (*pvr2_ctlf_val_to_sym)(struct pvr2_ctrl *,int msk,int val, + char *,unsigned int,unsigned int *); +typedef int (*pvr2_ctlf_sym_to_val)(struct pvr2_ctrl *, + const char *,unsigned int, + int *mskp,int *valp); +typedef unsigned int (*pvr2_ctlf_get_v4lflags)(struct pvr2_ctrl *); + +/* This structure describes a specific control. A table of these is set up + in pvrusb2-hdw.c. */ +struct pvr2_ctl_info { + /* Control's name suitable for use as an identifier */ + const char *name; + + /* Short description of control */ + const char *desc; + + /* Control's implementation */ + pvr2_ctlf_get_value get_value; /* Get its value */ + pvr2_ctlf_set_value set_value; /* Set its value */ + pvr2_ctlf_val_to_sym val_to_sym; /* Custom convert value->symbol */ + pvr2_ctlf_sym_to_val sym_to_val; /* Custom convert symbol->value */ + pvr2_ctlf_is_dirty is_dirty; /* Return true if dirty */ + pvr2_ctlf_clear_dirty clear_dirty; /* Clear dirty state */ + pvr2_ctlf_get_v4lflags get_v4lflags;/* Retrieve v4l flags */ + + /* Control's type (int, enum, bitmask) */ + enum pvr2_ctl_type type; + + /* Associated V4L control ID, if any */ + int v4l_id; + + /* Associated driver internal ID, if any */ + int internal_id; + + /* Don't implicitly initialize this control's value */ + int skip_init; + + /* Starting value for this control */ + int default_value; + + /* Type-specific control information */ + union { + struct { /* Integer control */ + long min_value; /* lower limit */ + long max_value; /* upper limit */ + } type_int; + struct { /* enumerated control */ + unsigned int count; /* enum value count */ + const char **value_names; /* symbol names */ + } type_enum; + struct { /* bitmask control */ + unsigned int valid_bits; /* bits in use */ + const char **bit_names; /* symbol name/bit */ + } type_bitmask; + } def; +}; + + +/* Same as pvr2_ctl_info, but includes storage for the control description */ +#define PVR2_CTLD_INFO_DESC_SIZE 32 +struct pvr2_ctld_info { + struct pvr2_ctl_info info; + char desc[PVR2_CTLD_INFO_DESC_SIZE]; +}; + +struct pvr2_ctrl { + const struct pvr2_ctl_info *info; + struct pvr2_hdw *hdw; +}; + + +struct pvr2_audio_stat { + void *ctxt; + void (*detach)(void *); + int (*status)(void *); +}; + +struct pvr2_decoder_ctrl { + void *ctxt; + void (*detach)(void *); + void (*enable)(void *,int); + int (*tuned)(void *); + void (*force_reset)(void *); +}; + +#define PVR2_I2C_PEND_DETECT 0x01 /* Need to detect a client type */ +#define PVR2_I2C_PEND_CLIENT 0x02 /* Client needs a specific update */ +#define PVR2_I2C_PEND_REFRESH 0x04 /* Client has specific pending bits */ +#define PVR2_I2C_PEND_STALE 0x08 /* Broadcast pending bits */ + +#define PVR2_I2C_PEND_ALL (PVR2_I2C_PEND_DETECT |\ + PVR2_I2C_PEND_CLIENT |\ + PVR2_I2C_PEND_REFRESH |\ + PVR2_I2C_PEND_STALE) + +/* Disposition of firmware1 loading situation */ +#define FW1_STATE_UNKNOWN 0 +#define FW1_STATE_MISSING 1 +#define FW1_STATE_FAILED 2 +#define FW1_STATE_RELOAD 3 +#define FW1_STATE_OK 4 + +/* Known major hardware variants, keyed from device ID */ +#define PVR2_HDW_TYPE_29XXX 0 +#ifdef CONFIG_VIDEO_PVRUSB2_24XXX +#define PVR2_HDW_TYPE_24XXX 1 +#endif + +typedef int (*pvr2_i2c_func)(struct pvr2_hdw *,u8,u8 *,u16,u8 *, u16); +#define PVR2_I2C_FUNC_CNT 128 + +/* This structure contains all state data directly needed to + manipulate the hardware (as opposed to complying with a kernel + interface) */ +struct pvr2_hdw { + /* Underlying USB device handle */ + struct usb_device *usb_dev; + struct usb_interface *usb_intf; + + /* Device type, one of PVR2_HDW_TYPE_xxxxx */ + unsigned int hdw_type; + + /* Video spigot */ + struct pvr2_stream *vid_stream; + + /* Mutex for all hardware state control */ + struct mutex big_lock_mutex; + int big_lock_held; /* For debugging */ + + void (*poll_trigger_func)(void *); + void *poll_trigger_data; + + char name[32]; + + /* I2C stuff */ + struct i2c_adapter i2c_adap; + struct i2c_algorithm i2c_algo; + pvr2_i2c_func i2c_func[PVR2_I2C_FUNC_CNT]; + int i2c_cx25840_hack_state; + int i2c_linked; + unsigned int i2c_pend_types; /* Which types of update are needed */ + unsigned long i2c_pend_mask; /* Change bits we need to scan */ + unsigned long i2c_stale_mask; /* Pending broadcast change bits */ + unsigned long i2c_active_mask; /* All change bits currently in use */ + struct list_head i2c_clients; + struct mutex i2c_list_lock; + + /* Frequency table */ + unsigned int freqTable[FREQTABLE_SIZE]; + unsigned int freqProgSlot; + unsigned int freqSlot; + + /* Stuff for handling low level control interaction with device */ + struct mutex ctl_lock_mutex; + int ctl_lock_held; /* For debugging */ + struct urb *ctl_write_urb; + struct urb *ctl_read_urb; + unsigned char *ctl_write_buffer; + unsigned char *ctl_read_buffer; + volatile int ctl_write_pend_flag; + volatile int ctl_read_pend_flag; + volatile int ctl_timeout_flag; + struct completion ctl_done; + unsigned char cmd_buffer[PVR2_CTL_BUFFSIZE]; + int cmd_debug_state; // Low level command debugging info + unsigned char cmd_debug_code; // + unsigned int cmd_debug_write_len; // + unsigned int cmd_debug_read_len; // + + int flag_ok; // device in known good state + int flag_disconnected; // flag_ok == 0 due to disconnect + int flag_init_ok; // true if structure is fully initialized + int flag_streaming_enabled; // true if streaming should be on + int fw1_state; // current situation with fw1 + + int flag_decoder_is_tuned; + + struct pvr2_decoder_ctrl *decoder_ctrl; + + // CPU firmware info (used to help find / save firmware data) + char *fw_buffer; + unsigned int fw_size; + + // Which subsystem pieces have been enabled / configured + unsigned long subsys_enabled_mask; + + // Which subsystems are manipulated to enable streaming + unsigned long subsys_stream_mask; + + // True if there is a request to trigger logging of state in each + // module. + int log_requested; + + /* Tuner / frequency control stuff */ + unsigned int tuner_type; + int tuner_updated; + unsigned int freqVal; + int freqDirty; + + /* Video standard handling */ + v4l2_std_id std_mask_eeprom; // Hardware supported selections + v4l2_std_id std_mask_avail; // Which standards we may select from + v4l2_std_id std_mask_cur; // Currently selected standard(s) + unsigned int std_enum_cnt; // # of enumerated standards + int std_enum_cur; // selected standard enumeration value + int std_dirty; // True if std_mask_cur has changed + struct pvr2_ctl_info std_info_enum; + struct pvr2_ctl_info std_info_avail; + struct pvr2_ctl_info std_info_cur; + struct v4l2_standard *std_defs; + const char **std_enum_names; + + // Generated string names, one per actual V4L2 standard + const char *std_mask_ptrs[32]; + char std_mask_names[32][10]; + + int unit_number; /* ID for driver instance */ + unsigned long serial_number; /* ID for hardware itself */ + + /* Minor number used by v4l logic (yes, this is a hack, as there should + be no v4l junk here). Probably a better way to do this. */ + int v4l_minor_number; + + /* Location of eeprom or a negative number if none */ + int eeprom_addr; + + enum pvr2_config config; + + /* Information about what audio signal we're hearing */ + int flag_stereo; + int flag_bilingual; + struct pvr2_audio_stat *audio_stat; + + /* Control state needed for cx2341x module */ + struct cx2341x_mpeg_params enc_cur_state; + struct cx2341x_mpeg_params enc_ctl_state; + /* True if an encoder attribute has changed */ + int enc_stale; + /* True if enc_cur_state is valid */ + int enc_cur_valid; + + /* Control state */ +#define VCREATE_DATA(lab) int lab##_val; int lab##_dirty + VCREATE_DATA(brightness); + VCREATE_DATA(contrast); + VCREATE_DATA(saturation); + VCREATE_DATA(hue); + VCREATE_DATA(volume); + VCREATE_DATA(balance); + VCREATE_DATA(bass); + VCREATE_DATA(treble); + VCREATE_DATA(mute); + VCREATE_DATA(input); + VCREATE_DATA(audiomode); + VCREATE_DATA(res_hor); + VCREATE_DATA(res_ver); + VCREATE_DATA(srate); +#undef VCREATE_DATA + + struct pvr2_ctld_info *mpeg_ctrl_info; + + struct pvr2_ctrl *controls; + unsigned int control_cnt; +}; + +int pvr2_hdw_commit_ctl_internal(struct pvr2_hdw *hdw); + +unsigned int pvr2_hdw_get_signal_status_internal(struct pvr2_hdw *); + +void pvr2_hdw_subsys_bit_chg_no_lock(struct pvr2_hdw *hdw, + unsigned long msk,unsigned long val); +void pvr2_hdw_subsys_stream_bit_chg_no_lock(struct pvr2_hdw *hdw, + unsigned long msk, + unsigned long val); + +void pvr2_hdw_internal_find_stdenum(struct pvr2_hdw *hdw); +void pvr2_hdw_internal_set_std_avail(struct pvr2_hdw *hdw); + +int pvr2_i2c_basic_op(struct pvr2_hdw *,u8 i2c_addr, + u8 *wdata,u16 wlen, + u8 *rdata,u16 rlen); + +#endif /* __PVRUSB2_HDW_INTERNAL_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw.c b/drivers/media/video/pvrusb2/pvrusb2-hdw.c new file mode 100644 index 00000000000..643c471375d --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-hdw.c @@ -0,0 +1,3120 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/firmware.h> +#include <linux/videodev2.h> +#include <asm/semaphore.h> +#include "pvrusb2.h" +#include "pvrusb2-std.h" +#include "pvrusb2-util.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-i2c-core.h" +#include "pvrusb2-tuner.h" +#include "pvrusb2-eeprom.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-encoder.h" +#include "pvrusb2-debug.h" + +struct usb_device_id pvr2_device_table[] = { + [PVR2_HDW_TYPE_29XXX] = { USB_DEVICE(0x2040, 0x2900) }, +#ifdef CONFIG_VIDEO_PVRUSB2_24XXX + [PVR2_HDW_TYPE_24XXX] = { USB_DEVICE(0x2040, 0x2400) }, +#endif + { } +}; + +MODULE_DEVICE_TABLE(usb, pvr2_device_table); + +static const char *pvr2_device_names[] = { + [PVR2_HDW_TYPE_29XXX] = "WinTV PVR USB2 Model Category 29xxxx", +#ifdef CONFIG_VIDEO_PVRUSB2_24XXX + [PVR2_HDW_TYPE_24XXX] = "WinTV PVR USB2 Model Category 24xxxx", +#endif +}; + +struct pvr2_string_table { + const char **lst; + unsigned int cnt; +}; + +#ifdef CONFIG_VIDEO_PVRUSB2_24XXX +// Names of other client modules to request for 24xxx model hardware +static const char *pvr2_client_24xxx[] = { + "cx25840", + "tuner", + "tda9887", + "wm8775", +}; +#endif + +// Names of other client modules to request for 29xxx model hardware +static const char *pvr2_client_29xxx[] = { + "msp3400", + "saa7115", + "tuner", + "tda9887", +}; + +static struct pvr2_string_table pvr2_client_lists[] = { + [PVR2_HDW_TYPE_29XXX] = { + pvr2_client_29xxx, + sizeof(pvr2_client_29xxx)/sizeof(pvr2_client_29xxx[0]), + }, +#ifdef CONFIG_VIDEO_PVRUSB2_24XXX + [PVR2_HDW_TYPE_24XXX] = { + pvr2_client_24xxx, + sizeof(pvr2_client_24xxx)/sizeof(pvr2_client_24xxx[0]), + }, +#endif +}; + +static struct pvr2_hdw *unit_pointers[PVR_NUM] = {[ 0 ... PVR_NUM-1 ] = 0}; +DECLARE_MUTEX(pvr2_unit_sem); + +static int ctlchg = 0; +static int initusbreset = 1; +static int procreload = 0; +static int tuner[PVR_NUM] = { [0 ... PVR_NUM-1] = -1 }; +static int tolerance[PVR_NUM] = { [0 ... PVR_NUM-1] = 0 }; +static int video_std[PVR_NUM] = { [0 ... PVR_NUM-1] = 0 }; +static int init_pause_msec = 0; + +module_param(ctlchg, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(ctlchg, "0=optimize ctl change 1=always accept new ctl value"); +module_param(init_pause_msec, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(init_pause_msec, "hardware initialization settling delay"); +module_param(initusbreset, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(initusbreset, "Do USB reset device on probe"); +module_param(procreload, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(procreload, + "Attempt init failure recovery with firmware reload"); +module_param_array(tuner, int, NULL, 0444); +MODULE_PARM_DESC(tuner,"specify installed tuner type"); +module_param_array(video_std, int, NULL, 0444); +MODULE_PARM_DESC(video_std,"specify initial video standard"); +module_param_array(tolerance, int, NULL, 0444); +MODULE_PARM_DESC(tolerance,"specify stream error tolerance"); + +#define PVR2_CTL_WRITE_ENDPOINT 0x01 +#define PVR2_CTL_READ_ENDPOINT 0x81 + +#define PVR2_GPIO_IN 0x9008 +#define PVR2_GPIO_OUT 0x900c +#define PVR2_GPIO_DIR 0x9020 + +#define trace_firmware(...) pvr2_trace(PVR2_TRACE_FIRMWARE,__VA_ARGS__) + +#define PVR2_FIRMWARE_ENDPOINT 0x02 + +/* size of a firmware chunk */ +#define FIRMWARE_CHUNK_SIZE 0x2000 + +/* Define the list of additional controls we'll dynamically construct based + on query of the cx2341x module. */ +struct pvr2_mpeg_ids { + const char *strid; + int id; +}; +static const struct pvr2_mpeg_ids mpeg_ids[] = { + { + .strid = "audio_layer", + .id = V4L2_CID_MPEG_AUDIO_ENCODING, + },{ + .strid = "audio_bitrate", + .id = V4L2_CID_MPEG_AUDIO_L2_BITRATE, + },{ + /* Already using audio_mode elsewhere :-( */ + .strid = "mpeg_audio_mode", + .id = V4L2_CID_MPEG_AUDIO_MODE, + },{ + .strid = "mpeg_audio_mode_extension", + .id = V4L2_CID_MPEG_AUDIO_MODE_EXTENSION, + },{ + .strid = "audio_emphasis", + .id = V4L2_CID_MPEG_AUDIO_EMPHASIS, + },{ + .strid = "audio_crc", + .id = V4L2_CID_MPEG_AUDIO_CRC, + },{ + .strid = "video_aspect", + .id = V4L2_CID_MPEG_VIDEO_ASPECT, + },{ + .strid = "video_b_frames", + .id = V4L2_CID_MPEG_VIDEO_B_FRAMES, + },{ + .strid = "video_gop_size", + .id = V4L2_CID_MPEG_VIDEO_GOP_SIZE, + },{ + .strid = "video_gop_closure", + .id = V4L2_CID_MPEG_VIDEO_GOP_CLOSURE, + },{ + .strid = "video_pulldown", + .id = V4L2_CID_MPEG_VIDEO_PULLDOWN, + },{ + .strid = "video_bitrate_mode", + .id = V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + },{ + .strid = "video_bitrate", + .id = V4L2_CID_MPEG_VIDEO_BITRATE, + },{ + .strid = "video_bitrate_peak", + .id = V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, + },{ + .strid = "video_temporal_decimation", + .id = V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION, + },{ + .strid = "stream_type", + .id = V4L2_CID_MPEG_STREAM_TYPE, + },{ + .strid = "video_spatial_filter_mode", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE, + },{ + .strid = "video_spatial_filter", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER, + },{ + .strid = "video_luma_spatial_filter_type", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE, + },{ + .strid = "video_chroma_spatial_filter_type", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE, + },{ + .strid = "video_temporal_filter_mode", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE, + },{ + .strid = "video_temporal_filter", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER, + },{ + .strid = "video_median_filter_type", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE, + },{ + .strid = "video_luma_median_filter_top", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP, + },{ + .strid = "video_luma_median_filter_bottom", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM, + },{ + .strid = "video_chroma_median_filter_top", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP, + },{ + .strid = "video_chroma_median_filter_bottom", + .id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM, + } +}; +#define MPEGDEF_COUNT (sizeof(mpeg_ids)/sizeof(mpeg_ids[0])) + +static const char *control_values_srate[] = { + [PVR2_CVAL_SRATE_48] = "48KHz", + [PVR2_CVAL_SRATE_44_1] = "44.1KHz", +}; + + + + +static const char *control_values_input[] = { + [PVR2_CVAL_INPUT_TV] = "television", /*xawtv needs this name*/ + [PVR2_CVAL_INPUT_RADIO] = "radio", + [PVR2_CVAL_INPUT_SVIDEO] = "s-video", + [PVR2_CVAL_INPUT_COMPOSITE] = "composite", +}; + + +static const char *control_values_audiomode[] = { + [V4L2_TUNER_MODE_MONO] = "Mono", + [V4L2_TUNER_MODE_STEREO] = "Stereo", + [V4L2_TUNER_MODE_LANG1] = "Lang1", + [V4L2_TUNER_MODE_LANG2] = "Lang2", + [V4L2_TUNER_MODE_LANG1_LANG2] = "Lang1+Lang2", +}; + + +static const char *control_values_hsm[] = { + [PVR2_CVAL_HSM_FAIL] = "Fail", + [PVR2_CVAL_HSM_HIGH] = "High", + [PVR2_CVAL_HSM_FULL] = "Full", +}; + + +static const char *control_values_subsystem[] = { + [PVR2_SUBSYS_B_ENC_FIRMWARE] = "enc_firmware", + [PVR2_SUBSYS_B_ENC_CFG] = "enc_config", + [PVR2_SUBSYS_B_DIGITIZER_RUN] = "digitizer_run", + [PVR2_SUBSYS_B_USBSTREAM_RUN] = "usbstream_run", + [PVR2_SUBSYS_B_ENC_RUN] = "enc_run", +}; + + +static int ctrl_channelfreq_get(struct pvr2_ctrl *cptr,int *vp) +{ + struct pvr2_hdw *hdw = cptr->hdw; + if ((hdw->freqProgSlot > 0) && (hdw->freqProgSlot <= FREQTABLE_SIZE)) { + *vp = hdw->freqTable[hdw->freqProgSlot-1]; + } else { + *vp = 0; + } + return 0; +} + +static int ctrl_channelfreq_set(struct pvr2_ctrl *cptr,int m,int v) +{ + struct pvr2_hdw *hdw = cptr->hdw; + if ((hdw->freqProgSlot > 0) && (hdw->freqProgSlot <= FREQTABLE_SIZE)) { + hdw->freqTable[hdw->freqProgSlot-1] = v; + } + return 0; +} + +static int ctrl_channelprog_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->freqProgSlot; + return 0; +} + +static int ctrl_channelprog_set(struct pvr2_ctrl *cptr,int m,int v) +{ + struct pvr2_hdw *hdw = cptr->hdw; + if ((v >= 0) && (v <= FREQTABLE_SIZE)) { + hdw->freqProgSlot = v; + } + return 0; +} + +static int ctrl_channel_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->freqSlot; + return 0; +} + +static int ctrl_channel_set(struct pvr2_ctrl *cptr,int m,int v) +{ + unsigned freq = 0; + struct pvr2_hdw *hdw = cptr->hdw; + hdw->freqSlot = v; + if ((hdw->freqSlot > 0) && (hdw->freqSlot <= FREQTABLE_SIZE)) { + freq = hdw->freqTable[hdw->freqSlot-1]; + } + if (freq && (freq != hdw->freqVal)) { + hdw->freqVal = freq; + hdw->freqDirty = !0; + } + return 0; +} + +static int ctrl_freq_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->freqVal; + return 0; +} + +static int ctrl_freq_is_dirty(struct pvr2_ctrl *cptr) +{ + return cptr->hdw->freqDirty != 0; +} + +static void ctrl_freq_clear_dirty(struct pvr2_ctrl *cptr) +{ + cptr->hdw->freqDirty = 0; +} + +static int ctrl_freq_set(struct pvr2_ctrl *cptr,int m,int v) +{ + struct pvr2_hdw *hdw = cptr->hdw; + hdw->freqVal = v; + hdw->freqDirty = !0; + hdw->freqSlot = 0; + return 0; +} + +static int ctrl_cx2341x_is_dirty(struct pvr2_ctrl *cptr) +{ + return cptr->hdw->enc_stale != 0; +} + +static void ctrl_cx2341x_clear_dirty(struct pvr2_ctrl *cptr) +{ + cptr->hdw->enc_stale = 0; +} + +static int ctrl_cx2341x_get(struct pvr2_ctrl *cptr,int *vp) +{ + int ret; + struct v4l2_ext_controls cs; + struct v4l2_ext_control c1; + memset(&cs,0,sizeof(cs)); + memset(&c1,0,sizeof(c1)); + cs.controls = &c1; + cs.count = 1; + c1.id = cptr->info->v4l_id; + ret = cx2341x_ext_ctrls(&cptr->hdw->enc_ctl_state,&cs, + VIDIOC_G_EXT_CTRLS); + if (ret) return ret; + *vp = c1.value; + return 0; +} + +static int ctrl_cx2341x_set(struct pvr2_ctrl *cptr,int m,int v) +{ + int ret; + struct v4l2_ext_controls cs; + struct v4l2_ext_control c1; + memset(&cs,0,sizeof(cs)); + memset(&c1,0,sizeof(c1)); + cs.controls = &c1; + cs.count = 1; + c1.id = cptr->info->v4l_id; + c1.value = v; + ret = cx2341x_ext_ctrls(&cptr->hdw->enc_ctl_state,&cs, + VIDIOC_S_EXT_CTRLS); + if (ret) return ret; + cptr->hdw->enc_stale = !0; + return 0; +} + +static unsigned int ctrl_cx2341x_getv4lflags(struct pvr2_ctrl *cptr) +{ + struct v4l2_queryctrl qctrl; + struct pvr2_ctl_info *info; + qctrl.id = cptr->info->v4l_id; + cx2341x_ctrl_query(&cptr->hdw->enc_ctl_state,&qctrl); + /* Strip out the const so we can adjust a function pointer. It's + OK to do this here because we know this is a dynamically created + control, so the underlying storage for the info pointer is (a) + private to us, and (b) not in read-only storage. Either we do + this or we significantly complicate the underlying control + implementation. */ + info = (struct pvr2_ctl_info *)(cptr->info); + if (qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) { + if (info->set_value) { + info->set_value = 0; + } + } else { + if (!(info->set_value)) { + info->set_value = ctrl_cx2341x_set; + } + } + return qctrl.flags; +} + +static int ctrl_streamingenabled_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->flag_streaming_enabled; + return 0; +} + +static int ctrl_hsm_get(struct pvr2_ctrl *cptr,int *vp) +{ + int result = pvr2_hdw_is_hsm(cptr->hdw); + *vp = PVR2_CVAL_HSM_FULL; + if (result < 0) *vp = PVR2_CVAL_HSM_FAIL; + if (result) *vp = PVR2_CVAL_HSM_HIGH; + return 0; +} + +static int ctrl_stdavail_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->std_mask_avail; + return 0; +} + +static int ctrl_stdavail_set(struct pvr2_ctrl *cptr,int m,int v) +{ + struct pvr2_hdw *hdw = cptr->hdw; + v4l2_std_id ns; + ns = hdw->std_mask_avail; + ns = (ns & ~m) | (v & m); + if (ns == hdw->std_mask_avail) return 0; + hdw->std_mask_avail = ns; + pvr2_hdw_internal_set_std_avail(hdw); + pvr2_hdw_internal_find_stdenum(hdw); + return 0; +} + +static int ctrl_std_val_to_sym(struct pvr2_ctrl *cptr,int msk,int val, + char *bufPtr,unsigned int bufSize, + unsigned int *len) +{ + *len = pvr2_std_id_to_str(bufPtr,bufSize,msk & val); + return 0; +} + +static int ctrl_std_sym_to_val(struct pvr2_ctrl *cptr, + const char *bufPtr,unsigned int bufSize, + int *mskp,int *valp) +{ + int ret; + v4l2_std_id id; + ret = pvr2_std_str_to_id(&id,bufPtr,bufSize); + if (ret < 0) return ret; + if (mskp) *mskp = id; + if (valp) *valp = id; + return 0; +} + +static int ctrl_stdcur_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->std_mask_cur; + return 0; +} + +static int ctrl_stdcur_set(struct pvr2_ctrl *cptr,int m,int v) +{ + struct pvr2_hdw *hdw = cptr->hdw; + v4l2_std_id ns; + ns = hdw->std_mask_cur; + ns = (ns & ~m) | (v & m); + if (ns == hdw->std_mask_cur) return 0; + hdw->std_mask_cur = ns; + hdw->std_dirty = !0; + pvr2_hdw_internal_find_stdenum(hdw); + return 0; +} + +static int ctrl_stdcur_is_dirty(struct pvr2_ctrl *cptr) +{ + return cptr->hdw->std_dirty != 0; +} + +static void ctrl_stdcur_clear_dirty(struct pvr2_ctrl *cptr) +{ + cptr->hdw->std_dirty = 0; +} + +static int ctrl_signal_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = ((pvr2_hdw_get_signal_status_internal(cptr->hdw) & + PVR2_SIGNAL_OK) ? 1 : 0); + return 0; +} + +static int ctrl_subsys_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->subsys_enabled_mask; + return 0; +} + +static int ctrl_subsys_set(struct pvr2_ctrl *cptr,int m,int v) +{ + pvr2_hdw_subsys_bit_chg_no_lock(cptr->hdw,m,v); + return 0; +} + +static int ctrl_subsys_stream_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->subsys_stream_mask; + return 0; +} + +static int ctrl_subsys_stream_set(struct pvr2_ctrl *cptr,int m,int v) +{ + pvr2_hdw_subsys_stream_bit_chg_no_lock(cptr->hdw,m,v); + return 0; +} + +static int ctrl_stdenumcur_set(struct pvr2_ctrl *cptr,int m,int v) +{ + struct pvr2_hdw *hdw = cptr->hdw; + if (v < 0) return -EINVAL; + if (v > hdw->std_enum_cnt) return -EINVAL; + hdw->std_enum_cur = v; + if (!v) return 0; + v--; + if (hdw->std_mask_cur == hdw->std_defs[v].id) return 0; + hdw->std_mask_cur = hdw->std_defs[v].id; + hdw->std_dirty = !0; + return 0; +} + + +static int ctrl_stdenumcur_get(struct pvr2_ctrl *cptr,int *vp) +{ + *vp = cptr->hdw->std_enum_cur; + return 0; +} + + +static int ctrl_stdenumcur_is_dirty(struct pvr2_ctrl *cptr) +{ + return cptr->hdw->std_dirty != 0; +} + + +static void ctrl_stdenumcur_clear_dirty(struct pvr2_ctrl *cptr) +{ + cptr->hdw->std_dirty = 0; +} + + +#define DEFINT(vmin,vmax) \ + .type = pvr2_ctl_int, \ + .def.type_int.min_value = vmin, \ + .def.type_int.max_value = vmax + +#define DEFENUM(tab) \ + .type = pvr2_ctl_enum, \ + .def.type_enum.count = (sizeof(tab)/sizeof((tab)[0])), \ + .def.type_enum.value_names = tab + +#define DEFBOOL \ + .type = pvr2_ctl_bool + +#define DEFMASK(msk,tab) \ + .type = pvr2_ctl_bitmask, \ + .def.type_bitmask.valid_bits = msk, \ + .def.type_bitmask.bit_names = tab + +#define DEFREF(vname) \ + .set_value = ctrl_set_##vname, \ + .get_value = ctrl_get_##vname, \ + .is_dirty = ctrl_isdirty_##vname, \ + .clear_dirty = ctrl_cleardirty_##vname + + +#define VCREATE_FUNCS(vname) \ +static int ctrl_get_##vname(struct pvr2_ctrl *cptr,int *vp) \ +{*vp = cptr->hdw->vname##_val; return 0;} \ +static int ctrl_set_##vname(struct pvr2_ctrl *cptr,int m,int v) \ +{cptr->hdw->vname##_val = v; cptr->hdw->vname##_dirty = !0; return 0;} \ +static int ctrl_isdirty_##vname(struct pvr2_ctrl *cptr) \ +{return cptr->hdw->vname##_dirty != 0;} \ +static void ctrl_cleardirty_##vname(struct pvr2_ctrl *cptr) \ +{cptr->hdw->vname##_dirty = 0;} + +VCREATE_FUNCS(brightness) +VCREATE_FUNCS(contrast) +VCREATE_FUNCS(saturation) +VCREATE_FUNCS(hue) +VCREATE_FUNCS(volume) +VCREATE_FUNCS(balance) +VCREATE_FUNCS(bass) +VCREATE_FUNCS(treble) +VCREATE_FUNCS(mute) +VCREATE_FUNCS(input) +VCREATE_FUNCS(audiomode) +VCREATE_FUNCS(res_hor) +VCREATE_FUNCS(res_ver) +VCREATE_FUNCS(srate) + +#define MIN_FREQ 55250000L +#define MAX_FREQ 850000000L + +/* Table definition of all controls which can be manipulated */ +static const struct pvr2_ctl_info control_defs[] = { + { + .v4l_id = V4L2_CID_BRIGHTNESS, + .desc = "Brightness", + .name = "brightness", + .default_value = 128, + DEFREF(brightness), + DEFINT(0,255), + },{ + .v4l_id = V4L2_CID_CONTRAST, + .desc = "Contrast", + .name = "contrast", + .default_value = 68, + DEFREF(contrast), + DEFINT(0,127), + },{ + .v4l_id = V4L2_CID_SATURATION, + .desc = "Saturation", + .name = "saturation", + .default_value = 64, + DEFREF(saturation), + DEFINT(0,127), + },{ + .v4l_id = V4L2_CID_HUE, + .desc = "Hue", + .name = "hue", + .default_value = 0, + DEFREF(hue), + DEFINT(-128,127), + },{ + .v4l_id = V4L2_CID_AUDIO_VOLUME, + .desc = "Volume", + .name = "volume", + .default_value = 65535, + DEFREF(volume), + DEFINT(0,65535), + },{ + .v4l_id = V4L2_CID_AUDIO_BALANCE, + .desc = "Balance", + .name = "balance", + .default_value = 0, + DEFREF(balance), + DEFINT(-32768,32767), + },{ + .v4l_id = V4L2_CID_AUDIO_BASS, + .desc = "Bass", + .name = "bass", + .default_value = 0, + DEFREF(bass), + DEFINT(-32768,32767), + },{ + .v4l_id = V4L2_CID_AUDIO_TREBLE, + .desc = "Treble", + .name = "treble", + .default_value = 0, + DEFREF(treble), + DEFINT(-32768,32767), + },{ + .v4l_id = V4L2_CID_AUDIO_MUTE, + .desc = "Mute", + .name = "mute", + .default_value = 0, + DEFREF(mute), + DEFBOOL, + },{ + .desc = "Video Source", + .name = "input", + .internal_id = PVR2_CID_INPUT, + .default_value = PVR2_CVAL_INPUT_TV, + DEFREF(input), + DEFENUM(control_values_input), + },{ + .desc = "Audio Mode", + .name = "audio_mode", + .internal_id = PVR2_CID_AUDIOMODE, + .default_value = V4L2_TUNER_MODE_STEREO, + DEFREF(audiomode), + DEFENUM(control_values_audiomode), + },{ + .desc = "Horizontal capture resolution", + .name = "resolution_hor", + .internal_id = PVR2_CID_HRES, + .default_value = 720, + DEFREF(res_hor), + DEFINT(320,720), + },{ + .desc = "Vertical capture resolution", + .name = "resolution_ver", + .internal_id = PVR2_CID_VRES, + .default_value = 480, + DEFREF(res_ver), + DEFINT(200,625), + },{ + .v4l_id = V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ, + .desc = "Sample rate", + .name = "srate", + .default_value = PVR2_CVAL_SRATE_48, + DEFREF(srate), + DEFENUM(control_values_srate), + },{ + .desc = "Tuner Frequency (Hz)", + .name = "frequency", + .internal_id = PVR2_CID_FREQUENCY, + .default_value = 175250000L, + .set_value = ctrl_freq_set, + .get_value = ctrl_freq_get, + .is_dirty = ctrl_freq_is_dirty, + .clear_dirty = ctrl_freq_clear_dirty, + DEFINT(MIN_FREQ,MAX_FREQ), + },{ + .desc = "Channel", + .name = "channel", + .set_value = ctrl_channel_set, + .get_value = ctrl_channel_get, + DEFINT(0,FREQTABLE_SIZE), + },{ + .desc = "Channel Program Frequency", + .name = "freq_table_value", + .set_value = ctrl_channelfreq_set, + .get_value = ctrl_channelfreq_get, + DEFINT(MIN_FREQ,MAX_FREQ), + },{ + .desc = "Channel Program ID", + .name = "freq_table_channel", + .set_value = ctrl_channelprog_set, + .get_value = ctrl_channelprog_get, + DEFINT(0,FREQTABLE_SIZE), + },{ + .desc = "Streaming Enabled", + .name = "streaming_enabled", + .get_value = ctrl_streamingenabled_get, + DEFBOOL, + },{ + .desc = "USB Speed", + .name = "usb_speed", + .get_value = ctrl_hsm_get, + DEFENUM(control_values_hsm), + },{ + .desc = "Signal Present", + .name = "signal_present", + .get_value = ctrl_signal_get, + DEFBOOL, + },{ + .desc = "Video Standards Available Mask", + .name = "video_standard_mask_available", + .internal_id = PVR2_CID_STDAVAIL, + .skip_init = !0, + .get_value = ctrl_stdavail_get, + .set_value = ctrl_stdavail_set, + .val_to_sym = ctrl_std_val_to_sym, + .sym_to_val = ctrl_std_sym_to_val, + .type = pvr2_ctl_bitmask, + },{ + .desc = "Video Standards In Use Mask", + .name = "video_standard_mask_active", + .internal_id = PVR2_CID_STDCUR, + .skip_init = !0, + .get_value = ctrl_stdcur_get, + .set_value = ctrl_stdcur_set, + .is_dirty = ctrl_stdcur_is_dirty, + .clear_dirty = ctrl_stdcur_clear_dirty, + .val_to_sym = ctrl_std_val_to_sym, + .sym_to_val = ctrl_std_sym_to_val, + .type = pvr2_ctl_bitmask, + },{ + .desc = "Subsystem enabled mask", + .name = "debug_subsys_mask", + .skip_init = !0, + .get_value = ctrl_subsys_get, + .set_value = ctrl_subsys_set, + DEFMASK(PVR2_SUBSYS_ALL,control_values_subsystem), + },{ + .desc = "Subsystem stream mask", + .name = "debug_subsys_stream_mask", + .skip_init = !0, + .get_value = ctrl_subsys_stream_get, + .set_value = ctrl_subsys_stream_set, + DEFMASK(PVR2_SUBSYS_ALL,control_values_subsystem), + },{ + .desc = "Video Standard Name", + .name = "video_standard", + .internal_id = PVR2_CID_STDENUM, + .skip_init = !0, + .get_value = ctrl_stdenumcur_get, + .set_value = ctrl_stdenumcur_set, + .is_dirty = ctrl_stdenumcur_is_dirty, + .clear_dirty = ctrl_stdenumcur_clear_dirty, + .type = pvr2_ctl_enum, + } +}; + +#define CTRLDEF_COUNT (sizeof(control_defs)/sizeof(control_defs[0])) + + +const char *pvr2_config_get_name(enum pvr2_config cfg) +{ + switch (cfg) { + case pvr2_config_empty: return "empty"; + case pvr2_config_mpeg: return "mpeg"; + case pvr2_config_vbi: return "vbi"; + case pvr2_config_radio: return "radio"; + } + return "<unknown>"; +} + + +struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *hdw) +{ + return hdw->usb_dev; +} + + +unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *hdw) +{ + return hdw->serial_number; +} + + +struct pvr2_hdw *pvr2_hdw_find(int unit_number) +{ + if (unit_number < 0) return 0; + if (unit_number >= PVR_NUM) return 0; + return unit_pointers[unit_number]; +} + + +int pvr2_hdw_get_unit_number(struct pvr2_hdw *hdw) +{ + return hdw->unit_number; +} + + +/* Attempt to locate one of the given set of files. Messages are logged + appropriate to what has been found. The return value will be 0 or + greater on success (it will be the index of the file name found) and + fw_entry will be filled in. Otherwise a negative error is returned on + failure. If the return value is -ENOENT then no viable firmware file + could be located. */ +static int pvr2_locate_firmware(struct pvr2_hdw *hdw, + const struct firmware **fw_entry, + const char *fwtypename, + unsigned int fwcount, + const char *fwnames[]) +{ + unsigned int idx; + int ret = -EINVAL; + for (idx = 0; idx < fwcount; idx++) { + ret = request_firmware(fw_entry, + fwnames[idx], + &hdw->usb_dev->dev); + if (!ret) { + trace_firmware("Located %s firmware: %s;" + " uploading...", + fwtypename, + fwnames[idx]); + return idx; + } + if (ret == -ENOENT) continue; + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "request_firmware fatal error with code=%d",ret); + return ret; + } + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "***WARNING***" + " Device %s firmware" + " seems to be missing.", + fwtypename); + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Did you install the pvrusb2 firmware files" + " in their proper location?"); + if (fwcount == 1) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "request_firmware unable to locate %s file %s", + fwtypename,fwnames[0]); + } else { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "request_firmware unable to locate" + " one of the following %s files:", + fwtypename); + for (idx = 0; idx < fwcount; idx++) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "request_firmware: Failed to find %s", + fwnames[idx]); + } + } + return ret; +} + + +/* + * pvr2_upload_firmware1(). + * + * Send the 8051 firmware to the device. After the upload, arrange for + * device to re-enumerate. + * + * NOTE : the pointer to the firmware data given by request_firmware() + * is not suitable for an usb transaction. + * + */ +int pvr2_upload_firmware1(struct pvr2_hdw *hdw) +{ + const struct firmware *fw_entry = 0; + void *fw_ptr; + unsigned int pipe; + int ret; + u16 address; + static const char *fw_files_29xxx[] = { + "v4l-pvrusb2-29xxx-01.fw", + }; +#ifdef CONFIG_VIDEO_PVRUSB2_24XXX + static const char *fw_files_24xxx[] = { + "v4l-pvrusb2-24xxx-01.fw", + }; +#endif + static const struct pvr2_string_table fw_file_defs[] = { + [PVR2_HDW_TYPE_29XXX] = { + fw_files_29xxx, + sizeof(fw_files_29xxx)/sizeof(fw_files_29xxx[0]), + }, +#ifdef CONFIG_VIDEO_PVRUSB2_24XXX + [PVR2_HDW_TYPE_24XXX] = { + fw_files_24xxx, + sizeof(fw_files_24xxx)/sizeof(fw_files_24xxx[0]), + }, +#endif + }; + hdw->fw1_state = FW1_STATE_FAILED; // default result + + trace_firmware("pvr2_upload_firmware1"); + + ret = pvr2_locate_firmware(hdw,&fw_entry,"fx2 controller", + fw_file_defs[hdw->hdw_type].cnt, + fw_file_defs[hdw->hdw_type].lst); + if (ret < 0) { + if (ret == -ENOENT) hdw->fw1_state = FW1_STATE_MISSING; + return ret; + } + + usb_settoggle(hdw->usb_dev, 0 & 0xf, !(0 & USB_DIR_IN), 0); + usb_clear_halt(hdw->usb_dev, usb_sndbulkpipe(hdw->usb_dev, 0 & 0x7f)); + + pipe = usb_sndctrlpipe(hdw->usb_dev, 0); + + if (fw_entry->size != 0x2000){ + pvr2_trace(PVR2_TRACE_ERROR_LEGS,"wrong fx2 firmware size"); + release_firmware(fw_entry); + return -ENOMEM; + } + + fw_ptr = kmalloc(0x800, GFP_KERNEL); + if (fw_ptr == NULL){ + release_firmware(fw_entry); + return -ENOMEM; + } + + /* We have to hold the CPU during firmware upload. */ + pvr2_hdw_cpureset_assert(hdw,1); + + /* upload the firmware to address 0000-1fff in 2048 (=0x800) bytes + chunk. */ + + ret = 0; + for(address = 0; address < fw_entry->size; address += 0x800) { + memcpy(fw_ptr, fw_entry->data + address, 0x800); + ret += usb_control_msg(hdw->usb_dev, pipe, 0xa0, 0x40, address, + 0, fw_ptr, 0x800, HZ); + } + + trace_firmware("Upload done, releasing device's CPU"); + + /* Now release the CPU. It will disconnect and reconnect later. */ + pvr2_hdw_cpureset_assert(hdw,0); + + kfree(fw_ptr); + release_firmware(fw_entry); + + trace_firmware("Upload done (%d bytes sent)",ret); + + /* We should have written 8192 bytes */ + if (ret == 8192) { + hdw->fw1_state = FW1_STATE_RELOAD; + return 0; + } + + return -EIO; +} + + +/* + * pvr2_upload_firmware2() + * + * This uploads encoder firmware on endpoint 2. + * + */ + +int pvr2_upload_firmware2(struct pvr2_hdw *hdw) +{ + const struct firmware *fw_entry = 0; + void *fw_ptr; + unsigned int pipe, fw_len, fw_done; + int actual_length; + int ret = 0; + int fwidx; + static const char *fw_files[] = { + CX2341X_FIRM_ENC_FILENAME, + }; + + trace_firmware("pvr2_upload_firmware2"); + + ret = pvr2_locate_firmware(hdw,&fw_entry,"encoder", + sizeof(fw_files)/sizeof(fw_files[0]), + fw_files); + if (ret < 0) return ret; + fwidx = ret; + ret = 0; + /* Since we're about to completely reinitialize the encoder, + invalidate our cached copy of its configuration state. Next + time we configure the encoder, then we'll fully configure it. */ + hdw->enc_cur_valid = 0; + + /* First prepare firmware loading */ + ret |= pvr2_write_register(hdw, 0x0048, 0xffffffff); /*interrupt mask*/ + ret |= pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000088); /*gpio dir*/ + ret |= pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000008); /*gpio output state*/ + ret |= pvr2_hdw_cmd_deep_reset(hdw); + ret |= pvr2_write_register(hdw, 0xa064, 0x00000000); /*APU command*/ + ret |= pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000408); /*gpio dir*/ + ret |= pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000008); /*gpio output state*/ + ret |= pvr2_write_register(hdw, 0x9058, 0xffffffed); /*VPU ctrl*/ + ret |= pvr2_write_register(hdw, 0x9054, 0xfffffffd); /*reset hw blocks*/ + ret |= pvr2_write_register(hdw, 0x07f8, 0x80000800); /*encoder SDRAM refresh*/ + ret |= pvr2_write_register(hdw, 0x07fc, 0x0000001a); /*encoder SDRAM pre-charge*/ + ret |= pvr2_write_register(hdw, 0x0700, 0x00000000); /*I2C clock*/ + ret |= pvr2_write_register(hdw, 0xaa00, 0x00000000); /*unknown*/ + ret |= pvr2_write_register(hdw, 0xaa04, 0x00057810); /*unknown*/ + ret |= pvr2_write_register(hdw, 0xaa10, 0x00148500); /*unknown*/ + ret |= pvr2_write_register(hdw, 0xaa18, 0x00840000); /*unknown*/ + ret |= pvr2_write_u8(hdw, 0x52, 0); + ret |= pvr2_write_u16(hdw, 0x0600, 0); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "firmware2 upload prep failed, ret=%d",ret); + release_firmware(fw_entry); + return ret; + } + + /* Now send firmware */ + + fw_len = fw_entry->size; + + if (fw_len % FIRMWARE_CHUNK_SIZE) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "size of %s firmware" + " must be a multiple of 8192B", + fw_files[fwidx]); + release_firmware(fw_entry); + return -1; + } + + fw_ptr = kmalloc(FIRMWARE_CHUNK_SIZE, GFP_KERNEL); + if (fw_ptr == NULL){ + release_firmware(fw_entry); + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "failed to allocate memory for firmware2 upload"); + return -ENOMEM; + } + + pipe = usb_sndbulkpipe(hdw->usb_dev, PVR2_FIRMWARE_ENDPOINT); + + for (fw_done = 0 ; (fw_done < fw_len) && !ret ; + fw_done += FIRMWARE_CHUNK_SIZE ) { + int i; + memcpy(fw_ptr, fw_entry->data + fw_done, FIRMWARE_CHUNK_SIZE); + /* Usbsnoop log shows that we must swap bytes... */ + for (i = 0; i < FIRMWARE_CHUNK_SIZE/4 ; i++) + ((u32 *)fw_ptr)[i] = ___swab32(((u32 *)fw_ptr)[i]); + + ret |= usb_bulk_msg(hdw->usb_dev, pipe, fw_ptr, + FIRMWARE_CHUNK_SIZE, + &actual_length, HZ); + ret |= (actual_length != FIRMWARE_CHUNK_SIZE); + } + + trace_firmware("upload of %s : %i / %i ", + fw_files[fwidx],fw_done,fw_len); + + kfree(fw_ptr); + release_firmware(fw_entry); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "firmware2 upload transfer failure"); + return ret; + } + + /* Finish upload */ + + ret |= pvr2_write_register(hdw, 0x9054, 0xffffffff); /*reset hw blocks*/ + ret |= pvr2_write_register(hdw, 0x9058, 0xffffffe8); /*VPU ctrl*/ + ret |= pvr2_write_u16(hdw, 0x0600, 0); + + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "firmware2 upload post-proc failure"); + } else { + hdw->subsys_enabled_mask |= (1<<PVR2_SUBSYS_B_ENC_FIRMWARE); + } + return ret; +} + + +#define FIRMWARE_RECOVERY_BITS \ + ((1<<PVR2_SUBSYS_B_ENC_CFG) | \ + (1<<PVR2_SUBSYS_B_ENC_RUN) | \ + (1<<PVR2_SUBSYS_B_ENC_FIRMWARE) | \ + (1<<PVR2_SUBSYS_B_USBSTREAM_RUN)) + +/* + + This single function is key to pretty much everything. The pvrusb2 + device can logically be viewed as a series of subsystems which can be + stopped / started or unconfigured / configured. To get things streaming, + one must configure everything and start everything, but there may be + various reasons over time to deconfigure something or stop something. + This function handles all of this activity. Everything EVERYWHERE that + must affect a subsystem eventually comes here to do the work. + + The current state of all subsystems is represented by a single bit mask, + known as subsys_enabled_mask. The bit positions are defined by the + PVR2_SUBSYS_xxxx macros, with one subsystem per bit position. At any + time the set of configured or active subsystems can be queried just by + looking at that mask. To change bits in that mask, this function here + must be called. The "msk" argument indicates which bit positions to + change, and the "val" argument defines the new values for the positions + defined by "msk". + + There is a priority ordering of starting / stopping things, and for + multiple requested changes, this function implements that ordering. + (Thus we will act on a request to load encoder firmware before we + configure the encoder.) In addition to priority ordering, there is a + recovery strategy implemented here. If a particular step fails and we + detect that failure, this function will clear the affected subsystem bits + and restart. Thus we have a means for recovering from a dead encoder: + Clear all bits that correspond to subsystems that we need to restart / + reconfigure and start over. + +*/ +void pvr2_hdw_subsys_bit_chg_no_lock(struct pvr2_hdw *hdw, + unsigned long msk,unsigned long val) +{ + unsigned long nmsk; + unsigned long vmsk; + int ret; + unsigned int tryCount = 0; + + if (!hdw->flag_ok) return; + + msk &= PVR2_SUBSYS_ALL; + nmsk = (hdw->subsys_enabled_mask & ~msk) | (val & msk); + nmsk &= PVR2_SUBSYS_ALL; + + for (;;) { + tryCount++; + if (!((nmsk ^ hdw->subsys_enabled_mask) & + PVR2_SUBSYS_ALL)) break; + if (tryCount > 4) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Too many retries when configuring device;" + " giving up"); + pvr2_hdw_render_useless(hdw); + break; + } + if (tryCount > 1) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Retrying device reconfiguration"); + } + pvr2_trace(PVR2_TRACE_INIT, + "subsys mask changing 0x%lx:0x%lx" + " from 0x%lx to 0x%lx", + msk,val,hdw->subsys_enabled_mask,nmsk); + + vmsk = (nmsk ^ hdw->subsys_enabled_mask) & + hdw->subsys_enabled_mask; + if (vmsk) { + if (vmsk & (1<<PVR2_SUBSYS_B_ENC_RUN)) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_encoder_stop"); + ret = pvr2_encoder_stop(hdw); + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Error recovery initiated"); + hdw->subsys_enabled_mask &= + ~FIRMWARE_RECOVERY_BITS; + continue; + } + } + if (vmsk & (1<<PVR2_SUBSYS_B_USBSTREAM_RUN)) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_hdw_cmd_usbstream(0)"); + pvr2_hdw_cmd_usbstream(hdw,0); + } + if (vmsk & (1<<PVR2_SUBSYS_B_DIGITIZER_RUN)) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " decoder disable"); + if (hdw->decoder_ctrl) { + hdw->decoder_ctrl->enable( + hdw->decoder_ctrl->ctxt,0); + } else { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "WARNING:" + " No decoder present"); + } + hdw->subsys_enabled_mask &= + ~(1<<PVR2_SUBSYS_B_DIGITIZER_RUN); + } + if (vmsk & PVR2_SUBSYS_CFG_ALL) { + hdw->subsys_enabled_mask &= + ~(vmsk & PVR2_SUBSYS_CFG_ALL); + } + } + vmsk = (nmsk ^ hdw->subsys_enabled_mask) & nmsk; + if (vmsk) { + if (vmsk & (1<<PVR2_SUBSYS_B_ENC_FIRMWARE)) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_upload_firmware2"); + ret = pvr2_upload_firmware2(hdw); + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failure uploading encoder" + " firmware"); + pvr2_hdw_render_useless(hdw); + break; + } + } + if (vmsk & (1<<PVR2_SUBSYS_B_ENC_CFG)) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_encoder_configure"); + ret = pvr2_encoder_configure(hdw); + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Error recovery initiated"); + hdw->subsys_enabled_mask &= + ~FIRMWARE_RECOVERY_BITS; + continue; + } + } + if (vmsk & (1<<PVR2_SUBSYS_B_DIGITIZER_RUN)) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " decoder enable"); + if (hdw->decoder_ctrl) { + hdw->decoder_ctrl->enable( + hdw->decoder_ctrl->ctxt,!0); + } else { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "WARNING:" + " No decoder present"); + } + hdw->subsys_enabled_mask |= + (1<<PVR2_SUBSYS_B_DIGITIZER_RUN); + } + if (vmsk & (1<<PVR2_SUBSYS_B_USBSTREAM_RUN)) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_hdw_cmd_usbstream(1)"); + pvr2_hdw_cmd_usbstream(hdw,!0); + } + if (vmsk & (1<<PVR2_SUBSYS_B_ENC_RUN)) { + pvr2_trace(PVR2_TRACE_CTL, + "/*---TRACE_CTL----*/" + " pvr2_encoder_start"); + ret = pvr2_encoder_start(hdw); + if (ret) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Error recovery initiated"); + hdw->subsys_enabled_mask &= + ~FIRMWARE_RECOVERY_BITS; + continue; + } + } + } + } +} + + +void pvr2_hdw_subsys_bit_chg(struct pvr2_hdw *hdw, + unsigned long msk,unsigned long val) +{ + LOCK_TAKE(hdw->big_lock); do { + pvr2_hdw_subsys_bit_chg_no_lock(hdw,msk,val); + } while (0); LOCK_GIVE(hdw->big_lock); +} + + +void pvr2_hdw_subsys_bit_set(struct pvr2_hdw *hdw,unsigned long msk) +{ + pvr2_hdw_subsys_bit_chg(hdw,msk,msk); +} + + +void pvr2_hdw_subsys_bit_clr(struct pvr2_hdw *hdw,unsigned long msk) +{ + pvr2_hdw_subsys_bit_chg(hdw,msk,0); +} + + +unsigned long pvr2_hdw_subsys_get(struct pvr2_hdw *hdw) +{ + return hdw->subsys_enabled_mask; +} + + +unsigned long pvr2_hdw_subsys_stream_get(struct pvr2_hdw *hdw) +{ + return hdw->subsys_stream_mask; +} + + +void pvr2_hdw_subsys_stream_bit_chg_no_lock(struct pvr2_hdw *hdw, + unsigned long msk, + unsigned long val) +{ + unsigned long val2; + msk &= PVR2_SUBSYS_ALL; + val2 = ((hdw->subsys_stream_mask & ~msk) | (val & msk)); + pvr2_trace(PVR2_TRACE_INIT, + "stream mask changing 0x%lx:0x%lx from 0x%lx to 0x%lx", + msk,val,hdw->subsys_stream_mask,val2); + hdw->subsys_stream_mask = val2; +} + + +void pvr2_hdw_subsys_stream_bit_chg(struct pvr2_hdw *hdw, + unsigned long msk, + unsigned long val) +{ + LOCK_TAKE(hdw->big_lock); do { + pvr2_hdw_subsys_stream_bit_chg_no_lock(hdw,msk,val); + } while (0); LOCK_GIVE(hdw->big_lock); +} + + +int pvr2_hdw_set_streaming_no_lock(struct pvr2_hdw *hdw,int enableFl) +{ + if ((!enableFl) == !(hdw->flag_streaming_enabled)) return 0; + if (enableFl) { + pvr2_trace(PVR2_TRACE_START_STOP, + "/*--TRACE_STREAM--*/ enable"); + pvr2_hdw_subsys_bit_chg_no_lock(hdw,~0,~0); + } else { + pvr2_trace(PVR2_TRACE_START_STOP, + "/*--TRACE_STREAM--*/ disable"); + pvr2_hdw_subsys_bit_chg_no_lock(hdw,hdw->subsys_stream_mask,0); + } + if (!hdw->flag_ok) return -EIO; + hdw->flag_streaming_enabled = enableFl != 0; + return 0; +} + + +int pvr2_hdw_get_streaming(struct pvr2_hdw *hdw) +{ + return hdw->flag_streaming_enabled != 0; +} + + +int pvr2_hdw_set_streaming(struct pvr2_hdw *hdw,int enable_flag) +{ + int ret; + LOCK_TAKE(hdw->big_lock); do { + ret = pvr2_hdw_set_streaming_no_lock(hdw,enable_flag); + } while (0); LOCK_GIVE(hdw->big_lock); + return ret; +} + + +int pvr2_hdw_set_stream_type_no_lock(struct pvr2_hdw *hdw, + enum pvr2_config config) +{ + unsigned long sm = hdw->subsys_enabled_mask; + if (!hdw->flag_ok) return -EIO; + pvr2_hdw_subsys_bit_chg_no_lock(hdw,hdw->subsys_stream_mask,0); + hdw->config = config; + pvr2_hdw_subsys_bit_chg_no_lock(hdw,~0,sm); + return 0; +} + + +int pvr2_hdw_set_stream_type(struct pvr2_hdw *hdw,enum pvr2_config config) +{ + int ret; + if (!hdw->flag_ok) return -EIO; + LOCK_TAKE(hdw->big_lock); + ret = pvr2_hdw_set_stream_type_no_lock(hdw,config); + LOCK_GIVE(hdw->big_lock); + return ret; +} + + +static int get_default_tuner_type(struct pvr2_hdw *hdw) +{ + int unit_number = hdw->unit_number; + int tp = -1; + if ((unit_number >= 0) && (unit_number < PVR_NUM)) { + tp = tuner[unit_number]; + } + if (tp < 0) return -EINVAL; + hdw->tuner_type = tp; + return 0; +} + + +static v4l2_std_id get_default_standard(struct pvr2_hdw *hdw) +{ + int unit_number = hdw->unit_number; + int tp = 0; + if ((unit_number >= 0) && (unit_number < PVR_NUM)) { + tp = video_std[unit_number]; + } + return tp; +} + + +static unsigned int get_default_error_tolerance(struct pvr2_hdw *hdw) +{ + int unit_number = hdw->unit_number; + int tp = 0; + if ((unit_number >= 0) && (unit_number < PVR_NUM)) { + tp = tolerance[unit_number]; + } + return tp; +} + + +static int pvr2_hdw_check_firmware(struct pvr2_hdw *hdw) +{ + /* Try a harmless request to fetch the eeprom's address over + endpoint 1. See what happens. Only the full FX2 image can + respond to this. If this probe fails then likely the FX2 + firmware needs be loaded. */ + int result; + LOCK_TAKE(hdw->ctl_lock); do { + hdw->cmd_buffer[0] = 0xeb; + result = pvr2_send_request_ex(hdw,HZ*1,!0, + hdw->cmd_buffer,1, + hdw->cmd_buffer,1); + if (result < 0) break; + } while(0); LOCK_GIVE(hdw->ctl_lock); + if (result) { + pvr2_trace(PVR2_TRACE_INIT, + "Probe of device endpoint 1 result status %d", + result); + } else { + pvr2_trace(PVR2_TRACE_INIT, + "Probe of device endpoint 1 succeeded"); + } + return result == 0; +} + +static void pvr2_hdw_setup_std(struct pvr2_hdw *hdw) +{ + char buf[40]; + unsigned int bcnt; + v4l2_std_id std1,std2; + + std1 = get_default_standard(hdw); + + bcnt = pvr2_std_id_to_str(buf,sizeof(buf),hdw->std_mask_eeprom); + pvr2_trace(PVR2_TRACE_INIT, + "Supported video standard(s) reported by eeprom: %.*s", + bcnt,buf); + + hdw->std_mask_avail = hdw->std_mask_eeprom; + + std2 = std1 & ~hdw->std_mask_avail; + if (std2) { + bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std2); + pvr2_trace(PVR2_TRACE_INIT, + "Expanding supported video standards" + " to include: %.*s", + bcnt,buf); + hdw->std_mask_avail |= std2; + } + + pvr2_hdw_internal_set_std_avail(hdw); + + if (std1) { + bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std1); + pvr2_trace(PVR2_TRACE_INIT, + "Initial video standard forced to %.*s", + bcnt,buf); + hdw->std_mask_cur = std1; + hdw->std_dirty = !0; + pvr2_hdw_internal_find_stdenum(hdw); + return; + } + + if (hdw->std_enum_cnt > 1) { + // Autoselect the first listed standard + hdw->std_enum_cur = 1; + hdw->std_mask_cur = hdw->std_defs[hdw->std_enum_cur-1].id; + hdw->std_dirty = !0; + pvr2_trace(PVR2_TRACE_INIT, + "Initial video standard auto-selected to %s", + hdw->std_defs[hdw->std_enum_cur-1].name); + return; + } + + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Unable to select a viable initial video standard"); +} + + +static void pvr2_hdw_setup_low(struct pvr2_hdw *hdw) +{ + int ret; + unsigned int idx; + struct pvr2_ctrl *cptr; + int reloadFl = 0; + if (!reloadFl) { + reloadFl = (hdw->usb_intf->cur_altsetting->desc.bNumEndpoints + == 0); + if (reloadFl) { + pvr2_trace(PVR2_TRACE_INIT, + "USB endpoint config looks strange" + "; possibly firmware needs to be loaded"); + } + } + if (!reloadFl) { + reloadFl = !pvr2_hdw_check_firmware(hdw); + if (reloadFl) { + pvr2_trace(PVR2_TRACE_INIT, + "Check for FX2 firmware failed" + "; possibly firmware needs to be loaded"); + } + } + if (reloadFl) { + if (pvr2_upload_firmware1(hdw) != 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failure uploading firmware1"); + } + return; + } + hdw->fw1_state = FW1_STATE_OK; + + if (initusbreset) { + pvr2_hdw_device_reset(hdw); + } + if (!pvr2_hdw_dev_ok(hdw)) return; + + for (idx = 0; idx < pvr2_client_lists[hdw->hdw_type].cnt; idx++) { + request_module(pvr2_client_lists[hdw->hdw_type].lst[idx]); + } + + pvr2_hdw_cmd_powerup(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + + if (pvr2_upload_firmware2(hdw)){ + pvr2_trace(PVR2_TRACE_ERROR_LEGS,"device unstable!!"); + pvr2_hdw_render_useless(hdw); + return; + } + + // This step MUST happen after the earlier powerup step. + pvr2_i2c_core_init(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + + for (idx = 0; idx < CTRLDEF_COUNT; idx++) { + cptr = hdw->controls + idx; + if (cptr->info->skip_init) continue; + if (!cptr->info->set_value) continue; + cptr->info->set_value(cptr,~0,cptr->info->default_value); + } + + // Do not use pvr2_reset_ctl_endpoints() here. It is not + // thread-safe against the normal pvr2_send_request() mechanism. + // (We should make it thread safe). + + ret = pvr2_hdw_get_eeprom_addr(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Unable to determine location of eeprom, skipping"); + } else { + hdw->eeprom_addr = ret; + pvr2_eeprom_analyze(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + } + + pvr2_hdw_setup_std(hdw); + + if (!get_default_tuner_type(hdw)) { + pvr2_trace(PVR2_TRACE_INIT, + "pvr2_hdw_setup: Tuner type overridden to %d", + hdw->tuner_type); + } + + hdw->tuner_updated = !0; + pvr2_i2c_core_check_stale(hdw); + hdw->tuner_updated = 0; + + if (!pvr2_hdw_dev_ok(hdw)) return; + + pvr2_hdw_commit_ctl_internal(hdw); + if (!pvr2_hdw_dev_ok(hdw)) return; + + hdw->vid_stream = pvr2_stream_create(); + if (!pvr2_hdw_dev_ok(hdw)) return; + pvr2_trace(PVR2_TRACE_INIT, + "pvr2_hdw_setup: video stream is %p",hdw->vid_stream); + if (hdw->vid_stream) { + idx = get_default_error_tolerance(hdw); + if (idx) { + pvr2_trace(PVR2_TRACE_INIT, + "pvr2_hdw_setup: video stream %p" + " setting tolerance %u", + hdw->vid_stream,idx); + } + pvr2_stream_setup(hdw->vid_stream,hdw->usb_dev, + PVR2_VID_ENDPOINT,idx); + } + + if (!pvr2_hdw_dev_ok(hdw)) return; + + /* Make sure everything is up to date */ + pvr2_i2c_core_sync(hdw); + + if (!pvr2_hdw_dev_ok(hdw)) return; + + hdw->flag_init_ok = !0; +} + + +int pvr2_hdw_setup(struct pvr2_hdw *hdw) +{ + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_setup(hdw=%p) begin",hdw); + LOCK_TAKE(hdw->big_lock); do { + pvr2_hdw_setup_low(hdw); + pvr2_trace(PVR2_TRACE_INIT, + "pvr2_hdw_setup(hdw=%p) done, ok=%d init_ok=%d", + hdw,hdw->flag_ok,hdw->flag_init_ok); + if (pvr2_hdw_dev_ok(hdw)) { + if (pvr2_hdw_init_ok(hdw)) { + pvr2_trace( + PVR2_TRACE_INFO, + "Device initialization" + " completed successfully."); + break; + } + if (hdw->fw1_state == FW1_STATE_RELOAD) { + pvr2_trace( + PVR2_TRACE_INFO, + "Device microcontroller firmware" + " (re)loaded; it should now reset" + " and reconnect."); + break; + } + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Device initialization was not successful."); + if (hdw->fw1_state == FW1_STATE_MISSING) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Giving up since device" + " microcontroller firmware" + " appears to be missing."); + break; + } + } + if (procreload) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Attempting pvrusb2 recovery by reloading" + " primary firmware."); + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "If this works, device should disconnect" + " and reconnect in a sane state."); + hdw->fw1_state = FW1_STATE_UNKNOWN; + pvr2_upload_firmware1(hdw); + } else { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "***WARNING*** pvrusb2 device hardware" + " appears to be jammed" + " and I can't clear it."); + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "You might need to power cycle" + " the pvrusb2 device" + " in order to recover."); + } + } while (0); LOCK_GIVE(hdw->big_lock); + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_setup(hdw=%p) end",hdw); + return hdw->flag_init_ok; +} + + +/* Create and return a structure for interacting with the underlying + hardware */ +struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf, + const struct usb_device_id *devid) +{ + unsigned int idx,cnt1,cnt2; + struct pvr2_hdw *hdw; + unsigned int hdw_type; + int valid_std_mask; + struct pvr2_ctrl *cptr; + __u8 ifnum; + struct v4l2_queryctrl qctrl; + struct pvr2_ctl_info *ciptr; + + hdw_type = devid - pvr2_device_table; + if (hdw_type >= + sizeof(pvr2_device_names)/sizeof(pvr2_device_names[0])) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Bogus device type of %u reported",hdw_type); + return 0; + } + + hdw = kmalloc(sizeof(*hdw),GFP_KERNEL); + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_create: hdw=%p, type \"%s\"", + hdw,pvr2_device_names[hdw_type]); + if (!hdw) goto fail; + memset(hdw,0,sizeof(*hdw)); + cx2341x_fill_defaults(&hdw->enc_ctl_state); + + hdw->control_cnt = CTRLDEF_COUNT; + hdw->control_cnt += MPEGDEF_COUNT; + hdw->controls = kmalloc(sizeof(struct pvr2_ctrl) * hdw->control_cnt, + GFP_KERNEL); + if (!hdw->controls) goto fail; + memset(hdw->controls,0,sizeof(struct pvr2_ctrl) * hdw->control_cnt); + hdw->hdw_type = hdw_type; + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + cptr->hdw = hdw; + } + for (idx = 0; idx < 32; idx++) { + hdw->std_mask_ptrs[idx] = hdw->std_mask_names[idx]; + } + for (idx = 0; idx < CTRLDEF_COUNT; idx++) { + cptr = hdw->controls + idx; + cptr->info = control_defs+idx; + } + /* Define and configure additional controls from cx2341x module. */ + hdw->mpeg_ctrl_info = kmalloc( + sizeof(*(hdw->mpeg_ctrl_info)) * MPEGDEF_COUNT, GFP_KERNEL); + if (!hdw->mpeg_ctrl_info) goto fail; + memset(hdw->mpeg_ctrl_info,0, + sizeof(*(hdw->mpeg_ctrl_info)) * MPEGDEF_COUNT); + for (idx = 0; idx < MPEGDEF_COUNT; idx++) { + cptr = hdw->controls + idx + CTRLDEF_COUNT; + ciptr = &(hdw->mpeg_ctrl_info[idx].info); + ciptr->desc = hdw->mpeg_ctrl_info[idx].desc; + ciptr->name = mpeg_ids[idx].strid; + ciptr->v4l_id = mpeg_ids[idx].id; + ciptr->skip_init = !0; + ciptr->get_value = ctrl_cx2341x_get; + ciptr->get_v4lflags = ctrl_cx2341x_getv4lflags; + ciptr->is_dirty = ctrl_cx2341x_is_dirty; + if (!idx) ciptr->clear_dirty = ctrl_cx2341x_clear_dirty; + qctrl.id = ciptr->v4l_id; + cx2341x_ctrl_query(&hdw->enc_ctl_state,&qctrl); + if (!(qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY)) { + ciptr->set_value = ctrl_cx2341x_set; + } + strncpy(hdw->mpeg_ctrl_info[idx].desc,qctrl.name, + PVR2_CTLD_INFO_DESC_SIZE); + hdw->mpeg_ctrl_info[idx].desc[PVR2_CTLD_INFO_DESC_SIZE-1] = 0; + ciptr->default_value = qctrl.default_value; + switch (qctrl.type) { + default: + case V4L2_CTRL_TYPE_INTEGER: + ciptr->type = pvr2_ctl_int; + ciptr->def.type_int.min_value = qctrl.minimum; + ciptr->def.type_int.max_value = qctrl.maximum; + break; + case V4L2_CTRL_TYPE_BOOLEAN: + ciptr->type = pvr2_ctl_bool; + break; + case V4L2_CTRL_TYPE_MENU: + ciptr->type = pvr2_ctl_enum; + ciptr->def.type_enum.value_names = + cx2341x_ctrl_get_menu(ciptr->v4l_id); + for (cnt1 = 0; + ciptr->def.type_enum.value_names[cnt1] != NULL; + cnt1++) { } + ciptr->def.type_enum.count = cnt1; + break; + } + cptr->info = ciptr; + } + + // Initialize video standard enum dynamic control + cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDENUM); + if (cptr) { + memcpy(&hdw->std_info_enum,cptr->info, + sizeof(hdw->std_info_enum)); + cptr->info = &hdw->std_info_enum; + + } + // Initialize control data regarding video standard masks + valid_std_mask = pvr2_std_get_usable(); + for (idx = 0; idx < 32; idx++) { + if (!(valid_std_mask & (1 << idx))) continue; + cnt1 = pvr2_std_id_to_str( + hdw->std_mask_names[idx], + sizeof(hdw->std_mask_names[idx])-1, + 1 << idx); + hdw->std_mask_names[idx][cnt1] = 0; + } + cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDAVAIL); + if (cptr) { + memcpy(&hdw->std_info_avail,cptr->info, + sizeof(hdw->std_info_avail)); + cptr->info = &hdw->std_info_avail; + hdw->std_info_avail.def.type_bitmask.bit_names = + hdw->std_mask_ptrs; + hdw->std_info_avail.def.type_bitmask.valid_bits = + valid_std_mask; + } + cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR); + if (cptr) { + memcpy(&hdw->std_info_cur,cptr->info, + sizeof(hdw->std_info_cur)); + cptr->info = &hdw->std_info_cur; + hdw->std_info_cur.def.type_bitmask.bit_names = + hdw->std_mask_ptrs; + hdw->std_info_avail.def.type_bitmask.valid_bits = + valid_std_mask; + } + + hdw->eeprom_addr = -1; + hdw->unit_number = -1; + hdw->v4l_minor_number = -1; + hdw->ctl_write_buffer = kmalloc(PVR2_CTL_BUFFSIZE,GFP_KERNEL); + if (!hdw->ctl_write_buffer) goto fail; + hdw->ctl_read_buffer = kmalloc(PVR2_CTL_BUFFSIZE,GFP_KERNEL); + if (!hdw->ctl_read_buffer) goto fail; + hdw->ctl_write_urb = usb_alloc_urb(0,GFP_KERNEL); + if (!hdw->ctl_write_urb) goto fail; + hdw->ctl_read_urb = usb_alloc_urb(0,GFP_KERNEL); + if (!hdw->ctl_read_urb) goto fail; + + down(&pvr2_unit_sem); do { + for (idx = 0; idx < PVR_NUM; idx++) { + if (unit_pointers[idx]) continue; + hdw->unit_number = idx; + unit_pointers[idx] = hdw; + break; + } + } while (0); up(&pvr2_unit_sem); + + cnt1 = 0; + cnt2 = scnprintf(hdw->name+cnt1,sizeof(hdw->name)-cnt1,"pvrusb2"); + cnt1 += cnt2; + if (hdw->unit_number >= 0) { + cnt2 = scnprintf(hdw->name+cnt1,sizeof(hdw->name)-cnt1,"_%c", + ('a' + hdw->unit_number)); + cnt1 += cnt2; + } + if (cnt1 >= sizeof(hdw->name)) cnt1 = sizeof(hdw->name)-1; + hdw->name[cnt1] = 0; + + pvr2_trace(PVR2_TRACE_INIT,"Driver unit number is %d, name is %s", + hdw->unit_number,hdw->name); + + hdw->tuner_type = -1; + hdw->flag_ok = !0; + /* Initialize the mask of subsystems that we will shut down when we + stop streaming. */ + hdw->subsys_stream_mask = PVR2_SUBSYS_RUN_ALL; + hdw->subsys_stream_mask |= (1<<PVR2_SUBSYS_B_ENC_CFG); + + pvr2_trace(PVR2_TRACE_INIT,"subsys_stream_mask: 0x%lx", + hdw->subsys_stream_mask); + + hdw->usb_intf = intf; + hdw->usb_dev = interface_to_usbdev(intf); + + ifnum = hdw->usb_intf->cur_altsetting->desc.bInterfaceNumber; + usb_set_interface(hdw->usb_dev,ifnum,0); + + mutex_init(&hdw->ctl_lock_mutex); + mutex_init(&hdw->big_lock_mutex); + + return hdw; + fail: + if (hdw) { + if (hdw->ctl_read_urb) usb_free_urb(hdw->ctl_read_urb); + if (hdw->ctl_write_urb) usb_free_urb(hdw->ctl_write_urb); + if (hdw->ctl_read_buffer) kfree(hdw->ctl_read_buffer); + if (hdw->ctl_write_buffer) kfree(hdw->ctl_write_buffer); + if (hdw->controls) kfree(hdw->controls); + if (hdw->mpeg_ctrl_info) kfree(hdw->mpeg_ctrl_info); + kfree(hdw); + } + return 0; +} + + +/* Remove _all_ associations between this driver and the underlying USB + layer. */ +void pvr2_hdw_remove_usb_stuff(struct pvr2_hdw *hdw) +{ + if (hdw->flag_disconnected) return; + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_remove_usb_stuff: hdw=%p",hdw); + if (hdw->ctl_read_urb) { + usb_kill_urb(hdw->ctl_read_urb); + usb_free_urb(hdw->ctl_read_urb); + hdw->ctl_read_urb = 0; + } + if (hdw->ctl_write_urb) { + usb_kill_urb(hdw->ctl_write_urb); + usb_free_urb(hdw->ctl_write_urb); + hdw->ctl_write_urb = 0; + } + if (hdw->ctl_read_buffer) { + kfree(hdw->ctl_read_buffer); + hdw->ctl_read_buffer = 0; + } + if (hdw->ctl_write_buffer) { + kfree(hdw->ctl_write_buffer); + hdw->ctl_write_buffer = 0; + } + pvr2_hdw_render_useless_unlocked(hdw); + hdw->flag_disconnected = !0; + hdw->usb_dev = 0; + hdw->usb_intf = 0; +} + + +/* Destroy hardware interaction structure */ +void pvr2_hdw_destroy(struct pvr2_hdw *hdw) +{ + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_destroy: hdw=%p",hdw); + if (hdw->fw_buffer) { + kfree(hdw->fw_buffer); + hdw->fw_buffer = 0; + } + if (hdw->vid_stream) { + pvr2_stream_destroy(hdw->vid_stream); + hdw->vid_stream = 0; + } + if (hdw->audio_stat) { + hdw->audio_stat->detach(hdw->audio_stat->ctxt); + } + if (hdw->decoder_ctrl) { + hdw->decoder_ctrl->detach(hdw->decoder_ctrl->ctxt); + } + pvr2_i2c_core_done(hdw); + pvr2_hdw_remove_usb_stuff(hdw); + down(&pvr2_unit_sem); do { + if ((hdw->unit_number >= 0) && + (hdw->unit_number < PVR_NUM) && + (unit_pointers[hdw->unit_number] == hdw)) { + unit_pointers[hdw->unit_number] = 0; + } + } while (0); up(&pvr2_unit_sem); + if (hdw->controls) kfree(hdw->controls); + if (hdw->mpeg_ctrl_info) kfree(hdw->mpeg_ctrl_info); + if (hdw->std_defs) kfree(hdw->std_defs); + if (hdw->std_enum_names) kfree(hdw->std_enum_names); + kfree(hdw); +} + + +int pvr2_hdw_init_ok(struct pvr2_hdw *hdw) +{ + return hdw->flag_init_ok; +} + + +int pvr2_hdw_dev_ok(struct pvr2_hdw *hdw) +{ + return (hdw && hdw->flag_ok); +} + + +/* Called when hardware has been unplugged */ +void pvr2_hdw_disconnect(struct pvr2_hdw *hdw) +{ + pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_disconnect(hdw=%p)",hdw); + LOCK_TAKE(hdw->big_lock); + LOCK_TAKE(hdw->ctl_lock); + pvr2_hdw_remove_usb_stuff(hdw); + LOCK_GIVE(hdw->ctl_lock); + LOCK_GIVE(hdw->big_lock); +} + + +// Attempt to autoselect an appropriate value for std_enum_cur given +// whatever is currently in std_mask_cur +void pvr2_hdw_internal_find_stdenum(struct pvr2_hdw *hdw) +{ + unsigned int idx; + for (idx = 1; idx < hdw->std_enum_cnt; idx++) { + if (hdw->std_defs[idx-1].id == hdw->std_mask_cur) { + hdw->std_enum_cur = idx; + return; + } + } + hdw->std_enum_cur = 0; +} + + +// Calculate correct set of enumerated standards based on currently known +// set of available standards bits. +void pvr2_hdw_internal_set_std_avail(struct pvr2_hdw *hdw) +{ + struct v4l2_standard *newstd; + unsigned int std_cnt; + unsigned int idx; + + newstd = pvr2_std_create_enum(&std_cnt,hdw->std_mask_avail); + + if (hdw->std_defs) { + kfree(hdw->std_defs); + hdw->std_defs = 0; + } + hdw->std_enum_cnt = 0; + if (hdw->std_enum_names) { + kfree(hdw->std_enum_names); + hdw->std_enum_names = 0; + } + + if (!std_cnt) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "WARNING: Failed to identify any viable standards"); + } + hdw->std_enum_names = kmalloc(sizeof(char *)*(std_cnt+1),GFP_KERNEL); + hdw->std_enum_names[0] = "none"; + for (idx = 0; idx < std_cnt; idx++) { + hdw->std_enum_names[idx+1] = + newstd[idx].name; + } + // Set up the dynamic control for this standard + hdw->std_info_enum.def.type_enum.value_names = hdw->std_enum_names; + hdw->std_info_enum.def.type_enum.count = std_cnt+1; + hdw->std_defs = newstd; + hdw->std_enum_cnt = std_cnt+1; + hdw->std_enum_cur = 0; + hdw->std_info_cur.def.type_bitmask.valid_bits = hdw->std_mask_avail; +} + + +int pvr2_hdw_get_stdenum_value(struct pvr2_hdw *hdw, + struct v4l2_standard *std, + unsigned int idx) +{ + int ret = -EINVAL; + if (!idx) return ret; + LOCK_TAKE(hdw->big_lock); do { + if (idx >= hdw->std_enum_cnt) break; + idx--; + memcpy(std,hdw->std_defs+idx,sizeof(*std)); + ret = 0; + } while (0); LOCK_GIVE(hdw->big_lock); + return ret; +} + + +/* Get the number of defined controls */ +unsigned int pvr2_hdw_get_ctrl_count(struct pvr2_hdw *hdw) +{ + return hdw->control_cnt; +} + + +/* Retrieve a control handle given its index (0..count-1) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_index(struct pvr2_hdw *hdw, + unsigned int idx) +{ + if (idx >= hdw->control_cnt) return 0; + return hdw->controls + idx; +} + + +/* Retrieve a control handle given its index (0..count-1) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_id(struct pvr2_hdw *hdw, + unsigned int ctl_id) +{ + struct pvr2_ctrl *cptr; + unsigned int idx; + int i; + + /* This could be made a lot more efficient, but for now... */ + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + i = cptr->info->internal_id; + if (i && (i == ctl_id)) return cptr; + } + return 0; +} + + +/* Given a V4L ID, retrieve the control structure associated with it. */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_v4l(struct pvr2_hdw *hdw,unsigned int ctl_id) +{ + struct pvr2_ctrl *cptr; + unsigned int idx; + int i; + + /* This could be made a lot more efficient, but for now... */ + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + i = cptr->info->v4l_id; + if (i && (i == ctl_id)) return cptr; + } + return 0; +} + + +/* Given a V4L ID for its immediate predecessor, retrieve the control + structure associated with it. */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_nextv4l(struct pvr2_hdw *hdw, + unsigned int ctl_id) +{ + struct pvr2_ctrl *cptr,*cp2; + unsigned int idx; + int i; + + /* This could be made a lot more efficient, but for now... */ + cp2 = 0; + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + i = cptr->info->v4l_id; + if (!i) continue; + if (i <= ctl_id) continue; + if (cp2 && (cp2->info->v4l_id < i)) continue; + cp2 = cptr; + } + return cp2; + return 0; +} + + +static const char *get_ctrl_typename(enum pvr2_ctl_type tp) +{ + switch (tp) { + case pvr2_ctl_int: return "integer"; + case pvr2_ctl_enum: return "enum"; + case pvr2_ctl_bool: return "boolean"; + case pvr2_ctl_bitmask: return "bitmask"; + } + return ""; +} + + +/* Commit all control changes made up to this point. Subsystems can be + indirectly affected by these changes. For a given set of things being + committed, we'll clear the affected subsystem bits and then once we're + done committing everything we'll make a request to restore the subsystem + state(s) back to their previous value before this function was called. + Thus we can automatically reconfigure affected pieces of the driver as + controls are changed. */ +int pvr2_hdw_commit_ctl_internal(struct pvr2_hdw *hdw) +{ + unsigned long saved_subsys_mask = hdw->subsys_enabled_mask; + unsigned long stale_subsys_mask = 0; + unsigned int idx; + struct pvr2_ctrl *cptr; + int value; + int commit_flag = 0; + char buf[100]; + unsigned int bcnt,ccnt; + + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + if (cptr->info->is_dirty == 0) continue; + if (!cptr->info->is_dirty(cptr)) continue; + if (!commit_flag) { + commit_flag = !0; + } + + bcnt = scnprintf(buf,sizeof(buf),"\"%s\" <-- ", + cptr->info->name); + value = 0; + cptr->info->get_value(cptr,&value); + pvr2_ctrl_value_to_sym_internal(cptr,~0,value, + buf+bcnt, + sizeof(buf)-bcnt,&ccnt); + bcnt += ccnt; + bcnt += scnprintf(buf+bcnt,sizeof(buf)-bcnt," <%s>", + get_ctrl_typename(cptr->info->type)); + pvr2_trace(PVR2_TRACE_CTL, + "/*--TRACE_COMMIT--*/ %.*s", + bcnt,buf); + } + + if (!commit_flag) { + /* Nothing has changed */ + return 0; + } + + /* When video standard changes, reset the hres and vres values - + but if the user has pending changes there, then let the changes + take priority. */ + if (hdw->std_dirty) { + /* Rewrite the vertical resolution to be appropriate to the + video standard that has been selected. */ + int nvres; + if (hdw->std_mask_cur & V4L2_STD_525_60) { + nvres = 480; + } else { + nvres = 576; + } + if (nvres != hdw->res_ver_val) { + hdw->res_ver_val = nvres; + hdw->res_ver_dirty = !0; + } + } + + if (hdw->std_dirty || + 0) { + /* If any of this changes, then the encoder needs to be + reconfigured, and we need to reset the stream. */ + stale_subsys_mask |= (1<<PVR2_SUBSYS_B_ENC_CFG); + stale_subsys_mask |= hdw->subsys_stream_mask; + } + + if (hdw->srate_dirty) { + /* Write new sample rate into control structure since + * the master copy is stale. We must track srate + * separate from the mpeg control structure because + * other logic also uses this value. */ + struct v4l2_ext_controls cs; + struct v4l2_ext_control c1; + memset(&cs,0,sizeof(cs)); + memset(&c1,0,sizeof(c1)); + cs.controls = &c1; + cs.count = 1; + c1.id = V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ; + c1.value = hdw->srate_val; + cx2341x_ext_ctrls(&hdw->enc_ctl_state,&cs,VIDIOC_S_EXT_CTRLS); + } + + /* Scan i2c core at this point - before we clear all the dirty + bits. Various parts of the i2c core will notice dirty bits as + appropriate and arrange to broadcast or directly send updates to + the client drivers in order to keep everything in sync */ + pvr2_i2c_core_check_stale(hdw); + + for (idx = 0; idx < hdw->control_cnt; idx++) { + cptr = hdw->controls + idx; + if (!cptr->info->clear_dirty) continue; + cptr->info->clear_dirty(cptr); + } + + /* Now execute i2c core update */ + pvr2_i2c_core_sync(hdw); + + pvr2_hdw_subsys_bit_chg_no_lock(hdw,stale_subsys_mask,0); + pvr2_hdw_subsys_bit_chg_no_lock(hdw,~0,saved_subsys_mask); + + return 0; +} + + +int pvr2_hdw_commit_ctl(struct pvr2_hdw *hdw) +{ + LOCK_TAKE(hdw->big_lock); do { + pvr2_hdw_commit_ctl_internal(hdw); + } while (0); LOCK_GIVE(hdw->big_lock); + return 0; +} + + +void pvr2_hdw_poll(struct pvr2_hdw *hdw) +{ + LOCK_TAKE(hdw->big_lock); do { + pvr2_i2c_core_sync(hdw); + } while (0); LOCK_GIVE(hdw->big_lock); +} + + +void pvr2_hdw_setup_poll_trigger(struct pvr2_hdw *hdw, + void (*func)(void *), + void *data) +{ + LOCK_TAKE(hdw->big_lock); do { + hdw->poll_trigger_func = func; + hdw->poll_trigger_data = data; + } while (0); LOCK_GIVE(hdw->big_lock); +} + + +void pvr2_hdw_poll_trigger_unlocked(struct pvr2_hdw *hdw) +{ + if (hdw->poll_trigger_func) { + hdw->poll_trigger_func(hdw->poll_trigger_data); + } +} + + +void pvr2_hdw_poll_trigger(struct pvr2_hdw *hdw) +{ + LOCK_TAKE(hdw->big_lock); do { + pvr2_hdw_poll_trigger_unlocked(hdw); + } while (0); LOCK_GIVE(hdw->big_lock); +} + + +/* Return name for this driver instance */ +const char *pvr2_hdw_get_driver_name(struct pvr2_hdw *hdw) +{ + return hdw->name; +} + + +/* Return bit mask indicating signal status */ +unsigned int pvr2_hdw_get_signal_status_internal(struct pvr2_hdw *hdw) +{ + unsigned int msk = 0; + switch (hdw->input_val) { + case PVR2_CVAL_INPUT_TV: + case PVR2_CVAL_INPUT_RADIO: + if (hdw->decoder_ctrl && + hdw->decoder_ctrl->tuned(hdw->decoder_ctrl->ctxt)) { + msk |= PVR2_SIGNAL_OK; + if (hdw->audio_stat && + hdw->audio_stat->status(hdw->audio_stat->ctxt)) { + if (hdw->flag_stereo) { + msk |= PVR2_SIGNAL_STEREO; + } + if (hdw->flag_bilingual) { + msk |= PVR2_SIGNAL_SAP; + } + } + } + break; + default: + msk |= PVR2_SIGNAL_OK | PVR2_SIGNAL_STEREO; + } + return msk; +} + + +int pvr2_hdw_is_hsm(struct pvr2_hdw *hdw) +{ + int result; + LOCK_TAKE(hdw->ctl_lock); do { + hdw->cmd_buffer[0] = 0x0b; + result = pvr2_send_request(hdw, + hdw->cmd_buffer,1, + hdw->cmd_buffer,1); + if (result < 0) break; + result = (hdw->cmd_buffer[0] != 0); + } while(0); LOCK_GIVE(hdw->ctl_lock); + return result; +} + + +/* Return bit mask indicating signal status */ +unsigned int pvr2_hdw_get_signal_status(struct pvr2_hdw *hdw) +{ + unsigned int msk = 0; + LOCK_TAKE(hdw->big_lock); do { + msk = pvr2_hdw_get_signal_status_internal(hdw); + } while (0); LOCK_GIVE(hdw->big_lock); + return msk; +} + + +/* Get handle to video output stream */ +struct pvr2_stream *pvr2_hdw_get_video_stream(struct pvr2_hdw *hp) +{ + return hp->vid_stream; +} + + +void pvr2_hdw_trigger_module_log(struct pvr2_hdw *hdw) +{ + int nr = pvr2_hdw_get_unit_number(hdw); + LOCK_TAKE(hdw->big_lock); do { + hdw->log_requested = !0; + printk(KERN_INFO "pvrusb2: ================= START STATUS CARD #%d =================\n", nr); + pvr2_i2c_core_check_stale(hdw); + hdw->log_requested = 0; + pvr2_i2c_core_sync(hdw); + pvr2_trace(PVR2_TRACE_INFO,"cx2341x config:"); + cx2341x_log_status(&hdw->enc_ctl_state, "pvrusb2"); + printk(KERN_INFO "pvrusb2: ================== END STATUS CARD #%d ==================\n", nr); + } while (0); LOCK_GIVE(hdw->big_lock); +} + +void pvr2_hdw_cpufw_set_enabled(struct pvr2_hdw *hdw, int enable_flag) +{ + int ret; + u16 address; + unsigned int pipe; + LOCK_TAKE(hdw->big_lock); do { + if ((hdw->fw_buffer == 0) == !enable_flag) break; + + if (!enable_flag) { + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Cleaning up after CPU firmware fetch"); + kfree(hdw->fw_buffer); + hdw->fw_buffer = 0; + hdw->fw_size = 0; + /* Now release the CPU. It will disconnect and + reconnect later. */ + pvr2_hdw_cpureset_assert(hdw,0); + break; + } + + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Preparing to suck out CPU firmware"); + hdw->fw_size = 0x2000; + hdw->fw_buffer = kmalloc(hdw->fw_size,GFP_KERNEL); + if (!hdw->fw_buffer) { + hdw->fw_size = 0; + break; + } + + memset(hdw->fw_buffer,0,hdw->fw_size); + + /* We have to hold the CPU during firmware upload. */ + pvr2_hdw_cpureset_assert(hdw,1); + + /* download the firmware from address 0000-1fff in 2048 + (=0x800) bytes chunk. */ + + pvr2_trace(PVR2_TRACE_FIRMWARE,"Grabbing CPU firmware"); + pipe = usb_rcvctrlpipe(hdw->usb_dev, 0); + for(address = 0; address < hdw->fw_size; address += 0x800) { + ret = usb_control_msg(hdw->usb_dev,pipe,0xa0,0xc0, + address,0, + hdw->fw_buffer+address,0x800,HZ); + if (ret < 0) break; + } + + pvr2_trace(PVR2_TRACE_FIRMWARE,"Done grabbing CPU firmware"); + + } while (0); LOCK_GIVE(hdw->big_lock); +} + + +/* Return true if we're in a mode for retrieval CPU firmware */ +int pvr2_hdw_cpufw_get_enabled(struct pvr2_hdw *hdw) +{ + return hdw->fw_buffer != 0; +} + + +int pvr2_hdw_cpufw_get(struct pvr2_hdw *hdw,unsigned int offs, + char *buf,unsigned int cnt) +{ + int ret = -EINVAL; + LOCK_TAKE(hdw->big_lock); do { + if (!buf) break; + if (!cnt) break; + + if (!hdw->fw_buffer) { + ret = -EIO; + break; + } + + if (offs >= hdw->fw_size) { + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Read firmware data offs=%d EOF", + offs); + ret = 0; + break; + } + + if (offs + cnt > hdw->fw_size) cnt = hdw->fw_size - offs; + + memcpy(buf,hdw->fw_buffer+offs,cnt); + + pvr2_trace(PVR2_TRACE_FIRMWARE, + "Read firmware data offs=%d cnt=%d", + offs,cnt); + ret = cnt; + } while (0); LOCK_GIVE(hdw->big_lock); + + return ret; +} + + +int pvr2_hdw_v4l_get_minor_number(struct pvr2_hdw *hdw) +{ + return hdw->v4l_minor_number; +} + + +/* Store the v4l minor device number */ +void pvr2_hdw_v4l_store_minor_number(struct pvr2_hdw *hdw,int v) +{ + hdw->v4l_minor_number = v; +} + + +void pvr2_reset_ctl_endpoints(struct pvr2_hdw *hdw) +{ + if (!hdw->usb_dev) return; + usb_settoggle(hdw->usb_dev, PVR2_CTL_WRITE_ENDPOINT & 0xf, + !(PVR2_CTL_WRITE_ENDPOINT & USB_DIR_IN), 0); + usb_settoggle(hdw->usb_dev, PVR2_CTL_READ_ENDPOINT & 0xf, + !(PVR2_CTL_READ_ENDPOINT & USB_DIR_IN), 0); + usb_clear_halt(hdw->usb_dev, + usb_rcvbulkpipe(hdw->usb_dev, + PVR2_CTL_READ_ENDPOINT & 0x7f)); + usb_clear_halt(hdw->usb_dev, + usb_sndbulkpipe(hdw->usb_dev, + PVR2_CTL_WRITE_ENDPOINT & 0x7f)); +} + + +static void pvr2_ctl_write_complete(struct urb *urb, struct pt_regs *regs) +{ + struct pvr2_hdw *hdw = urb->context; + hdw->ctl_write_pend_flag = 0; + if (hdw->ctl_read_pend_flag) return; + complete(&hdw->ctl_done); +} + + +static void pvr2_ctl_read_complete(struct urb *urb, struct pt_regs *regs) +{ + struct pvr2_hdw *hdw = urb->context; + hdw->ctl_read_pend_flag = 0; + if (hdw->ctl_write_pend_flag) return; + complete(&hdw->ctl_done); +} + + +static void pvr2_ctl_timeout(unsigned long data) +{ + struct pvr2_hdw *hdw = (struct pvr2_hdw *)data; + if (hdw->ctl_write_pend_flag || hdw->ctl_read_pend_flag) { + hdw->ctl_timeout_flag = !0; + if (hdw->ctl_write_pend_flag && hdw->ctl_write_urb) { + usb_unlink_urb(hdw->ctl_write_urb); + } + if (hdw->ctl_read_pend_flag && hdw->ctl_read_urb) { + usb_unlink_urb(hdw->ctl_read_urb); + } + } +} + + +int pvr2_send_request_ex(struct pvr2_hdw *hdw, + unsigned int timeout,int probe_fl, + void *write_data,unsigned int write_len, + void *read_data,unsigned int read_len) +{ + unsigned int idx; + int status = 0; + struct timer_list timer; + if (!hdw->ctl_lock_held) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Attempted to execute control transfer" + " without lock!!"); + return -EDEADLK; + } + if ((!hdw->flag_ok) && !probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Attempted to execute control transfer" + " when device not ok"); + return -EIO; + } + if (!(hdw->ctl_read_urb && hdw->ctl_write_urb)) { + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Attempted to execute control transfer" + " when USB is disconnected"); + } + return -ENOTTY; + } + + /* Ensure that we have sane parameters */ + if (!write_data) write_len = 0; + if (!read_data) read_len = 0; + if (write_len > PVR2_CTL_BUFFSIZE) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Attempted to execute %d byte" + " control-write transfer (limit=%d)", + write_len,PVR2_CTL_BUFFSIZE); + return -EINVAL; + } + if (read_len > PVR2_CTL_BUFFSIZE) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Attempted to execute %d byte" + " control-read transfer (limit=%d)", + write_len,PVR2_CTL_BUFFSIZE); + return -EINVAL; + } + if ((!write_len) && (!read_len)) { + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "Attempted to execute null control transfer?"); + return -EINVAL; + } + + + hdw->cmd_debug_state = 1; + if (write_len) { + hdw->cmd_debug_code = ((unsigned char *)write_data)[0]; + } else { + hdw->cmd_debug_code = 0; + } + hdw->cmd_debug_write_len = write_len; + hdw->cmd_debug_read_len = read_len; + + /* Initialize common stuff */ + init_completion(&hdw->ctl_done); + hdw->ctl_timeout_flag = 0; + hdw->ctl_write_pend_flag = 0; + hdw->ctl_read_pend_flag = 0; + init_timer(&timer); + timer.expires = jiffies + timeout; + timer.data = (unsigned long)hdw; + timer.function = pvr2_ctl_timeout; + + if (write_len) { + hdw->cmd_debug_state = 2; + /* Transfer write data to internal buffer */ + for (idx = 0; idx < write_len; idx++) { + hdw->ctl_write_buffer[idx] = + ((unsigned char *)write_data)[idx]; + } + /* Initiate a write request */ + usb_fill_bulk_urb(hdw->ctl_write_urb, + hdw->usb_dev, + usb_sndbulkpipe(hdw->usb_dev, + PVR2_CTL_WRITE_ENDPOINT), + hdw->ctl_write_buffer, + write_len, + pvr2_ctl_write_complete, + hdw); + hdw->ctl_write_urb->actual_length = 0; + hdw->ctl_write_pend_flag = !0; + status = usb_submit_urb(hdw->ctl_write_urb,GFP_KERNEL); + if (status < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to submit write-control" + " URB status=%d",status); + hdw->ctl_write_pend_flag = 0; + goto done; + } + } + + if (read_len) { + hdw->cmd_debug_state = 3; + memset(hdw->ctl_read_buffer,0x43,read_len); + /* Initiate a read request */ + usb_fill_bulk_urb(hdw->ctl_read_urb, + hdw->usb_dev, + usb_rcvbulkpipe(hdw->usb_dev, + PVR2_CTL_READ_ENDPOINT), + hdw->ctl_read_buffer, + read_len, + pvr2_ctl_read_complete, + hdw); + hdw->ctl_read_urb->actual_length = 0; + hdw->ctl_read_pend_flag = !0; + status = usb_submit_urb(hdw->ctl_read_urb,GFP_KERNEL); + if (status < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to submit read-control" + " URB status=%d",status); + hdw->ctl_read_pend_flag = 0; + goto done; + } + } + + /* Start timer */ + add_timer(&timer); + + /* Now wait for all I/O to complete */ + hdw->cmd_debug_state = 4; + while (hdw->ctl_write_pend_flag || hdw->ctl_read_pend_flag) { + wait_for_completion(&hdw->ctl_done); + } + hdw->cmd_debug_state = 5; + + /* Stop timer */ + del_timer_sync(&timer); + + hdw->cmd_debug_state = 6; + status = 0; + + if (hdw->ctl_timeout_flag) { + status = -ETIMEDOUT; + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Timed out control-write"); + } + goto done; + } + + if (write_len) { + /* Validate results of write request */ + if ((hdw->ctl_write_urb->status != 0) && + (hdw->ctl_write_urb->status != -ENOENT) && + (hdw->ctl_write_urb->status != -ESHUTDOWN) && + (hdw->ctl_write_urb->status != -ECONNRESET)) { + /* USB subsystem is reporting some kind of failure + on the write */ + status = hdw->ctl_write_urb->status; + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "control-write URB failure," + " status=%d", + status); + } + goto done; + } + if (hdw->ctl_write_urb->actual_length < write_len) { + /* Failed to write enough data */ + status = -EIO; + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "control-write URB short," + " expected=%d got=%d", + write_len, + hdw->ctl_write_urb->actual_length); + } + goto done; + } + } + if (read_len) { + /* Validate results of read request */ + if ((hdw->ctl_read_urb->status != 0) && + (hdw->ctl_read_urb->status != -ENOENT) && + (hdw->ctl_read_urb->status != -ESHUTDOWN) && + (hdw->ctl_read_urb->status != -ECONNRESET)) { + /* USB subsystem is reporting some kind of failure + on the read */ + status = hdw->ctl_read_urb->status; + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "control-read URB failure," + " status=%d", + status); + } + goto done; + } + if (hdw->ctl_read_urb->actual_length < read_len) { + /* Failed to read enough data */ + status = -EIO; + if (!probe_fl) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "control-read URB short," + " expected=%d got=%d", + read_len, + hdw->ctl_read_urb->actual_length); + } + goto done; + } + /* Transfer retrieved data out from internal buffer */ + for (idx = 0; idx < read_len; idx++) { + ((unsigned char *)read_data)[idx] = + hdw->ctl_read_buffer[idx]; + } + } + + done: + + hdw->cmd_debug_state = 0; + if ((status < 0) && (!probe_fl)) { + pvr2_hdw_render_useless_unlocked(hdw); + } + return status; +} + + +int pvr2_send_request(struct pvr2_hdw *hdw, + void *write_data,unsigned int write_len, + void *read_data,unsigned int read_len) +{ + return pvr2_send_request_ex(hdw,HZ*4,0, + write_data,write_len, + read_data,read_len); +} + +int pvr2_write_register(struct pvr2_hdw *hdw, u16 reg, u32 data) +{ + int ret; + + LOCK_TAKE(hdw->ctl_lock); + + hdw->cmd_buffer[0] = 0x04; /* write register prefix */ + PVR2_DECOMPOSE_LE(hdw->cmd_buffer,1,data); + hdw->cmd_buffer[5] = 0; + hdw->cmd_buffer[6] = (reg >> 8) & 0xff; + hdw->cmd_buffer[7] = reg & 0xff; + + + ret = pvr2_send_request(hdw, hdw->cmd_buffer, 8, hdw->cmd_buffer, 0); + + LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + + +int pvr2_read_register(struct pvr2_hdw *hdw, u16 reg, u32 *data) +{ + int ret = 0; + + LOCK_TAKE(hdw->ctl_lock); + + hdw->cmd_buffer[0] = 0x05; /* read register prefix */ + hdw->cmd_buffer[1] = 0; + hdw->cmd_buffer[2] = 0; + hdw->cmd_buffer[3] = 0; + hdw->cmd_buffer[4] = 0; + hdw->cmd_buffer[5] = 0; + hdw->cmd_buffer[6] = (reg >> 8) & 0xff; + hdw->cmd_buffer[7] = reg & 0xff; + + ret |= pvr2_send_request(hdw, hdw->cmd_buffer, 8, hdw->cmd_buffer, 4); + *data = PVR2_COMPOSE_LE(hdw->cmd_buffer,0); + + LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + + +int pvr2_write_u16(struct pvr2_hdw *hdw, u16 data, int res) +{ + int ret; + + LOCK_TAKE(hdw->ctl_lock); + + hdw->cmd_buffer[0] = (data >> 8) & 0xff; + hdw->cmd_buffer[1] = data & 0xff; + + ret = pvr2_send_request(hdw, hdw->cmd_buffer, 2, hdw->cmd_buffer, res); + + LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + + +int pvr2_write_u8(struct pvr2_hdw *hdw, u8 data, int res) +{ + int ret; + + LOCK_TAKE(hdw->ctl_lock); + + hdw->cmd_buffer[0] = data; + + ret = pvr2_send_request(hdw, hdw->cmd_buffer, 1, hdw->cmd_buffer, res); + + LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + + +void pvr2_hdw_render_useless_unlocked(struct pvr2_hdw *hdw) +{ + if (!hdw->flag_ok) return; + pvr2_trace(PVR2_TRACE_INIT,"render_useless"); + hdw->flag_ok = 0; + if (hdw->vid_stream) { + pvr2_stream_setup(hdw->vid_stream,0,0,0); + } + hdw->flag_streaming_enabled = 0; + hdw->subsys_enabled_mask = 0; +} + + +void pvr2_hdw_render_useless(struct pvr2_hdw *hdw) +{ + LOCK_TAKE(hdw->ctl_lock); + pvr2_hdw_render_useless_unlocked(hdw); + LOCK_GIVE(hdw->ctl_lock); +} + + +void pvr2_hdw_device_reset(struct pvr2_hdw *hdw) +{ + int ret; + pvr2_trace(PVR2_TRACE_INIT,"Performing a device reset..."); + ret = usb_lock_device_for_reset(hdw->usb_dev,0); + if (ret == 1) { + ret = usb_reset_device(hdw->usb_dev); + usb_unlock_device(hdw->usb_dev); + } else { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to lock USB device ret=%d",ret); + } + if (init_pause_msec) { + pvr2_trace(PVR2_TRACE_INFO, + "Waiting %u msec for hardware to settle", + init_pause_msec); + msleep(init_pause_msec); + } + +} + + +void pvr2_hdw_cpureset_assert(struct pvr2_hdw *hdw,int val) +{ + char da[1]; + unsigned int pipe; + int ret; + + if (!hdw->usb_dev) return; + + pvr2_trace(PVR2_TRACE_INIT,"cpureset_assert(%d)",val); + + da[0] = val ? 0x01 : 0x00; + + /* Write the CPUCS register on the 8051. The lsb of the register + is the reset bit; a 1 asserts reset while a 0 clears it. */ + pipe = usb_sndctrlpipe(hdw->usb_dev, 0); + ret = usb_control_msg(hdw->usb_dev,pipe,0xa0,0x40,0xe600,0,da,1,HZ); + if (ret < 0) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "cpureset_assert(%d) error=%d",val,ret); + pvr2_hdw_render_useless(hdw); + } +} + + +int pvr2_hdw_cmd_deep_reset(struct pvr2_hdw *hdw) +{ + int status; + LOCK_TAKE(hdw->ctl_lock); do { + pvr2_trace(PVR2_TRACE_INIT,"Requesting uproc hard reset"); + hdw->flag_ok = !0; + hdw->cmd_buffer[0] = 0xdd; + status = pvr2_send_request(hdw,hdw->cmd_buffer,1,0,0); + } while (0); LOCK_GIVE(hdw->ctl_lock); + return status; +} + + +int pvr2_hdw_cmd_powerup(struct pvr2_hdw *hdw) +{ + int status; + LOCK_TAKE(hdw->ctl_lock); do { + pvr2_trace(PVR2_TRACE_INIT,"Requesting powerup"); + hdw->cmd_buffer[0] = 0xde; + status = pvr2_send_request(hdw,hdw->cmd_buffer,1,0,0); + } while (0); LOCK_GIVE(hdw->ctl_lock); + return status; +} + + +int pvr2_hdw_cmd_decoder_reset(struct pvr2_hdw *hdw) +{ + if (!hdw->decoder_ctrl) { + pvr2_trace(PVR2_TRACE_INIT, + "Unable to reset decoder: nothing attached"); + return -ENOTTY; + } + + if (!hdw->decoder_ctrl->force_reset) { + pvr2_trace(PVR2_TRACE_INIT, + "Unable to reset decoder: not implemented"); + return -ENOTTY; + } + + pvr2_trace(PVR2_TRACE_INIT, + "Requesting decoder reset"); + hdw->decoder_ctrl->force_reset(hdw->decoder_ctrl->ctxt); + return 0; +} + + +int pvr2_hdw_cmd_usbstream(struct pvr2_hdw *hdw,int runFl) +{ + int status; + LOCK_TAKE(hdw->ctl_lock); do { + hdw->cmd_buffer[0] = (runFl ? 0x36 : 0x37); + status = pvr2_send_request(hdw,hdw->cmd_buffer,1,0,0); + } while (0); LOCK_GIVE(hdw->ctl_lock); + if (!status) { + hdw->subsys_enabled_mask = + ((hdw->subsys_enabled_mask & + ~(1<<PVR2_SUBSYS_B_USBSTREAM_RUN)) | + (runFl ? (1<<PVR2_SUBSYS_B_USBSTREAM_RUN) : 0)); + } + return status; +} + + +void pvr2_hdw_get_debug_info(const struct pvr2_hdw *hdw, + struct pvr2_hdw_debug_info *ptr) +{ + ptr->big_lock_held = hdw->big_lock_held; + ptr->ctl_lock_held = hdw->ctl_lock_held; + ptr->flag_ok = hdw->flag_ok; + ptr->flag_disconnected = hdw->flag_disconnected; + ptr->flag_init_ok = hdw->flag_init_ok; + ptr->flag_streaming_enabled = hdw->flag_streaming_enabled; + ptr->subsys_flags = hdw->subsys_enabled_mask; + ptr->cmd_debug_state = hdw->cmd_debug_state; + ptr->cmd_code = hdw->cmd_debug_code; + ptr->cmd_debug_write_len = hdw->cmd_debug_write_len; + ptr->cmd_debug_read_len = hdw->cmd_debug_read_len; + ptr->cmd_debug_timeout = hdw->ctl_timeout_flag; + ptr->cmd_debug_write_pend = hdw->ctl_write_pend_flag; + ptr->cmd_debug_read_pend = hdw->ctl_read_pend_flag; + ptr->cmd_debug_rstatus = hdw->ctl_read_urb->status; + ptr->cmd_debug_wstatus = hdw->ctl_read_urb->status; +} + + +int pvr2_hdw_gpio_get_dir(struct pvr2_hdw *hdw,u32 *dp) +{ + return pvr2_read_register(hdw,PVR2_GPIO_DIR,dp); +} + + +int pvr2_hdw_gpio_get_out(struct pvr2_hdw *hdw,u32 *dp) +{ + return pvr2_read_register(hdw,PVR2_GPIO_OUT,dp); +} + + +int pvr2_hdw_gpio_get_in(struct pvr2_hdw *hdw,u32 *dp) +{ + return pvr2_read_register(hdw,PVR2_GPIO_IN,dp); +} + + +int pvr2_hdw_gpio_chg_dir(struct pvr2_hdw *hdw,u32 msk,u32 val) +{ + u32 cval,nval; + int ret; + if (~msk) { + ret = pvr2_read_register(hdw,PVR2_GPIO_DIR,&cval); + if (ret) return ret; + nval = (cval & ~msk) | (val & msk); + pvr2_trace(PVR2_TRACE_GPIO, + "GPIO direction changing 0x%x:0x%x" + " from 0x%x to 0x%x", + msk,val,cval,nval); + } else { + nval = val; + pvr2_trace(PVR2_TRACE_GPIO, + "GPIO direction changing to 0x%x",nval); + } + return pvr2_write_register(hdw,PVR2_GPIO_DIR,nval); +} + + +int pvr2_hdw_gpio_chg_out(struct pvr2_hdw *hdw,u32 msk,u32 val) +{ + u32 cval,nval; + int ret; + if (~msk) { + ret = pvr2_read_register(hdw,PVR2_GPIO_OUT,&cval); + if (ret) return ret; + nval = (cval & ~msk) | (val & msk); + pvr2_trace(PVR2_TRACE_GPIO, + "GPIO output changing 0x%x:0x%x from 0x%x to 0x%x", + msk,val,cval,nval); + } else { + nval = val; + pvr2_trace(PVR2_TRACE_GPIO, + "GPIO output changing to 0x%x",nval); + } + return pvr2_write_register(hdw,PVR2_GPIO_OUT,nval); +} + + +int pvr2_hdw_get_eeprom_addr(struct pvr2_hdw *hdw) +{ + int result; + LOCK_TAKE(hdw->ctl_lock); do { + hdw->cmd_buffer[0] = 0xeb; + result = pvr2_send_request(hdw, + hdw->cmd_buffer,1, + hdw->cmd_buffer,1); + if (result < 0) break; + result = hdw->cmd_buffer[0]; + } while(0); LOCK_GIVE(hdw->ctl_lock); + return result; +} + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw.h b/drivers/media/video/pvrusb2/pvrusb2-hdw.h new file mode 100644 index 00000000000..63f52915443 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-hdw.h @@ -0,0 +1,335 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_HDW_H +#define __PVRUSB2_HDW_H + +#include <linux/usb.h> +#include <linux/videodev2.h> +#include "pvrusb2-io.h" +#include "pvrusb2-ctrl.h" + + +/* Private internal control ids, look these up with + pvr2_hdw_get_ctrl_by_id() - these are NOT visible in V4L */ +#define PVR2_CID_STDENUM 1 +#define PVR2_CID_STDCUR 2 +#define PVR2_CID_STDAVAIL 3 +#define PVR2_CID_INPUT 4 +#define PVR2_CID_AUDIOMODE 5 +#define PVR2_CID_FREQUENCY 6 +#define PVR2_CID_HRES 7 +#define PVR2_CID_VRES 8 + +/* Legal values for the INPUT state variable */ +#define PVR2_CVAL_INPUT_TV 0 +#define PVR2_CVAL_INPUT_SVIDEO 1 +#define PVR2_CVAL_INPUT_COMPOSITE 2 +#define PVR2_CVAL_INPUT_RADIO 3 + +/* Values that pvr2_hdw_get_signal_status() returns */ +#define PVR2_SIGNAL_OK 0x0001 +#define PVR2_SIGNAL_STEREO 0x0002 +#define PVR2_SIGNAL_SAP 0x0004 + + +/* Subsystem definitions - these are various pieces that can be + independently stopped / started. Usually you don't want to mess with + this directly (let the driver handle things itself), but it is useful + for debugging. */ +#define PVR2_SUBSYS_B_ENC_FIRMWARE 0 +#define PVR2_SUBSYS_B_ENC_CFG 1 +#define PVR2_SUBSYS_B_DIGITIZER_RUN 2 +#define PVR2_SUBSYS_B_USBSTREAM_RUN 3 +#define PVR2_SUBSYS_B_ENC_RUN 4 + +#define PVR2_SUBSYS_CFG_ALL ( \ + (1 << PVR2_SUBSYS_B_ENC_FIRMWARE) | \ + (1 << PVR2_SUBSYS_B_ENC_CFG) ) +#define PVR2_SUBSYS_RUN_ALL ( \ + (1 << PVR2_SUBSYS_B_DIGITIZER_RUN) | \ + (1 << PVR2_SUBSYS_B_USBSTREAM_RUN) | \ + (1 << PVR2_SUBSYS_B_ENC_RUN) ) +#define PVR2_SUBSYS_ALL ( \ + PVR2_SUBSYS_CFG_ALL | \ + PVR2_SUBSYS_RUN_ALL ) + +enum pvr2_config { + pvr2_config_empty, + pvr2_config_mpeg, + pvr2_config_vbi, + pvr2_config_radio, +}; + +const char *pvr2_config_get_name(enum pvr2_config); + +struct pvr2_hdw; + +/* Create and return a structure for interacting with the underlying + hardware */ +struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf, + const struct usb_device_id *devid); + +/* Poll for background activity (if any) */ +void pvr2_hdw_poll(struct pvr2_hdw *); + +/* Trigger a poll to take place later at a convenient time */ +void pvr2_hdw_poll_trigger(struct pvr2_hdw *); +void pvr2_hdw_poll_trigger_unlocked(struct pvr2_hdw *); + +/* Register a callback used to trigger a future poll */ +void pvr2_hdw_setup_poll_trigger(struct pvr2_hdw *, + void (*func)(void *), + void *data); + +/* Get pointer to structure given unit number */ +struct pvr2_hdw *pvr2_hdw_find(int unit_number); + +/* Destroy hardware interaction structure */ +void pvr2_hdw_destroy(struct pvr2_hdw *); + +/* Set up the structure and attempt to put the device into a usable state. + This can be a time-consuming operation, which is why it is not done + internally as part of the create() step. Return value is exactly the + same as pvr2_hdw_init_ok(). */ +int pvr2_hdw_setup(struct pvr2_hdw *); + +/* Initialization succeeded */ +int pvr2_hdw_init_ok(struct pvr2_hdw *); + +/* Return true if in the ready (normal) state */ +int pvr2_hdw_dev_ok(struct pvr2_hdw *); + +/* Return small integer number [1..N] for logical instance number of this + device. This is useful for indexing array-valued module parameters. */ +int pvr2_hdw_get_unit_number(struct pvr2_hdw *); + +/* Get pointer to underlying USB device */ +struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *); + +/* Retrieve serial number of device */ +unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *); + +/* Called when hardware has been unplugged */ +void pvr2_hdw_disconnect(struct pvr2_hdw *); + +/* Get the number of defined controls */ +unsigned int pvr2_hdw_get_ctrl_count(struct pvr2_hdw *); + +/* Retrieve a control handle given its index (0..count-1) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_index(struct pvr2_hdw *,unsigned int); + +/* Retrieve a control handle given its internal ID (if any) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_id(struct pvr2_hdw *,unsigned int); + +/* Retrieve a control handle given its V4L ID (if any) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_v4l(struct pvr2_hdw *,unsigned int ctl_id); + +/* Retrieve a control handle given its immediate predecessor V4L ID (if any) */ +struct pvr2_ctrl *pvr2_hdw_get_ctrl_nextv4l(struct pvr2_hdw *, + unsigned int ctl_id); + +/* Commit all control changes made up to this point */ +int pvr2_hdw_commit_ctl(struct pvr2_hdw *); + +/* Return name for this driver instance */ +const char *pvr2_hdw_get_driver_name(struct pvr2_hdw *); + +/* Return PVR2_SIGNAL_XXXX bit mask indicating signal status */ +unsigned int pvr2_hdw_get_signal_status(struct pvr2_hdw *); + +/* Query device and see if it thinks it is on a high-speed USB link */ +int pvr2_hdw_is_hsm(struct pvr2_hdw *); + +/* Turn streaming on/off */ +int pvr2_hdw_set_streaming(struct pvr2_hdw *,int); + +/* Find out if streaming is on */ +int pvr2_hdw_get_streaming(struct pvr2_hdw *); + +/* Configure the type of stream to generate */ +int pvr2_hdw_set_stream_type(struct pvr2_hdw *, enum pvr2_config); + +/* Get handle to video output stream */ +struct pvr2_stream *pvr2_hdw_get_video_stream(struct pvr2_hdw *); + +/* Emit a video standard struct */ +int pvr2_hdw_get_stdenum_value(struct pvr2_hdw *hdw,struct v4l2_standard *std, + unsigned int idx); + +/* Enable / disable various pieces of hardware. Items to change are + identified by bit positions within msk, and new state for each item is + identified by corresponding bit positions within val. */ +void pvr2_hdw_subsys_bit_chg(struct pvr2_hdw *hdw, + unsigned long msk,unsigned long val); + +/* Shortcut for pvr2_hdw_subsys_bit_chg(hdw,msk,msk) */ +void pvr2_hdw_subsys_bit_set(struct pvr2_hdw *hdw,unsigned long msk); + +/* Shortcut for pvr2_hdw_subsys_bit_chg(hdw,msk,0) */ +void pvr2_hdw_subsys_bit_clr(struct pvr2_hdw *hdw,unsigned long msk); + +/* Retrieve mask indicating which pieces of hardware are currently enabled + / configured. */ +unsigned long pvr2_hdw_subsys_get(struct pvr2_hdw *); + +/* Adjust mask of what get shut down when streaming is stopped. This is a + debugging aid. */ +void pvr2_hdw_subsys_stream_bit_chg(struct pvr2_hdw *hdw, + unsigned long msk,unsigned long val); + +/* Retrieve mask indicating which pieces of hardware are disabled when + streaming is turned off. */ +unsigned long pvr2_hdw_subsys_stream_get(struct pvr2_hdw *); + + +/* Enable / disable retrieval of CPU firmware. This must be enabled before + pvr2_hdw_cpufw_get() will function. Note that doing this may prevent + the device from running (and leaving this mode may imply a device + reset). */ +void pvr2_hdw_cpufw_set_enabled(struct pvr2_hdw *, int enable_flag); + +/* Return true if we're in a mode for retrieval CPU firmware */ +int pvr2_hdw_cpufw_get_enabled(struct pvr2_hdw *); + +/* Retrieve a piece of the CPU's firmware at the given offset. Return + value is the number of bytes retrieved or zero if we're past the end or + an error otherwise (e.g. if firmware retrieval is not enabled). */ +int pvr2_hdw_cpufw_get(struct pvr2_hdw *,unsigned int offs, + char *buf,unsigned int cnt); + +/* Retrieve previously stored v4l minor device number */ +int pvr2_hdw_v4l_get_minor_number(struct pvr2_hdw *); + +/* Store the v4l minor device number */ +void pvr2_hdw_v4l_store_minor_number(struct pvr2_hdw *,int); + + +/* The following entry points are all lower level things you normally don't + want to worry about. */ + +/* Attempt to recover from a USB foul-up (in practice I find that if you + have to do this, then it's already too late). */ +void pvr2_reset_ctl_endpoints(struct pvr2_hdw *hdw); + +/* Issue a command and get a response from the device. LOTS of higher + level stuff is built on this. */ +int pvr2_send_request(struct pvr2_hdw *, + void *write_ptr,unsigned int write_len, + void *read_ptr,unsigned int read_len); + +/* Issue a command and get a response from the device. This extended + version includes a probe flag (which if set means that device errors + should not be logged or treated as fatal) and a timeout in jiffies. + This can be used to non-lethally probe the health of endpoint 1. */ +int pvr2_send_request_ex(struct pvr2_hdw *,unsigned int timeout,int probe_fl, + void *write_ptr,unsigned int write_len, + void *read_ptr,unsigned int read_len); + +/* Slightly higher level device communication functions. */ +int pvr2_write_register(struct pvr2_hdw *, u16, u32); +int pvr2_read_register(struct pvr2_hdw *, u16, u32 *); +int pvr2_write_u16(struct pvr2_hdw *, u16, int); +int pvr2_write_u8(struct pvr2_hdw *, u8, int); + +/* Call if for any reason we can't talk to the hardware anymore - this will + cause the driver to stop flailing on the device. */ +void pvr2_hdw_render_useless(struct pvr2_hdw *); +void pvr2_hdw_render_useless_unlocked(struct pvr2_hdw *); + +/* Set / clear 8051's reset bit */ +void pvr2_hdw_cpureset_assert(struct pvr2_hdw *,int); + +/* Execute a USB-commanded device reset */ +void pvr2_hdw_device_reset(struct pvr2_hdw *); + +/* Execute hard reset command (after this point it's likely that the + encoder will have to be reconfigured). This also clears the "useless" + state. */ +int pvr2_hdw_cmd_deep_reset(struct pvr2_hdw *); + +/* Execute simple reset command */ +int pvr2_hdw_cmd_powerup(struct pvr2_hdw *); + +/* Order decoder to reset */ +int pvr2_hdw_cmd_decoder_reset(struct pvr2_hdw *); + +/* Stop / start video stream transport */ +int pvr2_hdw_cmd_usbstream(struct pvr2_hdw *hdw,int runFl); + +/* Find I2C address of eeprom */ +int pvr2_hdw_get_eeprom_addr(struct pvr2_hdw *); + +/* Direct manipulation of GPIO bits */ +int pvr2_hdw_gpio_get_dir(struct pvr2_hdw *hdw,u32 *); +int pvr2_hdw_gpio_get_out(struct pvr2_hdw *hdw,u32 *); +int pvr2_hdw_gpio_get_in(struct pvr2_hdw *hdw,u32 *); +int pvr2_hdw_gpio_chg_dir(struct pvr2_hdw *hdw,u32 msk,u32 val); +int pvr2_hdw_gpio_chg_out(struct pvr2_hdw *hdw,u32 msk,u32 val); + +/* This data structure is specifically for the next function... */ +struct pvr2_hdw_debug_info { + int big_lock_held; + int ctl_lock_held; + int flag_ok; + int flag_disconnected; + int flag_init_ok; + int flag_streaming_enabled; + unsigned long subsys_flags; + int cmd_debug_state; + int cmd_debug_write_len; + int cmd_debug_read_len; + int cmd_debug_write_pend; + int cmd_debug_read_pend; + int cmd_debug_timeout; + int cmd_debug_rstatus; + int cmd_debug_wstatus; + unsigned char cmd_code; +}; + +/* Non-intrusively retrieve internal state info - this is useful for + diagnosing lockups. Note that this operation is completed without any + kind of locking and so it is not atomic and may yield inconsistent + results. This is *purely* a debugging aid. */ +void pvr2_hdw_get_debug_info(const struct pvr2_hdw *hdw, + struct pvr2_hdw_debug_info *); + +/* Cause modules to log their state once */ +void pvr2_hdw_trigger_module_log(struct pvr2_hdw *hdw); + +/* Cause encoder firmware to be uploaded into the device. This is normally + done autonomously, but the interface is exported here because it is also + a debugging aid. */ +int pvr2_upload_firmware2(struct pvr2_hdw *hdw); + +/* List of device types that we can match */ +extern struct usb_device_id pvr2_device_table[]; + +#endif /* __PVRUSB2_HDW_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c new file mode 100644 index 00000000000..1dd4f6249b9 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-i2c-chips-v4l2.c @@ -0,0 +1,115 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-i2c-core.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-i2c-cmd-v4l2.h" +#include "pvrusb2-audio.h" +#include "pvrusb2-tuner.h" +#include "pvrusb2-demod.h" +#include "pvrusb2-video-v4l.h" +#ifdef CONFIG_VIDEO_PVRUSB2_24XXX +#include "pvrusb2-cx2584x-v4l.h" +#include "pvrusb2-wm8775.h" +#endif + +#define trace_i2c(...) pvr2_trace(PVR2_TRACE_I2C,__VA_ARGS__) + +#define OP_STANDARD 0 +#define OP_BCSH 1 +#define OP_VOLUME 2 +#define OP_FREQ 3 +#define OP_AUDIORATE 4 +#define OP_SIZE 5 +#define OP_LOG 6 + +static const struct pvr2_i2c_op * const ops[] = { + [OP_STANDARD] = &pvr2_i2c_op_v4l2_standard, + [OP_BCSH] = &pvr2_i2c_op_v4l2_bcsh, + [OP_VOLUME] = &pvr2_i2c_op_v4l2_volume, + [OP_FREQ] = &pvr2_i2c_op_v4l2_frequency, + [OP_SIZE] = &pvr2_i2c_op_v4l2_size, + [OP_LOG] = &pvr2_i2c_op_v4l2_log, +}; + +void pvr2_i2c_probe(struct pvr2_hdw *hdw,struct pvr2_i2c_client *cp) +{ + int id; + id = cp->client->driver->id; + cp->ctl_mask = ((1 << OP_STANDARD) | + (1 << OP_BCSH) | + (1 << OP_VOLUME) | + (1 << OP_FREQ) | + (1 << OP_SIZE) | + (1 << OP_LOG)); + + if (id == I2C_DRIVERID_MSP3400) { + if (pvr2_i2c_msp3400_setup(hdw,cp)) { + return; + } + } + if (id == I2C_DRIVERID_TUNER) { + if (pvr2_i2c_tuner_setup(hdw,cp)) { + return; + } + } +#ifdef CONFIG_VIDEO_PVRUSB2_24XXX + if (id == I2C_DRIVERID_CX25840) { + if (pvr2_i2c_cx2584x_v4l_setup(hdw,cp)) { + return; + } + } + if (id == I2C_DRIVERID_WM8775) { + if (pvr2_i2c_wm8775_setup(hdw,cp)) { + return; + } + } +#endif + if (id == I2C_DRIVERID_SAA711X) { + if (pvr2_i2c_decoder_v4l_setup(hdw,cp)) { + return; + } + } + if (id == I2C_DRIVERID_TDA9887) { + if (pvr2_i2c_demod_setup(hdw,cp)) { + return; + } + } +} + + +const struct pvr2_i2c_op *pvr2_i2c_get_op(unsigned int idx) +{ + if (idx >= sizeof(ops)/sizeof(ops[0])) return 0; + return ops[idx]; +} + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c new file mode 100644 index 00000000000..9f81aff2b38 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.c @@ -0,0 +1,232 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-i2c-cmd-v4l2.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <linux/videodev2.h> + + +static void set_standard(struct pvr2_hdw *hdw) +{ + v4l2_std_id vs; + vs = hdw->std_mask_cur; + pvr2_trace(PVR2_TRACE_CHIPS, + "i2c v4l2 set_standard(0x%llx)",(__u64)vs); + + pvr2_i2c_core_cmd(hdw,VIDIOC_S_STD,&vs); +} + + +static int check_standard(struct pvr2_hdw *hdw) +{ + return hdw->std_dirty != 0; +} + + +const struct pvr2_i2c_op pvr2_i2c_op_v4l2_standard = { + .check = check_standard, + .update = set_standard, + .name = "v4l2_standard", +}; + + +static void set_bcsh(struct pvr2_hdw *hdw) +{ + struct v4l2_control ctrl; + pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 set_bcsh" + " b=%d c=%d s=%d h=%d", + hdw->brightness_val,hdw->contrast_val, + hdw->saturation_val,hdw->hue_val); + memset(&ctrl,0,sizeof(ctrl)); + ctrl.id = V4L2_CID_BRIGHTNESS; + ctrl.value = hdw->brightness_val; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_CONTRAST; + ctrl.value = hdw->contrast_val; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_SATURATION; + ctrl.value = hdw->saturation_val; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_HUE; + ctrl.value = hdw->hue_val; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); +} + + +static int check_bcsh(struct pvr2_hdw *hdw) +{ + return (hdw->brightness_dirty || + hdw->contrast_dirty || + hdw->saturation_dirty || + hdw->hue_dirty); +} + + +const struct pvr2_i2c_op pvr2_i2c_op_v4l2_bcsh = { + .check = check_bcsh, + .update = set_bcsh, + .name = "v4l2_bcsh", +}; + + +static void set_volume(struct pvr2_hdw *hdw) +{ + struct v4l2_control ctrl; + pvr2_trace(PVR2_TRACE_CHIPS, + "i2c v4l2 set_volume" + "(vol=%d bal=%d bas=%d treb=%d mute=%d)", + hdw->volume_val, + hdw->balance_val, + hdw->bass_val, + hdw->treble_val, + hdw->mute_val); + memset(&ctrl,0,sizeof(ctrl)); + ctrl.id = V4L2_CID_AUDIO_MUTE; + ctrl.value = hdw->mute_val ? 1 : 0; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_AUDIO_VOLUME; + ctrl.value = hdw->volume_val; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_AUDIO_BALANCE; + ctrl.value = hdw->balance_val; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_AUDIO_BASS; + ctrl.value = hdw->bass_val; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); + ctrl.id = V4L2_CID_AUDIO_TREBLE; + ctrl.value = hdw->treble_val; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_CTRL,&ctrl); +} + + +static int check_volume(struct pvr2_hdw *hdw) +{ + return (hdw->volume_dirty || + hdw->balance_dirty || + hdw->bass_dirty || + hdw->treble_dirty || + hdw->mute_dirty); +} + + +const struct pvr2_i2c_op pvr2_i2c_op_v4l2_volume = { + .check = check_volume, + .update = set_volume, + .name = "v4l2_volume", +}; + + +static void set_frequency(struct pvr2_hdw *hdw) +{ + unsigned long fv; + struct v4l2_frequency freq; + fv = hdw->freqVal; + pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 set_freq(%lu)",fv); + memset(&freq,0,sizeof(freq)); + freq.frequency = fv / 62500; + freq.tuner = 0; + freq.type = V4L2_TUNER_ANALOG_TV; + pvr2_i2c_core_cmd(hdw,VIDIOC_S_FREQUENCY,&freq); +} + + +static int check_frequency(struct pvr2_hdw *hdw) +{ + return hdw->freqDirty != 0; +} + + +const struct pvr2_i2c_op pvr2_i2c_op_v4l2_frequency = { + .check = check_frequency, + .update = set_frequency, + .name = "v4l2_freq", +}; + + +static void set_size(struct pvr2_hdw *hdw) +{ + struct v4l2_format fmt; + + memset(&fmt,0,sizeof(fmt)); + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fmt.fmt.pix.width = hdw->res_hor_val; + fmt.fmt.pix.height = hdw->res_ver_val; + + pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 set_size(%dx%d)", + fmt.fmt.pix.width,fmt.fmt.pix.height); + + pvr2_i2c_core_cmd(hdw,VIDIOC_S_FMT,&fmt); +} + + +static int check_size(struct pvr2_hdw *hdw) +{ + return (hdw->res_hor_dirty || hdw->res_ver_dirty); +} + + +const struct pvr2_i2c_op pvr2_i2c_op_v4l2_size = { + .check = check_size, + .update = set_size, + .name = "v4l2_size", +}; + + +static void do_log(struct pvr2_hdw *hdw) +{ + pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 do_log()"); + pvr2_i2c_core_cmd(hdw,VIDIOC_LOG_STATUS,0); + +} + + +static int check_log(struct pvr2_hdw *hdw) +{ + return hdw->log_requested != 0; +} + + +const struct pvr2_i2c_op pvr2_i2c_op_v4l2_log = { + .check = check_log, + .update = do_log, + .name = "v4l2_log", +}; + + +void pvr2_v4l2_cmd_stream(struct pvr2_i2c_client *cp,int fl) +{ + pvr2_i2c_client_cmd(cp, + (fl ? VIDIOC_STREAMON : VIDIOC_STREAMOFF),0); +} + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h b/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h new file mode 100644 index 00000000000..ecabddba1ec --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-i2c-cmd-v4l2.h @@ -0,0 +1,47 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_CMD_V4L2_H +#define __PVRUSB2_CMD_V4L2_H + +#include "pvrusb2-i2c-core.h" + +extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_standard; +extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_bcsh; +extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_volume; +extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_frequency; +extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_size; +extern const struct pvr2_i2c_op pvr2_i2c_op_v4l2_log; + +void pvr2_v4l2_cmd_stream(struct pvr2_i2c_client *,int); + +#endif /* __PVRUSB2_CMD_V4L2_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c b/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c new file mode 100644 index 00000000000..c8d0bdee3ff --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-i2c-core.c @@ -0,0 +1,937 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-i2c-core.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" + +#define trace_i2c(...) pvr2_trace(PVR2_TRACE_I2C,__VA_ARGS__) + +/* + + This module attempts to implement a compliant I2C adapter for the pvrusb2 + device. By doing this we can then make use of existing functionality in + V4L (e.g. tuner.c) rather than rolling our own. + +*/ + +static unsigned int i2c_scan = 0; +module_param(i2c_scan, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time"); + +static int pvr2_i2c_write(struct pvr2_hdw *hdw, /* Context */ + u8 i2c_addr, /* I2C address we're talking to */ + u8 *data, /* Data to write */ + u16 length) /* Size of data to write */ +{ + /* Return value - default 0 means success */ + int ret; + + + if (!data) length = 0; + if (length > (sizeof(hdw->cmd_buffer) - 3)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Killing an I2C write to %u that is too large" + " (desired=%u limit=%u)", + i2c_addr, + length,(unsigned int)(sizeof(hdw->cmd_buffer) - 3)); + return -ENOTSUPP; + } + + LOCK_TAKE(hdw->ctl_lock); + + /* Clear the command buffer (likely to be paranoia) */ + memset(hdw->cmd_buffer, 0, sizeof(hdw->cmd_buffer)); + + /* Set up command buffer for an I2C write */ + hdw->cmd_buffer[0] = 0x08; /* write prefix */ + hdw->cmd_buffer[1] = i2c_addr; /* i2c addr of chip */ + hdw->cmd_buffer[2] = length; /* length of what follows */ + if (length) memcpy(hdw->cmd_buffer + 3, data, length); + + /* Do the operation */ + ret = pvr2_send_request(hdw, + hdw->cmd_buffer, + length + 3, + hdw->cmd_buffer, + 1); + if (!ret) { + if (hdw->cmd_buffer[0] != 8) { + ret = -EIO; + if (hdw->cmd_buffer[0] != 7) { + trace_i2c("unexpected status" + " from i2_write[%d]: %d", + i2c_addr,hdw->cmd_buffer[0]); + } + } + } + + LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + +static int pvr2_i2c_read(struct pvr2_hdw *hdw, /* Context */ + u8 i2c_addr, /* I2C address we're talking to */ + u8 *data, /* Data to write */ + u16 dlen, /* Size of data to write */ + u8 *res, /* Where to put data we read */ + u16 rlen) /* Amount of data to read */ +{ + /* Return value - default 0 means success */ + int ret; + + + if (!data) dlen = 0; + if (dlen > (sizeof(hdw->cmd_buffer) - 4)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Killing an I2C read to %u that has wlen too large" + " (desired=%u limit=%u)", + i2c_addr, + dlen,(unsigned int)(sizeof(hdw->cmd_buffer) - 4)); + return -ENOTSUPP; + } + if (res && (rlen > (sizeof(hdw->cmd_buffer) - 1))) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Killing an I2C read to %u that has rlen too large" + " (desired=%u limit=%u)", + i2c_addr, + rlen,(unsigned int)(sizeof(hdw->cmd_buffer) - 1)); + return -ENOTSUPP; + } + + LOCK_TAKE(hdw->ctl_lock); + + /* Clear the command buffer (likely to be paranoia) */ + memset(hdw->cmd_buffer, 0, sizeof(hdw->cmd_buffer)); + + /* Set up command buffer for an I2C write followed by a read */ + hdw->cmd_buffer[0] = 0x09; /* read prefix */ + hdw->cmd_buffer[1] = dlen; /* arg length */ + hdw->cmd_buffer[2] = rlen; /* answer length. Device will send one + more byte (status). */ + hdw->cmd_buffer[3] = i2c_addr; /* i2c addr of chip */ + if (dlen) memcpy(hdw->cmd_buffer + 4, data, dlen); + + /* Do the operation */ + ret = pvr2_send_request(hdw, + hdw->cmd_buffer, + 4 + dlen, + hdw->cmd_buffer, + rlen + 1); + if (!ret) { + if (hdw->cmd_buffer[0] != 8) { + ret = -EIO; + if (hdw->cmd_buffer[0] != 7) { + trace_i2c("unexpected status" + " from i2_read[%d]: %d", + i2c_addr,hdw->cmd_buffer[0]); + } + } + } + + /* Copy back the result */ + if (res && rlen) { + if (ret) { + /* Error, just blank out the return buffer */ + memset(res, 0, rlen); + } else { + memcpy(res, hdw->cmd_buffer + 1, rlen); + } + } + + LOCK_GIVE(hdw->ctl_lock); + + return ret; +} + +/* This is the common low level entry point for doing I2C operations to the + hardware. */ +int pvr2_i2c_basic_op(struct pvr2_hdw *hdw, + u8 i2c_addr, + u8 *wdata, + u16 wlen, + u8 *rdata, + u16 rlen) +{ + if (!rdata) rlen = 0; + if (!wdata) wlen = 0; + if (rlen || !wlen) { + return pvr2_i2c_read(hdw,i2c_addr,wdata,wlen,rdata,rlen); + } else { + return pvr2_i2c_write(hdw,i2c_addr,wdata,wlen); + } +} + +#ifdef CONFIG_VIDEO_PVRUSB2_24XXX + +/* This is a special entry point that is entered if an I2C operation is + attempted to a wm8775 chip on model 24xxx hardware. Autodetect of this + part doesn't work, but we know it is really there. So let's look for + the autodetect attempt and just return success if we see that. */ +static int i2c_hack_wm8775(struct pvr2_hdw *hdw, + u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen) +{ + if (!(rlen || wlen)) { + // This is a probe attempt. Just let it succeed. + return 0; + } + return pvr2_i2c_basic_op(hdw,i2c_addr,wdata,wlen,rdata,rlen); +} + +/* This is a special entry point that is entered if an I2C operation is + attempted to a cx25840 chip on model 24xxx hardware. This chip can + sometimes wedge itself. Worse still, when this happens msp3400 can + falsely detect this part and then the system gets hosed up after msp3400 + gets confused and dies. What we want to do here is try to keep msp3400 + away and also try to notice if the chip is wedged and send a warning to + the system log. */ +static int i2c_hack_cx25840(struct pvr2_hdw *hdw, + u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen) +{ + int ret; + unsigned int subaddr; + u8 wbuf[2]; + int state = hdw->i2c_cx25840_hack_state; + + if (!(rlen || wlen)) { + // Probe attempt - always just succeed and don't bother the + // hardware (this helps to make the state machine further + // down somewhat easier). + return 0; + } + + if (state == 3) { + return pvr2_i2c_basic_op(hdw,i2c_addr,wdata,wlen,rdata,rlen); + } + + /* We're looking for the exact pattern where the revision register + is being read. The cx25840 module will always look at the + revision register first. Any other pattern of access therefore + has to be a probe attempt from somebody else so we'll reject it. + Normally we could just let each client just probe the part + anyway, but when the cx25840 is wedged, msp3400 will get a false + positive and that just screws things up... */ + + if (wlen == 0) { + switch (state) { + case 1: subaddr = 0x0100; break; + case 2: subaddr = 0x0101; break; + default: goto fail; + } + } else if (wlen == 2) { + subaddr = (wdata[0] << 8) | wdata[1]; + switch (subaddr) { + case 0x0100: state = 1; break; + case 0x0101: state = 2; break; + default: goto fail; + } + } else { + goto fail; + } + if (!rlen) goto success; + state = 0; + if (rlen != 1) goto fail; + + /* If we get to here then we have a legitimate read for one of the + two revision bytes, so pass it through. */ + wbuf[0] = subaddr >> 8; + wbuf[1] = subaddr; + ret = pvr2_i2c_basic_op(hdw,i2c_addr,wbuf,2,rdata,rlen); + + if ((ret != 0) || (*rdata == 0x04) || (*rdata == 0x0a)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "WARNING: Detected a wedged cx25840 chip;" + " the device will not work."); + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "WARNING: Try power cycling the pvrusb2 device."); + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "WARNING: Disabling further access to the device" + " to prevent other foul-ups."); + // This blocks all further communication with the part. + hdw->i2c_func[0x44] = 0; + pvr2_hdw_render_useless(hdw); + goto fail; + } + + /* Success! */ + pvr2_trace(PVR2_TRACE_CHIPS,"cx25840 appears to be OK."); + state = 3; + + success: + hdw->i2c_cx25840_hack_state = state; + return 0; + + fail: + hdw->i2c_cx25840_hack_state = state; + return -EIO; +} + +#endif /* CONFIG_VIDEO_PVRUSB2_24XXX */ + +/* This is a very, very limited I2C adapter implementation. We can only + support what we actually know will work on the device... */ +static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msgs[], + int num) +{ + int ret = -ENOTSUPP; + pvr2_i2c_func funcp = 0; + struct pvr2_hdw *hdw = (struct pvr2_hdw *)(i2c_adap->algo_data); + + if (!num) { + ret = -EINVAL; + goto done; + } + if ((msgs[0].flags & I2C_M_NOSTART)) { + trace_i2c("i2c refusing I2C_M_NOSTART"); + goto done; + } + if (msgs[0].addr < PVR2_I2C_FUNC_CNT) { + funcp = hdw->i2c_func[msgs[0].addr]; + } + if (!funcp) { + ret = -EIO; + goto done; + } + + if (num == 1) { + if (msgs[0].flags & I2C_M_RD) { + /* Simple read */ + u16 tcnt,bcnt,offs; + if (!msgs[0].len) { + /* Length == 0 read. This is a probe. */ + if (funcp(hdw,msgs[0].addr,0,0,0,0)) { + ret = -EIO; + goto done; + } + ret = 1; + goto done; + } + /* If the read is short enough we'll do the whole + thing atomically. Otherwise we have no choice + but to break apart the reads. */ + tcnt = msgs[0].len; + offs = 0; + while (tcnt) { + bcnt = tcnt; + if (bcnt > sizeof(hdw->cmd_buffer)-1) { + bcnt = sizeof(hdw->cmd_buffer)-1; + } + if (funcp(hdw,msgs[0].addr,0,0, + msgs[0].buf+offs,bcnt)) { + ret = -EIO; + goto done; + } + offs += bcnt; + tcnt -= bcnt; + } + ret = 1; + goto done; + } else { + /* Simple write */ + ret = 1; + if (funcp(hdw,msgs[0].addr, + msgs[0].buf,msgs[0].len,0,0)) { + ret = -EIO; + } + goto done; + } + } else if (num == 2) { + if (msgs[0].addr != msgs[1].addr) { + trace_i2c("i2c refusing 2 phase transfer with" + " conflicting target addresses"); + ret = -ENOTSUPP; + goto done; + } + if ((!((msgs[0].flags & I2C_M_RD))) && + (msgs[1].flags & I2C_M_RD)) { + u16 tcnt,bcnt,wcnt,offs; + /* Write followed by atomic read. If the read + portion is short enough we'll do the whole thing + atomically. Otherwise we have no choice but to + break apart the reads. */ + tcnt = msgs[1].len; + wcnt = msgs[0].len; + offs = 0; + while (tcnt || wcnt) { + bcnt = tcnt; + if (bcnt > sizeof(hdw->cmd_buffer)-1) { + bcnt = sizeof(hdw->cmd_buffer)-1; + } + if (funcp(hdw,msgs[0].addr, + msgs[0].buf,wcnt, + msgs[1].buf+offs,bcnt)) { + ret = -EIO; + goto done; + } + offs += bcnt; + tcnt -= bcnt; + wcnt = 0; + } + ret = 2; + goto done; + } else { + trace_i2c("i2c refusing complex transfer" + " read0=%d read1=%d", + (msgs[0].flags & I2C_M_RD), + (msgs[1].flags & I2C_M_RD)); + } + } else { + trace_i2c("i2c refusing %d phase transfer",num); + } + + done: + if (pvrusb2_debug & PVR2_TRACE_I2C_TRAF) { + unsigned int idx,offs,cnt; + for (idx = 0; idx < num; idx++) { + cnt = msgs[idx].len; + printk(KERN_INFO + "pvrusb2 i2c xfer %u/%u:" + " addr=0x%x len=%d %s%s", + idx+1,num, + msgs[idx].addr, + cnt, + (msgs[idx].flags & I2C_M_RD ? + "read" : "write"), + (msgs[idx].flags & I2C_M_NOSTART ? + " nostart" : "")); + if ((ret > 0) || !(msgs[idx].flags & I2C_M_RD)) { + if (cnt > 8) cnt = 8; + printk(" ["); + for (offs = 0; offs < (cnt>8?8:cnt); offs++) { + if (offs) printk(" "); + printk("%02x",msgs[idx].buf[offs]); + } + if (offs < cnt) printk(" ..."); + printk("]"); + } + if (idx+1 == num) { + printk(" result=%d",ret); + } + printk("\n"); + } + if (!num) { + printk(KERN_INFO + "pvrusb2 i2c xfer null transfer result=%d\n", + ret); + } + } + return ret; +} + +static int pvr2_i2c_control(struct i2c_adapter *adapter, + unsigned int cmd, unsigned long arg) +{ + return 0; +} + +static u32 pvr2_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA; +} + +static int pvr2_i2c_core_singleton(struct i2c_client *cp, + unsigned int cmd,void *arg) +{ + int stat; + if (!cp) return -EINVAL; + if (!(cp->driver)) return -EINVAL; + if (!(cp->driver->command)) return -EINVAL; + if (!try_module_get(cp->driver->driver.owner)) return -EAGAIN; + stat = cp->driver->command(cp,cmd,arg); + module_put(cp->driver->driver.owner); + return stat; +} + +int pvr2_i2c_client_cmd(struct pvr2_i2c_client *cp,unsigned int cmd,void *arg) +{ + int stat; + if (pvrusb2_debug & PVR2_TRACE_I2C_CMD) { + char buf[100]; + unsigned int cnt; + cnt = pvr2_i2c_client_describe(cp,PVR2_I2C_DETAIL_DEBUG, + buf,sizeof(buf)); + pvr2_trace(PVR2_TRACE_I2C_CMD, + "i2c COMMAND (code=%u 0x%x) to %.*s", + cmd,cmd,cnt,buf); + } + stat = pvr2_i2c_core_singleton(cp->client,cmd,arg); + if (pvrusb2_debug & PVR2_TRACE_I2C_CMD) { + char buf[100]; + unsigned int cnt; + cnt = pvr2_i2c_client_describe(cp,PVR2_I2C_DETAIL_DEBUG, + buf,sizeof(buf)); + pvr2_trace(PVR2_TRACE_I2C_CMD, + "i2c COMMAND to %.*s (ret=%d)",cnt,buf,stat); + } + return stat; +} + +int pvr2_i2c_core_cmd(struct pvr2_hdw *hdw,unsigned int cmd,void *arg) +{ + struct list_head *item,*nc; + struct pvr2_i2c_client *cp; + int stat = -EINVAL; + + if (!hdw) return stat; + + mutex_lock(&hdw->i2c_list_lock); + list_for_each_safe(item,nc,&hdw->i2c_clients) { + cp = list_entry(item,struct pvr2_i2c_client,list); + if (!cp->recv_enable) continue; + mutex_unlock(&hdw->i2c_list_lock); + stat = pvr2_i2c_client_cmd(cp,cmd,arg); + mutex_lock(&hdw->i2c_list_lock); + } + mutex_unlock(&hdw->i2c_list_lock); + return stat; +} + + +static int handler_check(struct pvr2_i2c_client *cp) +{ + struct pvr2_i2c_handler *hp = cp->handler; + if (!hp) return 0; + if (!hp->func_table->check) return 0; + return hp->func_table->check(hp->func_data) != 0; +} + +#define BUFSIZE 500 + +void pvr2_i2c_core_sync(struct pvr2_hdw *hdw) +{ + unsigned long msk; + unsigned int idx; + struct list_head *item,*nc; + struct pvr2_i2c_client *cp; + + if (!hdw->i2c_linked) return; + if (!(hdw->i2c_pend_types & PVR2_I2C_PEND_ALL)) { + return; + } + mutex_lock(&hdw->i2c_list_lock); do { + pvr2_trace(PVR2_TRACE_I2C_CORE,"i2c: core_sync BEGIN"); + if (hdw->i2c_pend_types & PVR2_I2C_PEND_DETECT) { + /* One or more I2C clients have attached since we + last synced. So scan the list and identify the + new clients. */ + char *buf; + unsigned int cnt; + unsigned long amask = 0; + buf = kmalloc(BUFSIZE,GFP_KERNEL); + pvr2_trace(PVR2_TRACE_I2C_CORE,"i2c: PEND_DETECT"); + hdw->i2c_pend_types &= ~PVR2_I2C_PEND_DETECT; + list_for_each(item,&hdw->i2c_clients) { + cp = list_entry(item,struct pvr2_i2c_client, + list); + if (!cp->detected_flag) { + cp->ctl_mask = 0; + pvr2_i2c_probe(hdw,cp); + cp->detected_flag = !0; + msk = cp->ctl_mask; + cnt = 0; + if (buf) { + cnt = pvr2_i2c_client_describe( + cp, + PVR2_I2C_DETAIL_ALL, + buf,BUFSIZE); + } + trace_i2c("Probed: %.*s",cnt,buf); + if (handler_check(cp)) { + hdw->i2c_pend_types |= + PVR2_I2C_PEND_CLIENT; + } + cp->pend_mask = msk; + hdw->i2c_pend_mask |= msk; + hdw->i2c_pend_types |= + PVR2_I2C_PEND_REFRESH; + } + amask |= cp->ctl_mask; + } + hdw->i2c_active_mask = amask; + if (buf) kfree(buf); + } + if (hdw->i2c_pend_types & PVR2_I2C_PEND_STALE) { + /* Need to do one or more global updates. Arrange + for this to happen. */ + unsigned long m2; + pvr2_trace(PVR2_TRACE_I2C_CORE, + "i2c: PEND_STALE (0x%lx)", + hdw->i2c_stale_mask); + hdw->i2c_pend_types &= ~PVR2_I2C_PEND_STALE; + list_for_each(item,&hdw->i2c_clients) { + cp = list_entry(item,struct pvr2_i2c_client, + list); + m2 = hdw->i2c_stale_mask; + m2 &= cp->ctl_mask; + m2 &= ~cp->pend_mask; + if (m2) { + pvr2_trace(PVR2_TRACE_I2C_CORE, + "i2c: cp=%p setting 0x%lx", + cp,m2); + cp->pend_mask |= m2; + } + } + hdw->i2c_pend_mask |= hdw->i2c_stale_mask; + hdw->i2c_stale_mask = 0; + hdw->i2c_pend_types |= PVR2_I2C_PEND_REFRESH; + } + if (hdw->i2c_pend_types & PVR2_I2C_PEND_CLIENT) { + /* One or more client handlers are asking for an + update. Run through the list of known clients + and update each one. */ + pvr2_trace(PVR2_TRACE_I2C_CORE,"i2c: PEND_CLIENT"); + hdw->i2c_pend_types &= ~PVR2_I2C_PEND_CLIENT; + list_for_each_safe(item,nc,&hdw->i2c_clients) { + cp = list_entry(item,struct pvr2_i2c_client, + list); + if (!cp->handler) continue; + if (!cp->handler->func_table->update) continue; + pvr2_trace(PVR2_TRACE_I2C_CORE, + "i2c: cp=%p update",cp); + mutex_unlock(&hdw->i2c_list_lock); + cp->handler->func_table->update( + cp->handler->func_data); + mutex_lock(&hdw->i2c_list_lock); + /* If client's update function set some + additional pending bits, account for that + here. */ + if (cp->pend_mask & ~hdw->i2c_pend_mask) { + hdw->i2c_pend_mask |= cp->pend_mask; + hdw->i2c_pend_types |= + PVR2_I2C_PEND_REFRESH; + } + } + } + if (hdw->i2c_pend_types & PVR2_I2C_PEND_REFRESH) { + const struct pvr2_i2c_op *opf; + unsigned long pm; + /* Some actual updates are pending. Walk through + each update type and perform it. */ + pvr2_trace(PVR2_TRACE_I2C_CORE,"i2c: PEND_REFRESH" + " (0x%lx)",hdw->i2c_pend_mask); + hdw->i2c_pend_types &= ~PVR2_I2C_PEND_REFRESH; + pm = hdw->i2c_pend_mask; + hdw->i2c_pend_mask = 0; + for (idx = 0, msk = 1; pm; idx++, msk <<= 1) { + if (!(pm & msk)) continue; + pm &= ~msk; + list_for_each(item,&hdw->i2c_clients) { + cp = list_entry(item, + struct pvr2_i2c_client, + list); + if (cp->pend_mask & msk) { + cp->pend_mask &= ~msk; + cp->recv_enable = !0; + } else { + cp->recv_enable = 0; + } + } + opf = pvr2_i2c_get_op(idx); + if (!opf) continue; + mutex_unlock(&hdw->i2c_list_lock); + opf->update(hdw); + mutex_lock(&hdw->i2c_list_lock); + } + } + pvr2_trace(PVR2_TRACE_I2C_CORE,"i2c: core_sync END"); + } while (0); mutex_unlock(&hdw->i2c_list_lock); +} + +int pvr2_i2c_core_check_stale(struct pvr2_hdw *hdw) +{ + unsigned long msk,sm,pm; + unsigned int idx; + const struct pvr2_i2c_op *opf; + struct list_head *item; + struct pvr2_i2c_client *cp; + unsigned int pt = 0; + + pvr2_trace(PVR2_TRACE_I2C_CORE,"pvr2_i2c_core_check_stale BEGIN"); + + pm = hdw->i2c_active_mask; + sm = 0; + for (idx = 0, msk = 1; pm; idx++, msk <<= 1) { + if (!(msk & pm)) continue; + pm &= ~msk; + opf = pvr2_i2c_get_op(idx); + if (!opf) continue; + if (opf->check(hdw)) { + sm |= msk; + } + } + if (sm) pt |= PVR2_I2C_PEND_STALE; + + list_for_each(item,&hdw->i2c_clients) { + cp = list_entry(item,struct pvr2_i2c_client,list); + if (!handler_check(cp)) continue; + pt |= PVR2_I2C_PEND_CLIENT; + } + + if (pt) { + mutex_lock(&hdw->i2c_list_lock); do { + hdw->i2c_pend_types |= pt; + hdw->i2c_stale_mask |= sm; + hdw->i2c_pend_mask |= hdw->i2c_stale_mask; + } while (0); mutex_unlock(&hdw->i2c_list_lock); + } + + pvr2_trace(PVR2_TRACE_I2C_CORE, + "i2c: types=0x%x stale=0x%lx pend=0x%lx", + hdw->i2c_pend_types, + hdw->i2c_stale_mask, + hdw->i2c_pend_mask); + pvr2_trace(PVR2_TRACE_I2C_CORE,"pvr2_i2c_core_check_stale END"); + + return (hdw->i2c_pend_types & PVR2_I2C_PEND_ALL) != 0; +} + +unsigned int pvr2_i2c_client_describe(struct pvr2_i2c_client *cp, + unsigned int detail, + char *buf,unsigned int maxlen) +{ + unsigned int ccnt,bcnt; + int spcfl = 0; + const struct pvr2_i2c_op *opf; + + ccnt = 0; + if (detail & PVR2_I2C_DETAIL_DEBUG) { + bcnt = scnprintf(buf,maxlen, + "ctxt=%p ctl_mask=0x%lx", + cp,cp->ctl_mask); + ccnt += bcnt; buf += bcnt; maxlen -= bcnt; + spcfl = !0; + } + bcnt = scnprintf(buf,maxlen, + "%s%s @ 0x%x", + (spcfl ? " " : ""), + cp->client->name, + cp->client->addr); + ccnt += bcnt; buf += bcnt; maxlen -= bcnt; + if ((detail & PVR2_I2C_DETAIL_HANDLER) && + cp->handler && cp->handler->func_table->describe) { + bcnt = scnprintf(buf,maxlen," ("); + ccnt += bcnt; buf += bcnt; maxlen -= bcnt; + bcnt = cp->handler->func_table->describe( + cp->handler->func_data,buf,maxlen); + ccnt += bcnt; buf += bcnt; maxlen -= bcnt; + bcnt = scnprintf(buf,maxlen,")"); + ccnt += bcnt; buf += bcnt; maxlen -= bcnt; + } + if ((detail & PVR2_I2C_DETAIL_CTLMASK) && cp->ctl_mask) { + unsigned int idx; + unsigned long msk,sm; + int spcfl; + bcnt = scnprintf(buf,maxlen," ["); + ccnt += bcnt; buf += bcnt; maxlen -= bcnt; + sm = 0; + spcfl = 0; + for (idx = 0, msk = 1; msk; idx++, msk <<= 1) { + if (!(cp->ctl_mask & msk)) continue; + opf = pvr2_i2c_get_op(idx); + if (opf) { + bcnt = scnprintf(buf,maxlen,"%s%s", + spcfl ? " " : "", + opf->name); + ccnt += bcnt; buf += bcnt; maxlen -= bcnt; + spcfl = !0; + } else { + sm |= msk; + } + } + if (sm) { + bcnt = scnprintf(buf,maxlen,"%s%lx", + idx != 0 ? " " : "",sm); + ccnt += bcnt; buf += bcnt; maxlen -= bcnt; + } + bcnt = scnprintf(buf,maxlen,"]"); + ccnt += bcnt; buf += bcnt; maxlen -= bcnt; + } + return ccnt; +} + +unsigned int pvr2_i2c_report(struct pvr2_hdw *hdw, + char *buf,unsigned int maxlen) +{ + unsigned int ccnt,bcnt; + struct list_head *item; + struct pvr2_i2c_client *cp; + ccnt = 0; + mutex_lock(&hdw->i2c_list_lock); do { + list_for_each(item,&hdw->i2c_clients) { + cp = list_entry(item,struct pvr2_i2c_client,list); + bcnt = pvr2_i2c_client_describe( + cp, + (PVR2_I2C_DETAIL_HANDLER| + PVR2_I2C_DETAIL_CTLMASK), + buf,maxlen); + ccnt += bcnt; buf += bcnt; maxlen -= bcnt; + bcnt = scnprintf(buf,maxlen,"\n"); + ccnt += bcnt; buf += bcnt; maxlen -= bcnt; + } + } while (0); mutex_unlock(&hdw->i2c_list_lock); + return ccnt; +} + +static int pvr2_i2c_attach_inform(struct i2c_client *client) +{ + struct pvr2_hdw *hdw = (struct pvr2_hdw *)(client->adapter->algo_data); + struct pvr2_i2c_client *cp; + int fl = !(hdw->i2c_pend_types & PVR2_I2C_PEND_ALL); + cp = kmalloc(sizeof(*cp),GFP_KERNEL); + trace_i2c("i2c_attach [client=%s @ 0x%x ctxt=%p]", + client->name, + client->addr,cp); + if (!cp) return -ENOMEM; + memset(cp,0,sizeof(*cp)); + INIT_LIST_HEAD(&cp->list); + cp->client = client; + mutex_lock(&hdw->i2c_list_lock); do { + list_add_tail(&cp->list,&hdw->i2c_clients); + hdw->i2c_pend_types |= PVR2_I2C_PEND_DETECT; + } while (0); mutex_unlock(&hdw->i2c_list_lock); + if (fl) pvr2_hdw_poll_trigger_unlocked(hdw); + return 0; +} + +static int pvr2_i2c_detach_inform(struct i2c_client *client) +{ + struct pvr2_hdw *hdw = (struct pvr2_hdw *)(client->adapter->algo_data); + struct pvr2_i2c_client *cp; + struct list_head *item,*nc; + unsigned long amask = 0; + int foundfl = 0; + mutex_lock(&hdw->i2c_list_lock); do { + list_for_each_safe(item,nc,&hdw->i2c_clients) { + cp = list_entry(item,struct pvr2_i2c_client,list); + if (cp->client == client) { + trace_i2c("pvr2_i2c_detach" + " [client=%s @ 0x%x ctxt=%p]", + client->name, + client->addr,cp); + if (cp->handler && + cp->handler->func_table->detach) { + cp->handler->func_table->detach( + cp->handler->func_data); + } + list_del(&cp->list); + kfree(cp); + foundfl = !0; + continue; + } + amask |= cp->ctl_mask; + } + hdw->i2c_active_mask = amask; + } while (0); mutex_unlock(&hdw->i2c_list_lock); + if (!foundfl) { + trace_i2c("pvr2_i2c_detach [client=%s @ 0x%x ctxt=<unknown>]", + client->name, + client->addr); + } + return 0; +} + +static struct i2c_algorithm pvr2_i2c_algo_template = { + .master_xfer = pvr2_i2c_xfer, + .algo_control = pvr2_i2c_control, + .functionality = pvr2_i2c_functionality, +}; + +static struct i2c_adapter pvr2_i2c_adap_template = { + .owner = THIS_MODULE, + .class = I2C_CLASS_TV_ANALOG, + .id = I2C_HW_B_BT848, + .client_register = pvr2_i2c_attach_inform, + .client_unregister = pvr2_i2c_detach_inform, +}; + +static void do_i2c_scan(struct pvr2_hdw *hdw) +{ + struct i2c_msg msg[1]; + int i,rc; + msg[0].addr = 0; + msg[0].flags = I2C_M_RD; + msg[0].len = 0; + msg[0].buf = 0; + printk("%s: i2c scan beginning\n",hdw->name); + for (i = 0; i < 128; i++) { + msg[0].addr = i; + rc = i2c_transfer(&hdw->i2c_adap,msg, + sizeof(msg)/sizeof(msg[0])); + if (rc != 1) continue; + printk("%s: i2c scan: found device @ 0x%x\n",hdw->name,i); + } + printk("%s: i2c scan done.\n",hdw->name); +} + +void pvr2_i2c_core_init(struct pvr2_hdw *hdw) +{ + unsigned int idx; + + // The default action for all possible I2C addresses is just to do + // the transfer normally. + for (idx = 0; idx < PVR2_I2C_FUNC_CNT; idx++) { + hdw->i2c_func[idx] = pvr2_i2c_basic_op; + } + +#ifdef CONFIG_VIDEO_PVRUSB2_24XXX + // If however we're dealing with new hardware, insert some hacks in + // the I2C transfer stack to let things work better. + if (hdw->hdw_type == PVR2_HDW_TYPE_24XXX) { + hdw->i2c_func[0x1b] = i2c_hack_wm8775; + hdw->i2c_func[0x44] = i2c_hack_cx25840; + } +#endif + + // Configure the adapter and set up everything else related to it. + memcpy(&hdw->i2c_adap,&pvr2_i2c_adap_template,sizeof(hdw->i2c_adap)); + memcpy(&hdw->i2c_algo,&pvr2_i2c_algo_template,sizeof(hdw->i2c_algo)); + strlcpy(hdw->i2c_adap.name,hdw->name,sizeof(hdw->i2c_adap.name)); + hdw->i2c_adap.algo = &hdw->i2c_algo; + hdw->i2c_adap.algo_data = hdw; + hdw->i2c_pend_mask = 0; + hdw->i2c_stale_mask = 0; + hdw->i2c_active_mask = 0; + INIT_LIST_HEAD(&hdw->i2c_clients); + mutex_init(&hdw->i2c_list_lock); + hdw->i2c_linked = !0; + i2c_add_adapter(&hdw->i2c_adap); + if (i2c_scan) do_i2c_scan(hdw); +} + +void pvr2_i2c_core_done(struct pvr2_hdw *hdw) +{ + if (hdw->i2c_linked) { + i2c_del_adapter(&hdw->i2c_adap); + hdw->i2c_linked = 0; + } +} + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-i2c-core.h b/drivers/media/video/pvrusb2/pvrusb2-i2c-core.h new file mode 100644 index 00000000000..e8af5b0ed3c --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-i2c-core.h @@ -0,0 +1,96 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_I2C_CORE_H +#define __PVRUSB2_I2C_CORE_H + +#include <linux/list.h> +#include <linux/i2c.h> + +struct pvr2_hdw; +struct pvr2_i2c_client; +struct pvr2_i2c_handler; +struct pvr2_i2c_handler_functions; +struct pvr2_i2c_op; +struct pvr2_i2c_op_functions; + +struct pvr2_i2c_client { + struct i2c_client *client; + struct pvr2_i2c_handler *handler; + struct list_head list; + int detected_flag; + int recv_enable; + unsigned long pend_mask; + unsigned long ctl_mask; +}; + +struct pvr2_i2c_handler { + void *func_data; + const struct pvr2_i2c_handler_functions *func_table; +}; + +struct pvr2_i2c_handler_functions { + void (*detach)(void *); + int (*check)(void *); + void (*update)(void *); + unsigned int (*describe)(void *,char *,unsigned int); +}; + +struct pvr2_i2c_op { + int (*check)(struct pvr2_hdw *); + void (*update)(struct pvr2_hdw *); + const char *name; +}; + +void pvr2_i2c_core_init(struct pvr2_hdw *); +void pvr2_i2c_core_done(struct pvr2_hdw *); + +int pvr2_i2c_client_cmd(struct pvr2_i2c_client *,unsigned int cmd,void *arg); +int pvr2_i2c_core_cmd(struct pvr2_hdw *,unsigned int cmd,void *arg); + +int pvr2_i2c_core_check_stale(struct pvr2_hdw *); +void pvr2_i2c_core_sync(struct pvr2_hdw *); +unsigned int pvr2_i2c_report(struct pvr2_hdw *,char *buf,unsigned int maxlen); +#define PVR2_I2C_DETAIL_DEBUG 0x0001 +#define PVR2_I2C_DETAIL_HANDLER 0x0002 +#define PVR2_I2C_DETAIL_CTLMASK 0x0004 +#define PVR2_I2C_DETAIL_ALL (\ + PVR2_I2C_DETAIL_DEBUG |\ + PVR2_I2C_DETAIL_HANDLER |\ + PVR2_I2C_DETAIL_CTLMASK) +unsigned int pvr2_i2c_client_describe(struct pvr2_i2c_client *, + unsigned int detail_mask, + char *buf,unsigned int maxlen); + +void pvr2_i2c_probe(struct pvr2_hdw *,struct pvr2_i2c_client *); +const struct pvr2_i2c_op *pvr2_i2c_get_op(unsigned int idx); + +#endif /* __PVRUSB2_I2C_CORE_H */ + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-io.c b/drivers/media/video/pvrusb2/pvrusb2-io.c new file mode 100644 index 00000000000..a984c91f571 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-io.c @@ -0,0 +1,695 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-io.h" +#include "pvrusb2-debug.h" +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/mutex.h> + +#define BUFFER_SIG 0x47653271 + +// #define SANITY_CHECK_BUFFERS + + +#ifdef SANITY_CHECK_BUFFERS +#define BUFFER_CHECK(bp) do { \ + if ((bp)->signature != BUFFER_SIG) { \ + pvr2_trace(PVR2_TRACE_ERROR_LEGS, \ + "Buffer %p is bad at %s:%d", \ + (bp),__FILE__,__LINE__); \ + pvr2_buffer_describe(bp,"BadSig"); \ + BUG(); \ + } \ +} while (0) +#else +#define BUFFER_CHECK(bp) do {} while(0) +#endif + +struct pvr2_stream { + /* Buffers queued for reading */ + struct list_head queued_list; + unsigned int q_count; + unsigned int q_bcount; + /* Buffers with retrieved data */ + struct list_head ready_list; + unsigned int r_count; + unsigned int r_bcount; + /* Buffers available for use */ + struct list_head idle_list; + unsigned int i_count; + unsigned int i_bcount; + /* Pointers to all buffers */ + struct pvr2_buffer **buffers; + /* Array size of buffers */ + unsigned int buffer_slot_count; + /* Total buffers actually in circulation */ + unsigned int buffer_total_count; + /* Designed number of buffers to be in circulation */ + unsigned int buffer_target_count; + /* Executed when ready list become non-empty */ + pvr2_stream_callback callback_func; + void *callback_data; + /* Context for transfer endpoint */ + struct usb_device *dev; + int endpoint; + /* Overhead for mutex enforcement */ + spinlock_t list_lock; + struct mutex mutex; + /* Tracking state for tolerating errors */ + unsigned int fail_count; + unsigned int fail_tolerance; +}; + +struct pvr2_buffer { + int id; + int signature; + enum pvr2_buffer_state state; + void *ptr; /* Pointer to storage area */ + unsigned int max_count; /* Size of storage area */ + unsigned int used_count; /* Amount of valid data in storage area */ + int status; /* Transfer result status */ + struct pvr2_stream *stream; + struct list_head list_overhead; + struct urb *purb; +}; + +const char *pvr2_buffer_state_decode(enum pvr2_buffer_state st) +{ + switch (st) { + case pvr2_buffer_state_none: return "none"; + case pvr2_buffer_state_idle: return "idle"; + case pvr2_buffer_state_queued: return "queued"; + case pvr2_buffer_state_ready: return "ready"; + } + return "unknown"; +} + +void pvr2_buffer_describe(struct pvr2_buffer *bp,const char *msg) +{ + pvr2_trace(PVR2_TRACE_INFO, + "buffer%s%s %p state=%s id=%d status=%d" + " stream=%p purb=%p sig=0x%x", + (msg ? " " : ""), + (msg ? msg : ""), + bp, + (bp ? pvr2_buffer_state_decode(bp->state) : "(invalid)"), + (bp ? bp->id : 0), + (bp ? bp->status : 0), + (bp ? bp->stream : 0), + (bp ? bp->purb : 0), + (bp ? bp->signature : 0)); +} + +static void pvr2_buffer_remove(struct pvr2_buffer *bp) +{ + unsigned int *cnt; + unsigned int *bcnt; + unsigned int ccnt; + struct pvr2_stream *sp = bp->stream; + switch (bp->state) { + case pvr2_buffer_state_idle: + cnt = &sp->i_count; + bcnt = &sp->i_bcount; + ccnt = bp->max_count; + break; + case pvr2_buffer_state_queued: + cnt = &sp->q_count; + bcnt = &sp->q_bcount; + ccnt = bp->max_count; + break; + case pvr2_buffer_state_ready: + cnt = &sp->r_count; + bcnt = &sp->r_bcount; + ccnt = bp->used_count; + break; + default: + return; + } + list_del_init(&bp->list_overhead); + (*cnt)--; + (*bcnt) -= ccnt; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/" + " bufferPool %8s dec cap=%07d cnt=%02d", + pvr2_buffer_state_decode(bp->state),*bcnt,*cnt); + bp->state = pvr2_buffer_state_none; +} + +static void pvr2_buffer_set_none(struct pvr2_buffer *bp) +{ + unsigned long irq_flags; + struct pvr2_stream *sp; + BUFFER_CHECK(bp); + sp = bp->stream; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s", + bp, + pvr2_buffer_state_decode(bp->state), + pvr2_buffer_state_decode(pvr2_buffer_state_none)); + spin_lock_irqsave(&sp->list_lock,irq_flags); + pvr2_buffer_remove(bp); + spin_unlock_irqrestore(&sp->list_lock,irq_flags); +} + +static int pvr2_buffer_set_ready(struct pvr2_buffer *bp) +{ + int fl; + unsigned long irq_flags; + struct pvr2_stream *sp; + BUFFER_CHECK(bp); + sp = bp->stream; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s", + bp, + pvr2_buffer_state_decode(bp->state), + pvr2_buffer_state_decode(pvr2_buffer_state_ready)); + spin_lock_irqsave(&sp->list_lock,irq_flags); + fl = (sp->r_count == 0); + pvr2_buffer_remove(bp); + list_add_tail(&bp->list_overhead,&sp->ready_list); + bp->state = pvr2_buffer_state_ready; + (sp->r_count)++; + sp->r_bcount += bp->used_count; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/" + " bufferPool %8s inc cap=%07d cnt=%02d", + pvr2_buffer_state_decode(bp->state), + sp->r_bcount,sp->r_count); + spin_unlock_irqrestore(&sp->list_lock,irq_flags); + return fl; +} + +static void pvr2_buffer_set_idle(struct pvr2_buffer *bp) +{ + unsigned long irq_flags; + struct pvr2_stream *sp; + BUFFER_CHECK(bp); + sp = bp->stream; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s", + bp, + pvr2_buffer_state_decode(bp->state), + pvr2_buffer_state_decode(pvr2_buffer_state_idle)); + spin_lock_irqsave(&sp->list_lock,irq_flags); + pvr2_buffer_remove(bp); + list_add_tail(&bp->list_overhead,&sp->idle_list); + bp->state = pvr2_buffer_state_idle; + (sp->i_count)++; + sp->i_bcount += bp->max_count; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/" + " bufferPool %8s inc cap=%07d cnt=%02d", + pvr2_buffer_state_decode(bp->state), + sp->i_bcount,sp->i_count); + spin_unlock_irqrestore(&sp->list_lock,irq_flags); +} + +static void pvr2_buffer_set_queued(struct pvr2_buffer *bp) +{ + unsigned long irq_flags; + struct pvr2_stream *sp; + BUFFER_CHECK(bp); + sp = bp->stream; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferState %p %6s --> %6s", + bp, + pvr2_buffer_state_decode(bp->state), + pvr2_buffer_state_decode(pvr2_buffer_state_queued)); + spin_lock_irqsave(&sp->list_lock,irq_flags); + pvr2_buffer_remove(bp); + list_add_tail(&bp->list_overhead,&sp->queued_list); + bp->state = pvr2_buffer_state_queued; + (sp->q_count)++; + sp->q_bcount += bp->max_count; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/" + " bufferPool %8s inc cap=%07d cnt=%02d", + pvr2_buffer_state_decode(bp->state), + sp->q_bcount,sp->q_count); + spin_unlock_irqrestore(&sp->list_lock,irq_flags); +} + +static void pvr2_buffer_wipe(struct pvr2_buffer *bp) +{ + if (bp->state == pvr2_buffer_state_queued) { + usb_kill_urb(bp->purb); + } +} + +static int pvr2_buffer_init(struct pvr2_buffer *bp, + struct pvr2_stream *sp, + unsigned int id) +{ + memset(bp,0,sizeof(*bp)); + bp->signature = BUFFER_SIG; + bp->id = id; + pvr2_trace(PVR2_TRACE_BUF_POOL, + "/*---TRACE_FLOW---*/ bufferInit %p stream=%p",bp,sp); + bp->stream = sp; + bp->state = pvr2_buffer_state_none; + INIT_LIST_HEAD(&bp->list_overhead); + bp->purb = usb_alloc_urb(0,GFP_KERNEL); + if (! bp->purb) return -ENOMEM; +#ifdef SANITY_CHECK_BUFFERS + pvr2_buffer_describe(bp,"create"); +#endif + return 0; +} + +static void pvr2_buffer_done(struct pvr2_buffer *bp) +{ +#ifdef SANITY_CHECK_BUFFERS + pvr2_buffer_describe(bp,"delete"); +#endif + pvr2_buffer_wipe(bp); + pvr2_buffer_set_none(bp); + bp->signature = 0; + bp->stream = 0; + if (bp->purb) usb_free_urb(bp->purb); + pvr2_trace(PVR2_TRACE_BUF_POOL,"/*---TRACE_FLOW---*/" + " bufferDone %p",bp); +} + +static int pvr2_stream_buffer_count(struct pvr2_stream *sp,unsigned int cnt) +{ + int ret; + unsigned int scnt; + + /* Allocate buffers pointer array in multiples of 32 entries */ + if (cnt == sp->buffer_total_count) return 0; + + pvr2_trace(PVR2_TRACE_BUF_POOL, + "/*---TRACE_FLOW---*/ poolResize " + " stream=%p cur=%d adj=%+d", + sp, + sp->buffer_total_count, + cnt-sp->buffer_total_count); + + scnt = cnt & ~0x1f; + if (cnt > scnt) scnt += 0x20; + + if (cnt > sp->buffer_total_count) { + if (scnt > sp->buffer_slot_count) { + struct pvr2_buffer **nb; + nb = kmalloc(scnt * sizeof(*nb),GFP_KERNEL); + if (!nb) return -ENOMEM; + if (sp->buffer_slot_count) { + memcpy(nb,sp->buffers, + sp->buffer_slot_count * sizeof(*nb)); + kfree(sp->buffers); + } + sp->buffers = nb; + sp->buffer_slot_count = scnt; + } + while (sp->buffer_total_count < cnt) { + struct pvr2_buffer *bp; + bp = kmalloc(sizeof(*bp),GFP_KERNEL); + if (!bp) return -ENOMEM; + ret = pvr2_buffer_init(bp,sp,sp->buffer_total_count); + if (ret) { + kfree(bp); + return -ENOMEM; + } + sp->buffers[sp->buffer_total_count] = bp; + (sp->buffer_total_count)++; + pvr2_buffer_set_idle(bp); + } + } else { + while (sp->buffer_total_count > cnt) { + struct pvr2_buffer *bp; + bp = sp->buffers[sp->buffer_total_count - 1]; + /* Paranoia */ + sp->buffers[sp->buffer_total_count - 1] = 0; + (sp->buffer_total_count)--; + pvr2_buffer_done(bp); + kfree(bp); + } + if (scnt < sp->buffer_slot_count) { + struct pvr2_buffer **nb = 0; + if (scnt) { + nb = kmalloc(scnt * sizeof(*nb),GFP_KERNEL); + if (!nb) return -ENOMEM; + memcpy(nb,sp->buffers,scnt * sizeof(*nb)); + } + kfree(sp->buffers); + sp->buffers = nb; + sp->buffer_slot_count = scnt; + } + } + return 0; +} + +static int pvr2_stream_achieve_buffer_count(struct pvr2_stream *sp) +{ + struct pvr2_buffer *bp; + unsigned int cnt; + + if (sp->buffer_total_count == sp->buffer_target_count) return 0; + + pvr2_trace(PVR2_TRACE_BUF_POOL, + "/*---TRACE_FLOW---*/" + " poolCheck stream=%p cur=%d tgt=%d", + sp,sp->buffer_total_count,sp->buffer_target_count); + + if (sp->buffer_total_count < sp->buffer_target_count) { + return pvr2_stream_buffer_count(sp,sp->buffer_target_count); + } + + cnt = 0; + while ((sp->buffer_total_count - cnt) > sp->buffer_target_count) { + bp = sp->buffers[sp->buffer_total_count - (cnt + 1)]; + if (bp->state != pvr2_buffer_state_idle) break; + cnt++; + } + if (cnt) { + pvr2_stream_buffer_count(sp,sp->buffer_total_count - cnt); + } + + return 0; +} + +static void pvr2_stream_internal_flush(struct pvr2_stream *sp) +{ + struct list_head *lp; + struct pvr2_buffer *bp1; + while ((lp = sp->queued_list.next) != &sp->queued_list) { + bp1 = list_entry(lp,struct pvr2_buffer,list_overhead); + pvr2_buffer_wipe(bp1); + /* At this point, we should be guaranteed that no + completion callback may happen on this buffer. But it's + possible that it might have completed after we noticed + it but before we wiped it. So double check its status + here first. */ + if (bp1->state != pvr2_buffer_state_queued) continue; + pvr2_buffer_set_idle(bp1); + } + if (sp->buffer_total_count != sp->buffer_target_count) { + pvr2_stream_achieve_buffer_count(sp); + } +} + +static void pvr2_stream_init(struct pvr2_stream *sp) +{ + spin_lock_init(&sp->list_lock); + mutex_init(&sp->mutex); + INIT_LIST_HEAD(&sp->queued_list); + INIT_LIST_HEAD(&sp->ready_list); + INIT_LIST_HEAD(&sp->idle_list); +} + +static void pvr2_stream_done(struct pvr2_stream *sp) +{ + mutex_lock(&sp->mutex); do { + pvr2_stream_internal_flush(sp); + pvr2_stream_buffer_count(sp,0); + } while (0); mutex_unlock(&sp->mutex); +} + +static void buffer_complete(struct urb *urb, struct pt_regs *regs) +{ + struct pvr2_buffer *bp = urb->context; + struct pvr2_stream *sp; + unsigned long irq_flags; + BUFFER_CHECK(bp); + sp = bp->stream; + bp->used_count = 0; + bp->status = 0; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferComplete %p stat=%d cnt=%d", + bp,urb->status,urb->actual_length); + spin_lock_irqsave(&sp->list_lock,irq_flags); + if ((!(urb->status)) || + (urb->status == -ENOENT) || + (urb->status == -ECONNRESET) || + (urb->status == -ESHUTDOWN)) { + bp->used_count = urb->actual_length; + if (sp->fail_count) { + pvr2_trace(PVR2_TRACE_TOLERANCE, + "stream %p transfer ok" + " - fail count reset",sp); + sp->fail_count = 0; + } + } else if (sp->fail_count < sp->fail_tolerance) { + // We can tolerate this error, because we're below the + // threshold... + (sp->fail_count)++; + pvr2_trace(PVR2_TRACE_TOLERANCE, + "stream %p ignoring error %d" + " - fail count increased to %u", + sp,urb->status,sp->fail_count); + } else { + bp->status = urb->status; + } + spin_unlock_irqrestore(&sp->list_lock,irq_flags); + pvr2_buffer_set_ready(bp); + if (sp && sp->callback_func) { + sp->callback_func(sp->callback_data); + } +} + +struct pvr2_stream *pvr2_stream_create(void) +{ + struct pvr2_stream *sp; + sp = kmalloc(sizeof(*sp),GFP_KERNEL); + if (!sp) return sp; + memset(sp,0,sizeof(*sp)); + pvr2_trace(PVR2_TRACE_INIT,"pvr2_stream_create: sp=%p",sp); + pvr2_stream_init(sp); + return sp; +} + +void pvr2_stream_destroy(struct pvr2_stream *sp) +{ + if (!sp) return; + pvr2_trace(PVR2_TRACE_INIT,"pvr2_stream_destroy: sp=%p",sp); + pvr2_stream_done(sp); + kfree(sp); +} + +void pvr2_stream_setup(struct pvr2_stream *sp, + struct usb_device *dev, + int endpoint, + unsigned int tolerance) +{ + mutex_lock(&sp->mutex); do { + pvr2_stream_internal_flush(sp); + sp->dev = dev; + sp->endpoint = endpoint; + sp->fail_tolerance = tolerance; + } while(0); mutex_unlock(&sp->mutex); +} + +void pvr2_stream_set_callback(struct pvr2_stream *sp, + pvr2_stream_callback func, + void *data) +{ + unsigned long irq_flags; + mutex_lock(&sp->mutex); do { + spin_lock_irqsave(&sp->list_lock,irq_flags); + sp->callback_data = data; + sp->callback_func = func; + spin_unlock_irqrestore(&sp->list_lock,irq_flags); + } while(0); mutex_unlock(&sp->mutex); +} + +/* Query / set the nominal buffer count */ +int pvr2_stream_get_buffer_count(struct pvr2_stream *sp) +{ + return sp->buffer_target_count; +} + +int pvr2_stream_set_buffer_count(struct pvr2_stream *sp,unsigned int cnt) +{ + int ret; + if (sp->buffer_target_count == cnt) return 0; + mutex_lock(&sp->mutex); do { + sp->buffer_target_count = cnt; + ret = pvr2_stream_achieve_buffer_count(sp); + } while(0); mutex_unlock(&sp->mutex); + return ret; +} + +struct pvr2_buffer *pvr2_stream_get_idle_buffer(struct pvr2_stream *sp) +{ + struct list_head *lp = sp->idle_list.next; + if (lp == &sp->idle_list) return 0; + return list_entry(lp,struct pvr2_buffer,list_overhead); +} + +struct pvr2_buffer *pvr2_stream_get_ready_buffer(struct pvr2_stream *sp) +{ + struct list_head *lp = sp->ready_list.next; + if (lp == &sp->ready_list) return 0; + return list_entry(lp,struct pvr2_buffer,list_overhead); +} + +struct pvr2_buffer *pvr2_stream_get_buffer(struct pvr2_stream *sp,int id) +{ + if (id < 0) return 0; + if (id >= sp->buffer_total_count) return 0; + return sp->buffers[id]; +} + +int pvr2_stream_get_ready_count(struct pvr2_stream *sp) +{ + return sp->r_count; +} + +int pvr2_stream_get_idle_count(struct pvr2_stream *sp) +{ + return sp->i_count; +} + +void pvr2_stream_flush(struct pvr2_stream *sp) +{ + mutex_lock(&sp->mutex); do { + pvr2_stream_internal_flush(sp); + } while(0); mutex_unlock(&sp->mutex); +} + +void pvr2_stream_kill(struct pvr2_stream *sp) +{ + struct pvr2_buffer *bp; + mutex_lock(&sp->mutex); do { + pvr2_stream_internal_flush(sp); + while ((bp = pvr2_stream_get_ready_buffer(sp)) != 0) { + pvr2_buffer_set_idle(bp); + } + if (sp->buffer_total_count != sp->buffer_target_count) { + pvr2_stream_achieve_buffer_count(sp); + } + } while(0); mutex_unlock(&sp->mutex); +} + +int pvr2_buffer_queue(struct pvr2_buffer *bp) +{ +#undef SEED_BUFFER +#ifdef SEED_BUFFER + unsigned int idx; + unsigned int val; +#endif + int ret = 0; + struct pvr2_stream *sp; + if (!bp) return -EINVAL; + sp = bp->stream; + mutex_lock(&sp->mutex); do { + pvr2_buffer_wipe(bp); + if (!sp->dev) { + ret = -EIO; + break; + } + pvr2_buffer_set_queued(bp); +#ifdef SEED_BUFFER + for (idx = 0; idx < (bp->max_count) / 4; idx++) { + val = bp->id << 24; + val |= idx; + ((unsigned int *)(bp->ptr))[idx] = val; + } +#endif + bp->status = -EINPROGRESS; + usb_fill_bulk_urb(bp->purb, // struct urb *urb + sp->dev, // struct usb_device *dev + // endpoint (below) + usb_rcvbulkpipe(sp->dev,sp->endpoint), + bp->ptr, // void *transfer_buffer + bp->max_count, // int buffer_length + buffer_complete, + bp); + usb_submit_urb(bp->purb,GFP_KERNEL); + } while(0); mutex_unlock(&sp->mutex); + return ret; +} + +int pvr2_buffer_idle(struct pvr2_buffer *bp) +{ + struct pvr2_stream *sp; + if (!bp) return -EINVAL; + sp = bp->stream; + mutex_lock(&sp->mutex); do { + pvr2_buffer_wipe(bp); + pvr2_buffer_set_idle(bp); + if (sp->buffer_total_count != sp->buffer_target_count) { + pvr2_stream_achieve_buffer_count(sp); + } + } while(0); mutex_unlock(&sp->mutex); + return 0; +} + +int pvr2_buffer_set_buffer(struct pvr2_buffer *bp,void *ptr,unsigned int cnt) +{ + int ret = 0; + unsigned long irq_flags; + struct pvr2_stream *sp; + if (!bp) return -EINVAL; + sp = bp->stream; + mutex_lock(&sp->mutex); do { + spin_lock_irqsave(&sp->list_lock,irq_flags); + if (bp->state != pvr2_buffer_state_idle) { + ret = -EPERM; + } else { + bp->ptr = ptr; + bp->stream->i_bcount -= bp->max_count; + bp->max_count = cnt; + bp->stream->i_bcount += bp->max_count; + pvr2_trace(PVR2_TRACE_BUF_FLOW, + "/*---TRACE_FLOW---*/ bufferPool " + " %8s cap cap=%07d cnt=%02d", + pvr2_buffer_state_decode( + pvr2_buffer_state_idle), + bp->stream->i_bcount,bp->stream->i_count); + } + spin_unlock_irqrestore(&sp->list_lock,irq_flags); + } while(0); mutex_unlock(&sp->mutex); + return ret; +} + +unsigned int pvr2_buffer_get_count(struct pvr2_buffer *bp) +{ + return bp->used_count; +} + +int pvr2_buffer_get_status(struct pvr2_buffer *bp) +{ + return bp->status; +} + +enum pvr2_buffer_state pvr2_buffer_get_state(struct pvr2_buffer *bp) +{ + return bp->state; +} + +int pvr2_buffer_get_id(struct pvr2_buffer *bp) +{ + return bp->id; +} + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-io.h b/drivers/media/video/pvrusb2/pvrusb2-io.h new file mode 100644 index 00000000000..65e11385b2b --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-io.h @@ -0,0 +1,102 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_IO_H +#define __PVRUSB2_IO_H + +#include <linux/usb.h> +#include <linux/list.h> + +typedef void (*pvr2_stream_callback)(void *); + +enum pvr2_buffer_state { + pvr2_buffer_state_none = 0, // Not on any list + pvr2_buffer_state_idle = 1, // Buffer is ready to be used again + pvr2_buffer_state_queued = 2, // Buffer has been queued for filling + pvr2_buffer_state_ready = 3, // Buffer has data available +}; + +struct pvr2_stream; +struct pvr2_buffer; + +const char *pvr2_buffer_state_decode(enum pvr2_buffer_state); + +/* Initialize / tear down stream structure */ +struct pvr2_stream *pvr2_stream_create(void); +void pvr2_stream_destroy(struct pvr2_stream *); +void pvr2_stream_setup(struct pvr2_stream *, + struct usb_device *dev,int endpoint, + unsigned int tolerance); +void pvr2_stream_set_callback(struct pvr2_stream *, + pvr2_stream_callback func, + void *data); + +/* Query / set the nominal buffer count */ +int pvr2_stream_get_buffer_count(struct pvr2_stream *); +int pvr2_stream_set_buffer_count(struct pvr2_stream *,unsigned int); + +/* Get a pointer to a buffer that is either idle, ready, or is specified + named. */ +struct pvr2_buffer *pvr2_stream_get_idle_buffer(struct pvr2_stream *); +struct pvr2_buffer *pvr2_stream_get_ready_buffer(struct pvr2_stream *); +struct pvr2_buffer *pvr2_stream_get_buffer(struct pvr2_stream *sp,int id); + +/* Find out how many buffers are idle or ready */ +int pvr2_stream_get_idle_count(struct pvr2_stream *); +int pvr2_stream_get_ready_count(struct pvr2_stream *); + +/* Kill all pending operations */ +void pvr2_stream_flush(struct pvr2_stream *); + +/* Kill all pending buffers and throw away any ready buffers as well */ +void pvr2_stream_kill(struct pvr2_stream *); + +/* Set up the actual storage for a buffer */ +int pvr2_buffer_set_buffer(struct pvr2_buffer *,void *ptr,unsigned int cnt); + +/* Find out size of data in the given ready buffer */ +unsigned int pvr2_buffer_get_count(struct pvr2_buffer *); + +/* Retrieve completion code for given ready buffer */ +int pvr2_buffer_get_status(struct pvr2_buffer *); + +/* Retrieve state of given buffer */ +enum pvr2_buffer_state pvr2_buffer_get_state(struct pvr2_buffer *); + +/* Retrieve ID of given buffer */ +int pvr2_buffer_get_id(struct pvr2_buffer *); + +/* Start reading into given buffer (kill it if needed) */ +int pvr2_buffer_queue(struct pvr2_buffer *); + +/* Move buffer back to idle pool (kill it if needed) */ +int pvr2_buffer_idle(struct pvr2_buffer *); + +#endif /* __PVRUSB2_IO_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-ioread.c b/drivers/media/video/pvrusb2/pvrusb2-ioread.c new file mode 100644 index 00000000000..49da062e327 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-ioread.c @@ -0,0 +1,513 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-ioread.h" +#include "pvrusb2-debug.h" +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <asm/uaccess.h> + +#define BUFFER_COUNT 32 +#define BUFFER_SIZE PAGE_ALIGN(0x4000) + +struct pvr2_ioread { + struct pvr2_stream *stream; + char *buffer_storage[BUFFER_COUNT]; + char *sync_key_ptr; + unsigned int sync_key_len; + unsigned int sync_buf_offs; + unsigned int sync_state; + unsigned int sync_trashed_count; + int enabled; // Streaming is on + int spigot_open; // OK to pass data to client + int stream_running; // Passing data to client now + + /* State relevant to current buffer being read */ + struct pvr2_buffer *c_buf; + char *c_data_ptr; + unsigned int c_data_len; + unsigned int c_data_offs; + struct mutex mutex; +}; + +static int pvr2_ioread_init(struct pvr2_ioread *cp) +{ + unsigned int idx; + + cp->stream = 0; + mutex_init(&cp->mutex); + + for (idx = 0; idx < BUFFER_COUNT; idx++) { + cp->buffer_storage[idx] = kmalloc(BUFFER_SIZE,GFP_KERNEL); + if (!(cp->buffer_storage[idx])) break; + } + + if (idx < BUFFER_COUNT) { + // An allocation appears to have failed + for (idx = 0; idx < BUFFER_COUNT; idx++) { + if (!(cp->buffer_storage[idx])) continue; + kfree(cp->buffer_storage[idx]); + } + return -ENOMEM; + } + return 0; +} + +static void pvr2_ioread_done(struct pvr2_ioread *cp) +{ + unsigned int idx; + + pvr2_ioread_setup(cp,0); + for (idx = 0; idx < BUFFER_COUNT; idx++) { + if (!(cp->buffer_storage[idx])) continue; + kfree(cp->buffer_storage[idx]); + } +} + +struct pvr2_ioread *pvr2_ioread_create(void) +{ + struct pvr2_ioread *cp; + cp = kmalloc(sizeof(*cp),GFP_KERNEL); + if (!cp) return 0; + pvr2_trace(PVR2_TRACE_STRUCT,"pvr2_ioread_create id=%p",cp); + memset(cp,0,sizeof(*cp)); + if (pvr2_ioread_init(cp) < 0) { + kfree(cp); + return 0; + } + return cp; +} + +void pvr2_ioread_destroy(struct pvr2_ioread *cp) +{ + if (!cp) return; + pvr2_ioread_done(cp); + pvr2_trace(PVR2_TRACE_STRUCT,"pvr2_ioread_destroy id=%p",cp); + if (cp->sync_key_ptr) { + kfree(cp->sync_key_ptr); + cp->sync_key_ptr = 0; + } + kfree(cp); +} + +void pvr2_ioread_set_sync_key(struct pvr2_ioread *cp, + const char *sync_key_ptr, + unsigned int sync_key_len) +{ + if (!cp) return; + + if (!sync_key_ptr) sync_key_len = 0; + if ((sync_key_len == cp->sync_key_len) && + ((!sync_key_len) || + (!memcmp(sync_key_ptr,cp->sync_key_ptr,sync_key_len)))) return; + + if (sync_key_len != cp->sync_key_len) { + if (cp->sync_key_ptr) { + kfree(cp->sync_key_ptr); + cp->sync_key_ptr = 0; + } + cp->sync_key_len = 0; + if (sync_key_len) { + cp->sync_key_ptr = kmalloc(sync_key_len,GFP_KERNEL); + if (cp->sync_key_ptr) { + cp->sync_key_len = sync_key_len; + } + } + } + if (!cp->sync_key_len) return; + memcpy(cp->sync_key_ptr,sync_key_ptr,cp->sync_key_len); +} + +static void pvr2_ioread_stop(struct pvr2_ioread *cp) +{ + if (!(cp->enabled)) return; + pvr2_trace(PVR2_TRACE_START_STOP, + "/*---TRACE_READ---*/ pvr2_ioread_stop id=%p",cp); + pvr2_stream_kill(cp->stream); + cp->c_buf = 0; + cp->c_data_ptr = 0; + cp->c_data_len = 0; + cp->c_data_offs = 0; + cp->enabled = 0; + cp->stream_running = 0; + cp->spigot_open = 0; + if (cp->sync_state) { + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ sync_state <== 0"); + cp->sync_state = 0; + } +} + +static int pvr2_ioread_start(struct pvr2_ioread *cp) +{ + int stat; + struct pvr2_buffer *bp; + if (cp->enabled) return 0; + if (!(cp->stream)) return 0; + pvr2_trace(PVR2_TRACE_START_STOP, + "/*---TRACE_READ---*/ pvr2_ioread_start id=%p",cp); + while ((bp = pvr2_stream_get_idle_buffer(cp->stream)) != 0) { + stat = pvr2_buffer_queue(bp); + if (stat < 0) { + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/" + " pvr2_ioread_start id=%p" + " error=%d", + cp,stat); + pvr2_ioread_stop(cp); + return stat; + } + } + cp->enabled = !0; + cp->c_buf = 0; + cp->c_data_ptr = 0; + cp->c_data_len = 0; + cp->c_data_offs = 0; + cp->stream_running = 0; + if (cp->sync_key_len) { + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ sync_state <== 1"); + cp->sync_state = 1; + cp->sync_trashed_count = 0; + cp->sync_buf_offs = 0; + } + cp->spigot_open = 0; + return 0; +} + +struct pvr2_stream *pvr2_ioread_get_stream(struct pvr2_ioread *cp) +{ + return cp->stream; +} + +int pvr2_ioread_setup(struct pvr2_ioread *cp,struct pvr2_stream *sp) +{ + int ret; + unsigned int idx; + struct pvr2_buffer *bp; + + mutex_lock(&cp->mutex); do { + if (cp->stream) { + pvr2_trace(PVR2_TRACE_START_STOP, + "/*---TRACE_READ---*/" + " pvr2_ioread_setup (tear-down) id=%p",cp); + pvr2_ioread_stop(cp); + pvr2_stream_kill(cp->stream); + pvr2_stream_set_buffer_count(cp->stream,0); + cp->stream = 0; + } + if (sp) { + pvr2_trace(PVR2_TRACE_START_STOP, + "/*---TRACE_READ---*/" + " pvr2_ioread_setup (setup) id=%p",cp); + pvr2_stream_kill(sp); + ret = pvr2_stream_set_buffer_count(sp,BUFFER_COUNT); + if (ret < 0) return ret; + for (idx = 0; idx < BUFFER_COUNT; idx++) { + bp = pvr2_stream_get_buffer(sp,idx); + pvr2_buffer_set_buffer(bp, + cp->buffer_storage[idx], + BUFFER_SIZE); + } + cp->stream = sp; + } + } while (0); mutex_unlock(&cp->mutex); + + return 0; +} + +int pvr2_ioread_set_enabled(struct pvr2_ioread *cp,int fl) +{ + int ret = 0; + if ((!fl) == (!(cp->enabled))) return ret; + + mutex_lock(&cp->mutex); do { + if (fl) { + ret = pvr2_ioread_start(cp); + } else { + pvr2_ioread_stop(cp); + } + } while (0); mutex_unlock(&cp->mutex); + return ret; +} + +int pvr2_ioread_get_enabled(struct pvr2_ioread *cp) +{ + return cp->enabled != 0; +} + +int pvr2_ioread_get_buffer(struct pvr2_ioread *cp) +{ + int stat; + + while (cp->c_data_len <= cp->c_data_offs) { + if (cp->c_buf) { + // Flush out current buffer first. + stat = pvr2_buffer_queue(cp->c_buf); + if (stat < 0) { + // Streaming error... + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/" + " pvr2_ioread_read id=%p" + " queue_error=%d", + cp,stat); + pvr2_ioread_stop(cp); + return 0; + } + cp->c_buf = 0; + cp->c_data_ptr = 0; + cp->c_data_len = 0; + cp->c_data_offs = 0; + } + // Now get a freshly filled buffer. + cp->c_buf = pvr2_stream_get_ready_buffer(cp->stream); + if (!cp->c_buf) break; // Nothing ready; done. + cp->c_data_len = pvr2_buffer_get_count(cp->c_buf); + if (!cp->c_data_len) { + // Nothing transferred. Was there an error? + stat = pvr2_buffer_get_status(cp->c_buf); + if (stat < 0) { + // Streaming error... + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/" + " pvr2_ioread_read id=%p" + " buffer_error=%d", + cp,stat); + pvr2_ioread_stop(cp); + // Give up. + return 0; + } + // Start over... + continue; + } + cp->c_data_offs = 0; + cp->c_data_ptr = cp->buffer_storage[ + pvr2_buffer_get_id(cp->c_buf)]; + } + return !0; +} + +void pvr2_ioread_filter(struct pvr2_ioread *cp) +{ + unsigned int idx; + if (!cp->enabled) return; + if (cp->sync_state != 1) return; + + // Search the stream for our synchronization key. This is made + // complicated by the fact that in order to be honest with + // ourselves here we must search across buffer boundaries... + mutex_lock(&cp->mutex); while (1) { + // Ensure we have a buffer + if (!pvr2_ioread_get_buffer(cp)) break; + if (!cp->c_data_len) break; + + // Now walk the buffer contents until we match the key or + // run out of buffer data. + for (idx = cp->c_data_offs; idx < cp->c_data_len; idx++) { + if (cp->sync_buf_offs >= cp->sync_key_len) break; + if (cp->c_data_ptr[idx] == + cp->sync_key_ptr[cp->sync_buf_offs]) { + // Found the next key byte + (cp->sync_buf_offs)++; + } else { + // Whoops, mismatched. Start key over... + cp->sync_buf_offs = 0; + } + } + + // Consume what we've walked through + cp->c_data_offs += idx; + cp->sync_trashed_count += idx; + + // If we've found the key, then update state and get out. + if (cp->sync_buf_offs >= cp->sync_key_len) { + cp->sync_trashed_count -= cp->sync_key_len; + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/" + " sync_state <== 2 (skipped %u bytes)", + cp->sync_trashed_count); + cp->sync_state = 2; + cp->sync_buf_offs = 0; + break; + } + + if (cp->c_data_offs < cp->c_data_len) { + // Sanity check - should NEVER get here + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "ERROR: pvr2_ioread filter sync problem" + " len=%u offs=%u", + cp->c_data_len,cp->c_data_offs); + // Get out so we don't get stuck in an infinite + // loop. + break; + } + + continue; // (for clarity) + } mutex_unlock(&cp->mutex); +} + +int pvr2_ioread_avail(struct pvr2_ioread *cp) +{ + int ret; + if (!(cp->enabled)) { + // Stream is not enabled; so this is an I/O error + return -EIO; + } + + if (cp->sync_state == 1) { + pvr2_ioread_filter(cp); + if (cp->sync_state == 1) return -EAGAIN; + } + + ret = 0; + if (cp->stream_running) { + if (!pvr2_stream_get_ready_count(cp->stream)) { + // No data available at all right now. + ret = -EAGAIN; + } + } else { + if (pvr2_stream_get_ready_count(cp->stream) < BUFFER_COUNT/2) { + // Haven't buffered up enough yet; try again later + ret = -EAGAIN; + } + } + + if ((!(cp->spigot_open)) != (!(ret == 0))) { + cp->spigot_open = (ret == 0); + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ data is %s", + cp->spigot_open ? "available" : "pending"); + } + + return ret; +} + +int pvr2_ioread_read(struct pvr2_ioread *cp,void __user *buf,unsigned int cnt) +{ + unsigned int copied_cnt; + unsigned int bcnt; + const char *src; + int stat; + int ret = 0; + unsigned int req_cnt = cnt; + + if (!cnt) { + pvr2_trace(PVR2_TRACE_TRAP, + "/*---TRACE_READ---*/ pvr2_ioread_read id=%p" + " ZERO Request? Returning zero.",cp); + return 0; + } + + stat = pvr2_ioread_avail(cp); + if (stat < 0) return stat; + + cp->stream_running = !0; + + mutex_lock(&cp->mutex); do { + + // Suck data out of the buffers and copy to the user + copied_cnt = 0; + if (!buf) cnt = 0; + while (1) { + if (!pvr2_ioread_get_buffer(cp)) { + ret = -EIO; + break; + } + + if (!cnt) break; + + if (cp->sync_state == 2) { + // We're repeating the sync key data into + // the stream. + src = cp->sync_key_ptr + cp->sync_buf_offs; + bcnt = cp->sync_key_len - cp->sync_buf_offs; + } else { + // Normal buffer copy + src = cp->c_data_ptr + cp->c_data_offs; + bcnt = cp->c_data_len - cp->c_data_offs; + } + + if (!bcnt) break; + + // Don't run past user's buffer + if (bcnt > cnt) bcnt = cnt; + + if (copy_to_user(buf,src,bcnt)) { + // User supplied a bad pointer? + // Give up - this *will* cause data + // to be lost. + ret = -EFAULT; + break; + } + cnt -= bcnt; + buf += bcnt; + copied_cnt += bcnt; + + if (cp->sync_state == 2) { + // Update offset inside sync key that we're + // repeating back out. + cp->sync_buf_offs += bcnt; + if (cp->sync_buf_offs >= cp->sync_key_len) { + // Consumed entire key; switch mode + // to normal. + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/" + " sync_state <== 0"); + cp->sync_state = 0; + } + } else { + // Update buffer offset. + cp->c_data_offs += bcnt; + } + } + + } while (0); mutex_unlock(&cp->mutex); + + if (!ret) { + if (copied_cnt) { + // If anything was copied, return that count + ret = copied_cnt; + } else { + // Nothing copied; suggest to caller that another + // attempt should be tried again later + ret = -EAGAIN; + } + } + + pvr2_trace(PVR2_TRACE_DATA_FLOW, + "/*---TRACE_READ---*/ pvr2_ioread_read" + " id=%p request=%d result=%d", + cp,req_cnt,ret); + return ret; +} + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-ioread.h b/drivers/media/video/pvrusb2/pvrusb2-ioread.h new file mode 100644 index 00000000000..6b002597f5d --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-ioread.h @@ -0,0 +1,50 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_IOREAD_H +#define __PVRUSB2_IOREAD_H + +#include "pvrusb2-io.h" + +struct pvr2_ioread; + +struct pvr2_ioread *pvr2_ioread_create(void); +void pvr2_ioread_destroy(struct pvr2_ioread *); +int pvr2_ioread_setup(struct pvr2_ioread *,struct pvr2_stream *); +struct pvr2_stream *pvr2_ioread_get_stream(struct pvr2_ioread *); +void pvr2_ioread_set_sync_key(struct pvr2_ioread *, + const char *sync_key_ptr, + unsigned int sync_key_len); +int pvr2_ioread_set_enabled(struct pvr2_ioread *,int fl); +int pvr2_ioread_get_enabled(struct pvr2_ioread *); +int pvr2_ioread_read(struct pvr2_ioread *,void __user *buf,unsigned int cnt); +int pvr2_ioread_avail(struct pvr2_ioread *); + +#endif /* __PVRUSB2_IOREAD_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-main.c b/drivers/media/video/pvrusb2/pvrusb2-main.c new file mode 100644 index 00000000000..b95248274ed --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-main.c @@ -0,0 +1,172 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/smp_lock.h> +#include <linux/usb.h> +#include <linux/videodev2.h> + +#include "pvrusb2-hdw.h" +#include "pvrusb2-context.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-v4l2.h" +#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS +#include "pvrusb2-sysfs.h" +#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */ + +#define DRIVER_AUTHOR "Mike Isely <isely@pobox.com>" +#define DRIVER_DESC "Hauppauge WinTV-PVR-USB2 MPEG2 Encoder/Tuner" +#define DRIVER_VERSION "V4L in-tree version" + +#define DEFAULT_DEBUG_MASK (PVR2_TRACE_ERROR_LEGS| \ + PVR2_TRACE_INFO| \ + PVR2_TRACE_TOLERANCE| \ + PVR2_TRACE_TRAP| \ + 0) + +int pvrusb2_debug = DEFAULT_DEBUG_MASK; + +module_param_named(debug,pvrusb2_debug,int,S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(debug, "Debug trace mask"); + +#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS +static struct pvr2_sysfs_class *class_ptr = 0; +#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */ + +static void pvr_setup_attach(struct pvr2_context *pvr) +{ + /* Create association with v4l layer */ + pvr2_v4l2_create(pvr); +#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS + pvr2_sysfs_create(pvr,class_ptr); +#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */ +} + +static int pvr_probe(struct usb_interface *intf, + const struct usb_device_id *devid) +{ + struct pvr2_context *pvr; + + /* Create underlying hardware interface */ + pvr = pvr2_context_create(intf,devid,pvr_setup_attach); + if (!pvr) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "Failed to create hdw handler"); + return -ENOMEM; + } + + pvr2_trace(PVR2_TRACE_INIT,"pvr_probe(pvr=%p)",pvr); + + usb_set_intfdata(intf, pvr); + + return 0; +} + +/* + * pvr_disconnect() + * + */ +static void pvr_disconnect(struct usb_interface *intf) +{ + struct pvr2_context *pvr = usb_get_intfdata(intf); + + pvr2_trace(PVR2_TRACE_INIT,"pvr_disconnect(pvr=%p) BEGIN",pvr); + + usb_set_intfdata (intf, NULL); + pvr2_context_disconnect(pvr); + + pvr2_trace(PVR2_TRACE_INIT,"pvr_disconnect(pvr=%p) DONE",pvr); + +} + +static struct usb_driver pvr_driver = { + name: "pvrusb2", + id_table: pvr2_device_table, + probe: pvr_probe, + disconnect: pvr_disconnect +}; + +/* + * pvr_init() / pvr_exit() + * + * This code is run to initialize/exit the driver. + * + */ +static int __init pvr_init(void) +{ + int ret; + + pvr2_trace(PVR2_TRACE_INIT,"pvr_init"); + +#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS + class_ptr = pvr2_sysfs_class_create(); +#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */ + + ret = usb_register(&pvr_driver); + + if (ret == 0) + info(DRIVER_DESC " : " DRIVER_VERSION); + if (pvrusb2_debug) info("Debug mask is %d (0x%x)", + pvrusb2_debug,pvrusb2_debug); + + return ret; +} + +static void __exit pvr_exit(void) +{ + + pvr2_trace(PVR2_TRACE_INIT,"pvr_exit"); + +#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS + pvr2_sysfs_class_destroy(class_ptr); +#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */ + + usb_deregister(&pvr_driver); +} + +module_init(pvr_init); +module_exit(pvr_exit); + +/* Mike Isely <mcisely@pobox.com> 11-Mar-2006: See pvrusb2-hdw.c for + MODULE_DEVICE_TABLE(). We have to declare that attribute there + because that's where the device table actually is now and it seems + that certain gcc configurations get angry if MODULE_DEVICE_TABLE() + is used on what ends up being an external symbol. */ +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-std.c b/drivers/media/video/pvrusb2/pvrusb2-std.c new file mode 100644 index 00000000000..13406369364 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-std.c @@ -0,0 +1,408 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2-std.h" +#include "pvrusb2-debug.h" +#include <asm/string.h> +#include <linux/slab.h> + +struct std_name { + const char *name; + v4l2_std_id id; +}; + + +#define CSTD_PAL \ + (V4L2_STD_PAL_B| \ + V4L2_STD_PAL_B1| \ + V4L2_STD_PAL_G| \ + V4L2_STD_PAL_H| \ + V4L2_STD_PAL_I| \ + V4L2_STD_PAL_D| \ + V4L2_STD_PAL_D1| \ + V4L2_STD_PAL_K| \ + V4L2_STD_PAL_M| \ + V4L2_STD_PAL_N| \ + V4L2_STD_PAL_Nc| \ + V4L2_STD_PAL_60) + +#define CSTD_NTSC \ + (V4L2_STD_NTSC_M| \ + V4L2_STD_NTSC_M_JP| \ + V4L2_STD_NTSC_M_KR| \ + V4L2_STD_NTSC_443) + +#define CSTD_SECAM \ + (V4L2_STD_SECAM_B| \ + V4L2_STD_SECAM_D| \ + V4L2_STD_SECAM_G| \ + V4L2_STD_SECAM_H| \ + V4L2_STD_SECAM_K| \ + V4L2_STD_SECAM_K1| \ + V4L2_STD_SECAM_L| \ + V4L2_STD_SECAM_LC) + +#define TSTD_B (V4L2_STD_PAL_B|V4L2_STD_SECAM_B) +#define TSTD_B1 (V4L2_STD_PAL_B1) +#define TSTD_D (V4L2_STD_PAL_D|V4L2_STD_SECAM_D) +#define TSTD_D1 (V4L2_STD_PAL_D1) +#define TSTD_G (V4L2_STD_PAL_G|V4L2_STD_SECAM_G) +#define TSTD_H (V4L2_STD_PAL_H|V4L2_STD_SECAM_H) +#define TSTD_I (V4L2_STD_PAL_I) +#define TSTD_K (V4L2_STD_PAL_K|V4L2_STD_SECAM_K) +#define TSTD_K1 (V4L2_STD_SECAM_K1) +#define TSTD_L (V4L2_STD_SECAM_L) +#define TSTD_M (V4L2_STD_PAL_M|V4L2_STD_NTSC_M) +#define TSTD_N (V4L2_STD_PAL_N) +#define TSTD_Nc (V4L2_STD_PAL_Nc) +#define TSTD_60 (V4L2_STD_PAL_60) + +#define CSTD_ALL (CSTD_PAL|CSTD_NTSC|CSTD_SECAM) + +/* Mapping of standard bits to color system */ +const static struct std_name std_groups[] = { + {"PAL",CSTD_PAL}, + {"NTSC",CSTD_NTSC}, + {"SECAM",CSTD_SECAM}, +}; + +/* Mapping of standard bits to modulation system */ +const static struct std_name std_items[] = { + {"B",TSTD_B}, + {"B1",TSTD_B1}, + {"D",TSTD_D}, + {"D1",TSTD_D1}, + {"G",TSTD_G}, + {"H",TSTD_H}, + {"I",TSTD_I}, + {"K",TSTD_K}, + {"K1",TSTD_K1}, + {"L",TSTD_L}, + {"LC",V4L2_STD_SECAM_LC}, + {"M",TSTD_M}, + {"Mj",V4L2_STD_NTSC_M_JP}, + {"443",V4L2_STD_NTSC_443}, + {"Mk",V4L2_STD_NTSC_M_KR}, + {"N",TSTD_N}, + {"Nc",TSTD_Nc}, + {"60",TSTD_60}, +}; + + +// Search an array of std_name structures and return a pointer to the +// element with the matching name. +static const struct std_name *find_std_name(const struct std_name *arrPtr, + unsigned int arrSize, + const char *bufPtr, + unsigned int bufSize) +{ + unsigned int idx; + const struct std_name *p; + for (idx = 0; idx < arrSize; idx++) { + p = arrPtr + idx; + if (strlen(p->name) != bufSize) continue; + if (!memcmp(bufPtr,p->name,bufSize)) return p; + } + return 0; +} + + +int pvr2_std_str_to_id(v4l2_std_id *idPtr,const char *bufPtr, + unsigned int bufSize) +{ + v4l2_std_id id = 0; + v4l2_std_id cmsk = 0; + v4l2_std_id t; + int mMode = 0; + unsigned int cnt; + char ch; + const struct std_name *sp; + + while (bufSize) { + if (!mMode) { + cnt = 0; + while ((cnt < bufSize) && (bufPtr[cnt] != '-')) cnt++; + if (cnt >= bufSize) return 0; // No more characters + sp = find_std_name( + std_groups, + sizeof(std_groups)/sizeof(std_groups[0]), + bufPtr,cnt); + if (!sp) return 0; // Illegal color system name + cnt++; + bufPtr += cnt; + bufSize -= cnt; + mMode = !0; + cmsk = sp->id; + continue; + } + cnt = 0; + while (cnt < bufSize) { + ch = bufPtr[cnt]; + if (ch == ';') { + mMode = 0; + break; + } + if (ch == '/') break; + cnt++; + } + sp = find_std_name(std_items, + sizeof(std_items)/sizeof(std_items[0]), + bufPtr,cnt); + if (!sp) return 0; // Illegal modulation system ID + t = sp->id & cmsk; + if (!t) return 0; // Specific color + modulation system illegal + id |= t; + if (cnt < bufSize) cnt++; + bufPtr += cnt; + bufSize -= cnt; + } + + if (idPtr) *idPtr = id; + return !0; +} + + +unsigned int pvr2_std_id_to_str(char *bufPtr, unsigned int bufSize, + v4l2_std_id id) +{ + unsigned int idx1,idx2; + const struct std_name *ip,*gp; + int gfl,cfl; + unsigned int c1,c2; + cfl = 0; + c1 = 0; + for (idx1 = 0; + idx1 < sizeof(std_groups)/sizeof(std_groups[0]); + idx1++) { + gp = std_groups + idx1; + gfl = 0; + for (idx2 = 0; + idx2 < sizeof(std_items)/sizeof(std_items[0]); + idx2++) { + ip = std_items + idx2; + if (!(gp->id & ip->id & id)) continue; + if (!gfl) { + if (cfl) { + c2 = scnprintf(bufPtr,bufSize,";"); + c1 += c2; + bufSize -= c2; + bufPtr += c2; + } + cfl = !0; + c2 = scnprintf(bufPtr,bufSize, + "%s-",gp->name); + gfl = !0; + } else { + c2 = scnprintf(bufPtr,bufSize,"/"); + } + c1 += c2; + bufSize -= c2; + bufPtr += c2; + c2 = scnprintf(bufPtr,bufSize, + ip->name); + c1 += c2; + bufSize -= c2; + bufPtr += c2; + } + } + return c1; +} + + +// Template data for possible enumerated video standards. Here we group +// standards which share common frame rates and resolution. +static struct v4l2_standard generic_standards[] = { + { + .id = (TSTD_B|TSTD_B1| + TSTD_D|TSTD_D1| + TSTD_G| + TSTD_H| + TSTD_I| + TSTD_K|TSTD_K1| + TSTD_L| + V4L2_STD_SECAM_LC | + TSTD_N|TSTD_Nc), + .frameperiod = + { + .numerator = 1, + .denominator= 25 + }, + .framelines = 625, + .reserved = {0,0,0,0} + }, { + .id = (TSTD_M| + V4L2_STD_NTSC_M_JP| + V4L2_STD_NTSC_M_KR), + .frameperiod = + { + .numerator = 1001, + .denominator= 30000 + }, + .framelines = 525, + .reserved = {0,0,0,0} + }, { // This is a total wild guess + .id = (TSTD_60), + .frameperiod = + { + .numerator = 1001, + .denominator= 30000 + }, + .framelines = 525, + .reserved = {0,0,0,0} + }, { // This is total wild guess + .id = V4L2_STD_NTSC_443, + .frameperiod = + { + .numerator = 1001, + .denominator= 30000 + }, + .framelines = 525, + .reserved = {0,0,0,0} + } +}; + +#define generic_standards_cnt (sizeof(generic_standards)/sizeof(generic_standards[0])) + +static struct v4l2_standard *match_std(v4l2_std_id id) +{ + unsigned int idx; + for (idx = 0; idx < generic_standards_cnt; idx++) { + if (generic_standards[idx].id & id) { + return generic_standards + idx; + } + } + return 0; +} + +static int pvr2_std_fill(struct v4l2_standard *std,v4l2_std_id id) +{ + struct v4l2_standard *template; + int idx; + unsigned int bcnt; + template = match_std(id); + if (!template) return 0; + idx = std->index; + memcpy(std,template,sizeof(*template)); + std->index = idx; + std->id = id; + bcnt = pvr2_std_id_to_str(std->name,sizeof(std->name)-1,id); + std->name[bcnt] = 0; + pvr2_trace(PVR2_TRACE_INIT,"Set up standard idx=%u name=%s", + std->index,std->name); + return !0; +} + +/* These are special cases of combined standards that we should enumerate + separately if the component pieces are present. */ +static v4l2_std_id std_mixes[] = { + V4L2_STD_PAL_B | V4L2_STD_PAL_G, + V4L2_STD_PAL_D | V4L2_STD_PAL_K, + V4L2_STD_SECAM_B | V4L2_STD_SECAM_G, + V4L2_STD_SECAM_D | V4L2_STD_SECAM_K, +}; + +struct v4l2_standard *pvr2_std_create_enum(unsigned int *countptr, + v4l2_std_id id) +{ + unsigned int std_cnt = 0; + unsigned int idx,bcnt,idx2; + v4l2_std_id idmsk,cmsk,fmsk; + struct v4l2_standard *stddefs; + + if (pvrusb2_debug & PVR2_TRACE_INIT) { + char buf[50]; + bcnt = pvr2_std_id_to_str(buf,sizeof(buf),id); + pvr2_trace( + PVR2_TRACE_INIT,"Mapping standards mask=0x%x (%.*s)", + (int)id,bcnt,buf); + } + + *countptr = 0; + std_cnt = 0; + fmsk = 0; + for (idmsk = 1, cmsk = id; cmsk; idmsk <<= 1) { + if (!(idmsk & cmsk)) continue; + cmsk &= ~idmsk; + if (match_std(idmsk)) { + std_cnt++; + continue; + } + fmsk |= idmsk; + } + + for (idx2 = 0; idx2 < sizeof(std_mixes)/sizeof(std_mixes[0]); idx2++) { + if ((id & std_mixes[idx2]) == std_mixes[idx2]) std_cnt++; + } + + if (fmsk) { + char buf[50]; + bcnt = pvr2_std_id_to_str(buf,sizeof(buf),fmsk); + pvr2_trace( + PVR2_TRACE_ERROR_LEGS, + "WARNING:" + " Failed to classify the following standard(s): %.*s", + bcnt,buf); + } + + pvr2_trace(PVR2_TRACE_INIT,"Setting up %u unique standard(s)", + std_cnt); + if (!std_cnt) return 0; // paranoia + + stddefs = kmalloc(sizeof(struct v4l2_standard) * std_cnt, + GFP_KERNEL); + memset(stddefs,0,sizeof(struct v4l2_standard) * std_cnt); + for (idx = 0; idx < std_cnt; idx++) stddefs[idx].index = idx; + + idx = 0; + + /* Enumerate potential special cases */ + for (idx2 = 0; ((idx2 < sizeof(std_mixes)/sizeof(std_mixes[0])) && + (idx < std_cnt)); idx2++) { + if (!(id & std_mixes[idx2])) continue; + if (pvr2_std_fill(stddefs+idx,std_mixes[idx2])) idx++; + } + /* Now enumerate individual pieces */ + for (idmsk = 1, cmsk = id; cmsk && (idx < std_cnt); idmsk <<= 1) { + if (!(idmsk & cmsk)) continue; + cmsk &= ~idmsk; + if (!pvr2_std_fill(stddefs+idx,idmsk)) continue; + idx++; + } + + *countptr = std_cnt; + return stddefs; +} + +v4l2_std_id pvr2_std_get_usable(void) +{ + return CSTD_ALL; +} + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-std.h b/drivers/media/video/pvrusb2/pvrusb2-std.h new file mode 100644 index 00000000000..07c39937534 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-std.h @@ -0,0 +1,60 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_STD_H +#define __PVRUSB2_STD_H + +#include <linux/videodev2.h> + +// Convert string describing one or more video standards into a mask of V4L +// standard bits. Return true if conversion succeeds otherwise return +// false. String is expected to be of the form: C1-x/y;C2-a/b where C1 and +// C2 are color system names (e.g. "PAL", "NTSC") and x, y, a, and b are +// modulation schemes (e.g. "M", "B", "G", etc). +int pvr2_std_str_to_id(v4l2_std_id *idPtr,const char *bufPtr, + unsigned int bufSize); + +// Convert any arbitrary set of video standard bits into an unambiguous +// readable string. Return value is the number of bytes consumed in the +// buffer. The formatted string is of a form that can be parsed by our +// sibling std_std_to_id() function. +unsigned int pvr2_std_id_to_str(char *bufPtr, unsigned int bufSize, + v4l2_std_id id); + +// Create an array of suitable v4l2_standard structures given a bit mask of +// video standards to support. The array is allocated from the heap, and +// the number of elements is returned in the first argument. +struct v4l2_standard *pvr2_std_create_enum(unsigned int *countptr, + v4l2_std_id id); + +// Return mask of which video standard bits are valid +v4l2_std_id pvr2_std_get_usable(void); + +#endif /* __PVRUSB2_STD_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-sysfs.c b/drivers/media/video/pvrusb2/pvrusb2-sysfs.c new file mode 100644 index 00000000000..c6e6523d74b --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-sysfs.c @@ -0,0 +1,865 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/config.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <asm/semaphore.h> +#include "pvrusb2-sysfs.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2-debug.h" +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC +#include "pvrusb2-debugifc.h" +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ + +#define pvr2_sysfs_trace(...) pvr2_trace(PVR2_TRACE_SYSFS,__VA_ARGS__) + +struct pvr2_sysfs { + struct pvr2_channel channel; + struct class_device *class_dev; +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC + struct pvr2_sysfs_debugifc *debugifc; +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ + struct pvr2_sysfs_ctl_item *item_first; + struct pvr2_sysfs_ctl_item *item_last; + struct sysfs_ops kops; + struct kobj_type ktype; + struct class_device_attribute attr_v4l_minor_number; + struct class_device_attribute attr_unit_number; +}; + +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC +struct pvr2_sysfs_debugifc { + struct class_device_attribute attr_debugcmd; + struct class_device_attribute attr_debuginfo; +}; +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ + +struct pvr2_sysfs_ctl_item { + struct class_device_attribute attr_name; + struct class_device_attribute attr_type; + struct class_device_attribute attr_min; + struct class_device_attribute attr_max; + struct class_device_attribute attr_enum; + struct class_device_attribute attr_bits; + struct class_device_attribute attr_val; + struct class_device_attribute attr_custom; + struct pvr2_ctrl *cptr; + struct pvr2_sysfs *chptr; + struct pvr2_sysfs_ctl_item *item_next; + struct attribute *attr_gen[7]; + struct attribute_group grp; + char name[80]; +}; + +struct pvr2_sysfs_class { + struct class class; +}; + +static ssize_t show_name(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_ctrl *cptr; + struct pvr2_sysfs *sfp; + const char *name; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id); + if (!cptr) return -EINVAL; + + name = pvr2_ctrl_get_desc(cptr); + pvr2_sysfs_trace("pvr2_sysfs(%p) show_name(cid=%d) is %s",sfp,id,name); + + if (!name) return -EINVAL; + + return scnprintf(buf,PAGE_SIZE,"%s\n",name); +} + +static ssize_t show_type(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_ctrl *cptr; + struct pvr2_sysfs *sfp; + const char *name; + enum pvr2_ctl_type tp; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id); + if (!cptr) return -EINVAL; + + tp = pvr2_ctrl_get_type(cptr); + switch (tp) { + case pvr2_ctl_int: name = "integer"; break; + case pvr2_ctl_enum: name = "enum"; break; + case pvr2_ctl_bitmask: name = "bitmask"; break; + case pvr2_ctl_bool: name = "boolean"; break; + default: name = "?"; break; + } + pvr2_sysfs_trace("pvr2_sysfs(%p) show_type(cid=%d) is %s",sfp,id,name); + + if (!name) return -EINVAL; + + return scnprintf(buf,PAGE_SIZE,"%s\n",name); +} + +static ssize_t show_min(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_ctrl *cptr; + struct pvr2_sysfs *sfp; + long val; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id); + if (!cptr) return -EINVAL; + val = pvr2_ctrl_get_min(cptr); + + pvr2_sysfs_trace("pvr2_sysfs(%p) show_min(cid=%d) is %ld",sfp,id,val); + + return scnprintf(buf,PAGE_SIZE,"%ld\n",val); +} + +static ssize_t show_max(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_ctrl *cptr; + struct pvr2_sysfs *sfp; + long val; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id); + if (!cptr) return -EINVAL; + val = pvr2_ctrl_get_max(cptr); + + pvr2_sysfs_trace("pvr2_sysfs(%p) show_max(cid=%d) is %ld",sfp,id,val); + + return scnprintf(buf,PAGE_SIZE,"%ld\n",val); +} + +static ssize_t show_val_norm(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_ctrl *cptr; + struct pvr2_sysfs *sfp; + int val,ret; + unsigned int cnt = 0; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id); + if (!cptr) return -EINVAL; + + ret = pvr2_ctrl_get_value(cptr,&val); + if (ret < 0) return ret; + + ret = pvr2_ctrl_value_to_sym(cptr,~0,val, + buf,PAGE_SIZE-1,&cnt); + + pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_norm(cid=%d) is %.*s (%d)", + sfp,id,cnt,buf,val); + buf[cnt] = '\n'; + return cnt+1; +} + +static ssize_t show_val_custom(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_ctrl *cptr; + struct pvr2_sysfs *sfp; + int val,ret; + unsigned int cnt = 0; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id); + if (!cptr) return -EINVAL; + + ret = pvr2_ctrl_get_value(cptr,&val); + if (ret < 0) return ret; + + ret = pvr2_ctrl_custom_value_to_sym(cptr,~0,val, + buf,PAGE_SIZE-1,&cnt); + + pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_custom(cid=%d) is %.*s (%d)", + sfp,id,cnt,buf,val); + buf[cnt] = '\n'; + return cnt+1; +} + +static ssize_t show_enum(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_ctrl *cptr; + struct pvr2_sysfs *sfp; + long val; + unsigned int bcnt,ccnt,ecnt; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id); + if (!cptr) return -EINVAL; + ecnt = pvr2_ctrl_get_cnt(cptr); + bcnt = 0; + for (val = 0; val < ecnt; val++) { + pvr2_ctrl_get_valname(cptr,val,buf+bcnt,PAGE_SIZE-bcnt,&ccnt); + if (!ccnt) continue; + bcnt += ccnt; + if (bcnt >= PAGE_SIZE) break; + buf[bcnt] = '\n'; + bcnt++; + } + pvr2_sysfs_trace("pvr2_sysfs(%p) show_enum(cid=%d)",sfp,id); + return bcnt; +} + +static ssize_t show_bits(int id,struct class_device *class_dev,char *buf) +{ + struct pvr2_ctrl *cptr; + struct pvr2_sysfs *sfp; + int valid_bits,msk; + unsigned int bcnt,ccnt; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id); + if (!cptr) return -EINVAL; + valid_bits = pvr2_ctrl_get_mask(cptr); + bcnt = 0; + for (msk = 1; valid_bits; msk <<= 1) { + if (!(msk & valid_bits)) continue; + valid_bits &= ~msk; + pvr2_ctrl_get_valname(cptr,msk,buf+bcnt,PAGE_SIZE-bcnt,&ccnt); + bcnt += ccnt; + if (bcnt >= PAGE_SIZE) break; + buf[bcnt] = '\n'; + bcnt++; + } + pvr2_sysfs_trace("pvr2_sysfs(%p) show_bits(cid=%d)",sfp,id); + return bcnt; +} + +static int store_val_any(int id,int customfl,struct pvr2_sysfs *sfp, + const char *buf,unsigned int count) +{ + struct pvr2_ctrl *cptr; + int ret; + int mask,val; + + cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,id); + if (customfl) { + ret = pvr2_ctrl_custom_sym_to_value(cptr,buf,count,&mask,&val); + } else { + ret = pvr2_ctrl_sym_to_value(cptr,buf,count,&mask,&val); + } + if (ret < 0) return ret; + ret = pvr2_ctrl_set_mask_value(cptr,mask,val); + pvr2_hdw_commit_ctl(sfp->channel.hdw); + return ret; +} + +static ssize_t store_val_norm(int id,struct class_device *class_dev, + const char *buf,size_t count) +{ + struct pvr2_sysfs *sfp; + int ret; + sfp = (struct pvr2_sysfs *)class_dev->class_data; + ret = store_val_any(id,0,sfp,buf,count); + if (!ret) ret = count; + return ret; +} + +static ssize_t store_val_custom(int id,struct class_device *class_dev, + const char *buf,size_t count) +{ + struct pvr2_sysfs *sfp; + int ret; + sfp = (struct pvr2_sysfs *)class_dev->class_data; + ret = store_val_any(id,1,sfp,buf,count); + if (!ret) ret = count; + return ret; +} + +/* + Mike Isely <isely@pobox.com> 30-April-2005 + + This next batch of horrible preprocessor hackery is needed because the + kernel's class_device_attribute mechanism fails to pass the actual + attribute through to the show / store functions, which means we have no + way to package up any attribute-specific parameters, like for example the + control id. So we work around this brain-damage by encoding the control + id into the show / store functions themselves and pick the function based + on the control id we're setting up. These macros try to ease the pain. + Yuck. +*/ + +#define CREATE_SHOW_INSTANCE(sf_name,ctl_id) \ +static ssize_t sf_name##_##ctl_id(struct class_device *class_dev,char *buf) \ +{ return sf_name(ctl_id,class_dev,buf); } + +#define CREATE_STORE_INSTANCE(sf_name,ctl_id) \ +static ssize_t sf_name##_##ctl_id(struct class_device *class_dev,const char *buf,size_t count) \ +{ return sf_name(ctl_id,class_dev,buf,count); } + +#define CREATE_BATCH(ctl_id) \ +CREATE_SHOW_INSTANCE(show_name,ctl_id) \ +CREATE_SHOW_INSTANCE(show_type,ctl_id) \ +CREATE_SHOW_INSTANCE(show_min,ctl_id) \ +CREATE_SHOW_INSTANCE(show_max,ctl_id) \ +CREATE_SHOW_INSTANCE(show_val_norm,ctl_id) \ +CREATE_SHOW_INSTANCE(show_val_custom,ctl_id) \ +CREATE_SHOW_INSTANCE(show_enum,ctl_id) \ +CREATE_SHOW_INSTANCE(show_bits,ctl_id) \ +CREATE_STORE_INSTANCE(store_val_norm,ctl_id) \ +CREATE_STORE_INSTANCE(store_val_custom,ctl_id) \ + +CREATE_BATCH(0) +CREATE_BATCH(1) +CREATE_BATCH(2) +CREATE_BATCH(3) +CREATE_BATCH(4) +CREATE_BATCH(5) +CREATE_BATCH(6) +CREATE_BATCH(7) +CREATE_BATCH(8) +CREATE_BATCH(9) +CREATE_BATCH(10) +CREATE_BATCH(11) +CREATE_BATCH(12) +CREATE_BATCH(13) +CREATE_BATCH(14) +CREATE_BATCH(15) +CREATE_BATCH(16) +CREATE_BATCH(17) +CREATE_BATCH(18) +CREATE_BATCH(19) +CREATE_BATCH(20) +CREATE_BATCH(21) +CREATE_BATCH(22) +CREATE_BATCH(23) +CREATE_BATCH(24) +CREATE_BATCH(25) +CREATE_BATCH(26) +CREATE_BATCH(27) +CREATE_BATCH(28) +CREATE_BATCH(29) +CREATE_BATCH(30) +CREATE_BATCH(31) +CREATE_BATCH(32) +CREATE_BATCH(33) +CREATE_BATCH(34) +CREATE_BATCH(35) +CREATE_BATCH(36) +CREATE_BATCH(37) +CREATE_BATCH(38) +CREATE_BATCH(39) +CREATE_BATCH(40) +CREATE_BATCH(41) +CREATE_BATCH(42) +CREATE_BATCH(43) +CREATE_BATCH(44) +CREATE_BATCH(45) +CREATE_BATCH(46) +CREATE_BATCH(47) +CREATE_BATCH(48) +CREATE_BATCH(49) +CREATE_BATCH(50) +CREATE_BATCH(51) +CREATE_BATCH(52) +CREATE_BATCH(53) +CREATE_BATCH(54) +CREATE_BATCH(55) +CREATE_BATCH(56) +CREATE_BATCH(57) +CREATE_BATCH(58) +CREATE_BATCH(59) + +struct pvr2_sysfs_func_set { + ssize_t (*show_name)(struct class_device *,char *); + ssize_t (*show_type)(struct class_device *,char *); + ssize_t (*show_min)(struct class_device *,char *); + ssize_t (*show_max)(struct class_device *,char *); + ssize_t (*show_enum)(struct class_device *,char *); + ssize_t (*show_bits)(struct class_device *,char *); + ssize_t (*show_val_norm)(struct class_device *,char *); + ssize_t (*store_val_norm)(struct class_device *, + const char *,size_t); + ssize_t (*show_val_custom)(struct class_device *,char *); + ssize_t (*store_val_custom)(struct class_device *, + const char *,size_t); +}; + +#define INIT_BATCH(ctl_id) \ +[ctl_id] = { \ + .show_name = show_name_##ctl_id, \ + .show_type = show_type_##ctl_id, \ + .show_min = show_min_##ctl_id, \ + .show_max = show_max_##ctl_id, \ + .show_enum = show_enum_##ctl_id, \ + .show_bits = show_bits_##ctl_id, \ + .show_val_norm = show_val_norm_##ctl_id, \ + .store_val_norm = store_val_norm_##ctl_id, \ + .show_val_custom = show_val_custom_##ctl_id, \ + .store_val_custom = store_val_custom_##ctl_id, \ +} \ + +static struct pvr2_sysfs_func_set funcs[] = { + INIT_BATCH(0), + INIT_BATCH(1), + INIT_BATCH(2), + INIT_BATCH(3), + INIT_BATCH(4), + INIT_BATCH(5), + INIT_BATCH(6), + INIT_BATCH(7), + INIT_BATCH(8), + INIT_BATCH(9), + INIT_BATCH(10), + INIT_BATCH(11), + INIT_BATCH(12), + INIT_BATCH(13), + INIT_BATCH(14), + INIT_BATCH(15), + INIT_BATCH(16), + INIT_BATCH(17), + INIT_BATCH(18), + INIT_BATCH(19), + INIT_BATCH(20), + INIT_BATCH(21), + INIT_BATCH(22), + INIT_BATCH(23), + INIT_BATCH(24), + INIT_BATCH(25), + INIT_BATCH(26), + INIT_BATCH(27), + INIT_BATCH(28), + INIT_BATCH(29), + INIT_BATCH(30), + INIT_BATCH(31), + INIT_BATCH(32), + INIT_BATCH(33), + INIT_BATCH(34), + INIT_BATCH(35), + INIT_BATCH(36), + INIT_BATCH(37), + INIT_BATCH(38), + INIT_BATCH(39), + INIT_BATCH(40), + INIT_BATCH(41), + INIT_BATCH(42), + INIT_BATCH(43), + INIT_BATCH(44), + INIT_BATCH(45), + INIT_BATCH(46), + INIT_BATCH(47), + INIT_BATCH(48), + INIT_BATCH(49), + INIT_BATCH(50), + INIT_BATCH(51), + INIT_BATCH(52), + INIT_BATCH(53), + INIT_BATCH(54), + INIT_BATCH(55), + INIT_BATCH(56), + INIT_BATCH(57), + INIT_BATCH(58), + INIT_BATCH(59), +}; + + +static void pvr2_sysfs_add_control(struct pvr2_sysfs *sfp,int ctl_id) +{ + struct pvr2_sysfs_ctl_item *cip; + struct pvr2_sysfs_func_set *fp; + struct pvr2_ctrl *cptr; + unsigned int cnt,acnt; + + if ((ctl_id < 0) || (ctl_id >= (sizeof(funcs)/sizeof(funcs[0])))) { + return; + } + + fp = funcs + ctl_id; + cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,ctl_id); + if (!cptr) return; + + cip = kmalloc(sizeof(*cip),GFP_KERNEL); + if (!cip) return; + memset(cip,0,sizeof(*cip)); + pvr2_sysfs_trace("Creating pvr2_sysfs_ctl_item id=%p",cip); + + cip->cptr = cptr; + + cip->chptr = sfp; + cip->item_next = 0; + if (sfp->item_last) { + sfp->item_last->item_next = cip; + } else { + sfp->item_first = cip; + } + sfp->item_last = cip; + + cip->attr_name.attr.owner = THIS_MODULE; + cip->attr_name.attr.name = "name"; + cip->attr_name.attr.mode = S_IRUGO; + cip->attr_name.show = fp->show_name; + + cip->attr_type.attr.owner = THIS_MODULE; + cip->attr_type.attr.name = "type"; + cip->attr_type.attr.mode = S_IRUGO; + cip->attr_type.show = fp->show_type; + + cip->attr_min.attr.owner = THIS_MODULE; + cip->attr_min.attr.name = "min_val"; + cip->attr_min.attr.mode = S_IRUGO; + cip->attr_min.show = fp->show_min; + + cip->attr_max.attr.owner = THIS_MODULE; + cip->attr_max.attr.name = "max_val"; + cip->attr_max.attr.mode = S_IRUGO; + cip->attr_max.show = fp->show_max; + + cip->attr_val.attr.owner = THIS_MODULE; + cip->attr_val.attr.name = "cur_val"; + cip->attr_val.attr.mode = S_IRUGO; + + cip->attr_custom.attr.owner = THIS_MODULE; + cip->attr_custom.attr.name = "custom_val"; + cip->attr_custom.attr.mode = S_IRUGO; + + cip->attr_enum.attr.owner = THIS_MODULE; + cip->attr_enum.attr.name = "enum_val"; + cip->attr_enum.attr.mode = S_IRUGO; + cip->attr_enum.show = fp->show_enum; + + cip->attr_bits.attr.owner = THIS_MODULE; + cip->attr_bits.attr.name = "bit_val"; + cip->attr_bits.attr.mode = S_IRUGO; + cip->attr_bits.show = fp->show_bits; + + if (pvr2_ctrl_is_writable(cptr)) { + cip->attr_val.attr.mode |= S_IWUSR|S_IWGRP; + cip->attr_custom.attr.mode |= S_IWUSR|S_IWGRP; + } + + acnt = 0; + cip->attr_gen[acnt++] = &cip->attr_name.attr; + cip->attr_gen[acnt++] = &cip->attr_type.attr; + cip->attr_gen[acnt++] = &cip->attr_val.attr; + cip->attr_val.show = fp->show_val_norm; + cip->attr_val.store = fp->store_val_norm; + if (pvr2_ctrl_has_custom_symbols(cptr)) { + cip->attr_gen[acnt++] = &cip->attr_custom.attr; + cip->attr_custom.show = fp->show_val_custom; + cip->attr_custom.store = fp->store_val_custom; + } + switch (pvr2_ctrl_get_type(cptr)) { + case pvr2_ctl_enum: + // Control is an enumeration + cip->attr_gen[acnt++] = &cip->attr_enum.attr; + break; + case pvr2_ctl_int: + // Control is an integer + cip->attr_gen[acnt++] = &cip->attr_min.attr; + cip->attr_gen[acnt++] = &cip->attr_max.attr; + break; + case pvr2_ctl_bitmask: + // Control is an bitmask + cip->attr_gen[acnt++] = &cip->attr_bits.attr; + break; + default: break; + } + + cnt = scnprintf(cip->name,sizeof(cip->name)-1,"ctl_%s", + pvr2_ctrl_get_name(cptr)); + cip->name[cnt] = 0; + cip->grp.name = cip->name; + cip->grp.attrs = cip->attr_gen; + + sysfs_create_group(&sfp->class_dev->kobj,&cip->grp); +} + +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC +static ssize_t debuginfo_show(struct class_device *,char *); +static ssize_t debugcmd_show(struct class_device *,char *); +static ssize_t debugcmd_store(struct class_device *,const char *,size_t count); + +static void pvr2_sysfs_add_debugifc(struct pvr2_sysfs *sfp) +{ + struct pvr2_sysfs_debugifc *dip; + dip = kmalloc(sizeof(*dip),GFP_KERNEL); + if (!dip) return; + memset(dip,0,sizeof(*dip)); + dip->attr_debugcmd.attr.owner = THIS_MODULE; + dip->attr_debugcmd.attr.name = "debugcmd"; + dip->attr_debugcmd.attr.mode = S_IRUGO|S_IWUSR|S_IWGRP; + dip->attr_debugcmd.show = debugcmd_show; + dip->attr_debugcmd.store = debugcmd_store; + dip->attr_debuginfo.attr.owner = THIS_MODULE; + dip->attr_debuginfo.attr.name = "debuginfo"; + dip->attr_debuginfo.attr.mode = S_IRUGO; + dip->attr_debuginfo.show = debuginfo_show; + sfp->debugifc = dip; + class_device_create_file(sfp->class_dev,&dip->attr_debugcmd); + class_device_create_file(sfp->class_dev,&dip->attr_debuginfo); +} + + +static void pvr2_sysfs_tear_down_debugifc(struct pvr2_sysfs *sfp) +{ + if (!sfp->debugifc) return; + class_device_remove_file(sfp->class_dev, + &sfp->debugifc->attr_debuginfo); + class_device_remove_file(sfp->class_dev,&sfp->debugifc->attr_debugcmd); + kfree(sfp->debugifc); + sfp->debugifc = 0; +} +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ + + +static void pvr2_sysfs_add_controls(struct pvr2_sysfs *sfp) +{ + unsigned int idx,cnt; + cnt = pvr2_hdw_get_ctrl_count(sfp->channel.hdw); + for (idx = 0; idx < cnt; idx++) { + pvr2_sysfs_add_control(sfp,idx); + } +} + + +static void pvr2_sysfs_tear_down_controls(struct pvr2_sysfs *sfp) +{ + struct pvr2_sysfs_ctl_item *cip1,*cip2; + for (cip1 = sfp->item_first; cip1; cip1 = cip2) { + cip2 = cip1->item_next; + sysfs_remove_group(&sfp->class_dev->kobj,&cip1->grp); + pvr2_sysfs_trace("Destroying pvr2_sysfs_ctl_item id=%p",cip1); + kfree(cip1); + } +} + + +static void pvr2_sysfs_class_release(struct class *class) +{ + struct pvr2_sysfs_class *clp; + clp = container_of(class,struct pvr2_sysfs_class,class); + pvr2_sysfs_trace("Destroying pvr2_sysfs_class id=%p",clp); + kfree(clp); +} + + +static void pvr2_sysfs_release(struct class_device *class_dev) +{ + pvr2_sysfs_trace("Releasing class_dev id=%p",class_dev); + kfree(class_dev); +} + + +static void class_dev_destroy(struct pvr2_sysfs *sfp) +{ + if (!sfp->class_dev) return; +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC + pvr2_sysfs_tear_down_debugifc(sfp); +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ + pvr2_sysfs_tear_down_controls(sfp); + class_device_remove_file(sfp->class_dev,&sfp->attr_v4l_minor_number); + class_device_remove_file(sfp->class_dev,&sfp->attr_unit_number); + pvr2_sysfs_trace("Destroying class_dev id=%p",sfp->class_dev); + sfp->class_dev->class_data = 0; + class_device_unregister(sfp->class_dev); + sfp->class_dev = 0; +} + + +static ssize_t v4l_minor_number_show(struct class_device *class_dev,char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + return scnprintf(buf,PAGE_SIZE,"%d\n", + pvr2_hdw_v4l_get_minor_number(sfp->channel.hdw)); +} + + +static ssize_t unit_number_show(struct class_device *class_dev,char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + return scnprintf(buf,PAGE_SIZE,"%d\n", + pvr2_hdw_get_unit_number(sfp->channel.hdw)); +} + + +static void class_dev_create(struct pvr2_sysfs *sfp, + struct pvr2_sysfs_class *class_ptr) +{ + struct usb_device *usb_dev; + struct class_device *class_dev; + usb_dev = pvr2_hdw_get_dev(sfp->channel.hdw); + if (!usb_dev) return; + class_dev = kmalloc(sizeof(*class_dev),GFP_KERNEL); + if (!class_dev) return; + memset(class_dev,0,sizeof(*class_dev)); + + pvr2_sysfs_trace("Creating class_dev id=%p",class_dev); + + class_dev->class = &class_ptr->class; + if (pvr2_hdw_get_sn(sfp->channel.hdw)) { + snprintf(class_dev->class_id,BUS_ID_SIZE,"sn-%lu", + pvr2_hdw_get_sn(sfp->channel.hdw)); + } else if (pvr2_hdw_get_unit_number(sfp->channel.hdw) >= 0) { + snprintf(class_dev->class_id,BUS_ID_SIZE,"unit-%c", + pvr2_hdw_get_unit_number(sfp->channel.hdw) + 'a'); + } else { + kfree(class_dev); + return; + } + + class_dev->dev = &usb_dev->dev; + + sfp->class_dev = class_dev; + class_dev->class_data = sfp; + class_device_register(class_dev); + + sfp->attr_v4l_minor_number.attr.owner = THIS_MODULE; + sfp->attr_v4l_minor_number.attr.name = "v4l_minor_number"; + sfp->attr_v4l_minor_number.attr.mode = S_IRUGO; + sfp->attr_v4l_minor_number.show = v4l_minor_number_show; + sfp->attr_v4l_minor_number.store = 0; + class_device_create_file(sfp->class_dev,&sfp->attr_v4l_minor_number); + sfp->attr_unit_number.attr.owner = THIS_MODULE; + sfp->attr_unit_number.attr.name = "unit_number"; + sfp->attr_unit_number.attr.mode = S_IRUGO; + sfp->attr_unit_number.show = unit_number_show; + sfp->attr_unit_number.store = 0; + class_device_create_file(sfp->class_dev,&sfp->attr_unit_number); + + pvr2_sysfs_add_controls(sfp); +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC + pvr2_sysfs_add_debugifc(sfp); +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ +} + + +static void pvr2_sysfs_internal_check(struct pvr2_channel *chp) +{ + struct pvr2_sysfs *sfp; + sfp = container_of(chp,struct pvr2_sysfs,channel); + if (!sfp->channel.mc_head->disconnect_flag) return; + pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_sysfs id=%p",sfp); + class_dev_destroy(sfp); + pvr2_channel_done(&sfp->channel); + kfree(sfp); +} + + +struct pvr2_sysfs *pvr2_sysfs_create(struct pvr2_context *mp, + struct pvr2_sysfs_class *class_ptr) +{ + struct pvr2_sysfs *sfp; + sfp = kmalloc(sizeof(*sfp),GFP_KERNEL); + if (!sfp) return sfp; + memset(sfp,0,sizeof(*sfp)); + pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_sysfs id=%p",sfp); + pvr2_channel_init(&sfp->channel,mp); + sfp->channel.check_func = pvr2_sysfs_internal_check; + + class_dev_create(sfp,class_ptr); + return sfp; +} + + +static int pvr2_sysfs_hotplug(struct class_device *cd,char **envp, + int numenvp,char *buf,int size) +{ + /* Even though we don't do anything here, we still need this function + because sysfs will still try to call it. */ + return 0; +} + +struct pvr2_sysfs_class *pvr2_sysfs_class_create(void) +{ + struct pvr2_sysfs_class *clp; + clp = kmalloc(sizeof(*clp),GFP_KERNEL); + if (!clp) return clp; + memset(clp,0,sizeof(*clp)); + pvr2_sysfs_trace("Creating pvr2_sysfs_class id=%p",clp); + clp->class.name = "pvrusb2"; + clp->class.class_release = pvr2_sysfs_class_release; + clp->class.release = pvr2_sysfs_release; + clp->class.uevent = pvr2_sysfs_hotplug; + if (class_register(&clp->class)) { + pvr2_sysfs_trace( + "Registration failed for pvr2_sysfs_class id=%p",clp); + kfree(clp); + clp = 0; + } + return clp; +} + + +void pvr2_sysfs_class_destroy(struct pvr2_sysfs_class *clp) +{ + class_unregister(&clp->class); +} + + +#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC +static ssize_t debuginfo_show(struct class_device *class_dev,char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + pvr2_hdw_trigger_module_log(sfp->channel.hdw); + return pvr2_debugifc_print_info(sfp->channel.hdw,buf,PAGE_SIZE); +} + + +static ssize_t debugcmd_show(struct class_device *class_dev,char *buf) +{ + struct pvr2_sysfs *sfp; + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + return pvr2_debugifc_print_status(sfp->channel.hdw,buf,PAGE_SIZE); +} + + +static ssize_t debugcmd_store(struct class_device *class_dev, + const char *buf,size_t count) +{ + struct pvr2_sysfs *sfp; + int ret; + + sfp = (struct pvr2_sysfs *)class_dev->class_data; + if (!sfp) return -EINVAL; + + ret = pvr2_debugifc_docmd(sfp->channel.hdw,buf,count); + if (ret < 0) return ret; + return count; +} +#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */ + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-sysfs.h b/drivers/media/video/pvrusb2/pvrusb2-sysfs.h new file mode 100644 index 00000000000..ff9373b47f8 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-sysfs.h @@ -0,0 +1,47 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_SYSFS_H +#define __PVRUSB2_SYSFS_H + +#include <linux/list.h> +#include <linux/sysfs.h> +#include "pvrusb2-context.h" + +struct pvr2_sysfs; +struct pvr2_sysfs_class; + +struct pvr2_sysfs_class *pvr2_sysfs_class_create(void); +void pvr2_sysfs_class_destroy(struct pvr2_sysfs_class *); + +struct pvr2_sysfs *pvr2_sysfs_create(struct pvr2_context *, + struct pvr2_sysfs_class *); + +#endif /* __PVRUSB2_SYSFS_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-tuner.c b/drivers/media/video/pvrusb2/pvrusb2-tuner.c new file mode 100644 index 00000000000..f4aba8144ce --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-tuner.c @@ -0,0 +1,122 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "pvrusb2.h" +#include "pvrusb2-util.h" +#include "pvrusb2-tuner.h" +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <linux/videodev2.h> +#include <media/tuner.h> +#include <media/v4l2-common.h> + +struct pvr2_tuner_handler { + struct pvr2_hdw *hdw; + struct pvr2_i2c_client *client; + struct pvr2_i2c_handler i2c_handler; + int type_update_fl; +}; + + +static void set_type(struct pvr2_tuner_handler *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + struct tuner_setup setup; + pvr2_trace(PVR2_TRACE_CHIPS,"i2c tuner set_type(%d)",hdw->tuner_type); + if (((int)(hdw->tuner_type)) < 0) return; + + setup.addr = ADDR_UNSET; + setup.type = hdw->tuner_type; + setup.mode_mask = T_RADIO | T_ANALOG_TV; + /* We may really want mode_mask to be T_ANALOG_TV for now */ + pvr2_i2c_client_cmd(ctxt->client,TUNER_SET_TYPE_ADDR,&setup); + ctxt->type_update_fl = 0; +} + + +static int tuner_check(struct pvr2_tuner_handler *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + if (hdw->tuner_updated) ctxt->type_update_fl = !0; + return ctxt->type_update_fl != 0; +} + + +static void tuner_update(struct pvr2_tuner_handler *ctxt) +{ + if (ctxt->type_update_fl) set_type(ctxt); +} + + +static void pvr2_tuner_detach(struct pvr2_tuner_handler *ctxt) +{ + ctxt->client->handler = 0; + kfree(ctxt); +} + + +static unsigned int pvr2_tuner_describe(struct pvr2_tuner_handler *ctxt,char *buf,unsigned int cnt) +{ + return scnprintf(buf,cnt,"handler: pvrusb2-tuner"); +} + + +const static struct pvr2_i2c_handler_functions tuner_funcs = { + .detach = (void (*)(void *))pvr2_tuner_detach, + .check = (int (*)(void *))tuner_check, + .update = (void (*)(void *))tuner_update, + .describe = (unsigned int (*)(void *,char *,unsigned int))pvr2_tuner_describe, +}; + + +int pvr2_i2c_tuner_setup(struct pvr2_hdw *hdw,struct pvr2_i2c_client *cp) +{ + struct pvr2_tuner_handler *ctxt; + if (cp->handler) return 0; + + ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL); + if (!ctxt) return 0; + memset(ctxt,0,sizeof(*ctxt)); + + ctxt->i2c_handler.func_data = ctxt; + ctxt->i2c_handler.func_table = &tuner_funcs; + ctxt->type_update_fl = !0; + ctxt->client = cp; + ctxt->hdw = hdw; + cp->handler = &ctxt->i2c_handler; + pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x tuner handler set up", + cp->client->addr); + return !0; +} + + + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-tuner.h b/drivers/media/video/pvrusb2/pvrusb2-tuner.h new file mode 100644 index 00000000000..556f12aa916 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-tuner.h @@ -0,0 +1,38 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_TUNER_H +#define __PVRUSB2_TUNER_H + +#include "pvrusb2-i2c-core.h" + +int pvr2_i2c_tuner_setup(struct pvr2_hdw *,struct pvr2_i2c_client *); + +#endif /* __PVRUSB2_TUNER_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-util.h b/drivers/media/video/pvrusb2/pvrusb2-util.h new file mode 100644 index 00000000000..e53aee416f5 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-util.h @@ -0,0 +1,63 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_UTIL_H +#define __PVRUSB2_UTIL_H + +#define PVR2_DECOMPOSE_LE(t,i,d) \ + do { \ + (t)[i] = (d) & 0xff;\ + (t)[i+1] = ((d) >> 8) & 0xff;\ + (t)[i+2] = ((d) >> 16) & 0xff;\ + (t)[i+3] = ((d) >> 24) & 0xff;\ + } while(0) + +#define PVR2_DECOMPOSE_BE(t,i,d) \ + do { \ + (t)[i+3] = (d) & 0xff;\ + (t)[i+2] = ((d) >> 8) & 0xff;\ + (t)[i+1] = ((d) >> 16) & 0xff;\ + (t)[i] = ((d) >> 24) & 0xff;\ + } while(0) + +#define PVR2_COMPOSE_LE(t,i) \ + ((((u32)((t)[i+3])) << 24) | \ + (((u32)((t)[i+2])) << 16) | \ + (((u32)((t)[i+1])) << 8) | \ + ((u32)((t)[i]))) + +#define PVR2_COMPOSE_BE(t,i) \ + ((((u32)((t)[i])) << 24) | \ + (((u32)((t)[i+1])) << 16) | \ + (((u32)((t)[i+2])) << 8) | \ + ((u32)((t)[i+3]))) + + +#endif /* __PVRUSB2_UTIL_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-v4l2.c b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c new file mode 100644 index 00000000000..961951010c2 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-v4l2.c @@ -0,0 +1,1126 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/kernel.h> +#include <linux/version.h> +#include "pvrusb2-context.h" +#include "pvrusb2-hdw.h" +#include "pvrusb2.h" +#include "pvrusb2-debug.h" +#include "pvrusb2-v4l2.h" +#include "pvrusb2-ioread.h" +#include <linux/videodev2.h> +#include <media/v4l2-common.h> + +struct pvr2_v4l2_dev; +struct pvr2_v4l2_fh; +struct pvr2_v4l2; + +/* V4L no longer provide the ability to set / get a private context pointer + (i.e. video_get_drvdata / video_set_drvdata), which means we have to + concoct our own context locating mechanism. Supposedly this is intended + to simplify driver implementation. It's not clear to me how that can + possibly be true. Our solution here is to maintain a lookup table of + our context instances, indexed by the minor device number of the V4L + device. See pvr2_v4l2_open() for some implications of this approach. */ +static struct pvr2_v4l2_dev *devices[256]; +static DEFINE_MUTEX(device_lock); + +struct pvr2_v4l2_dev { + struct pvr2_v4l2 *v4lp; + struct video_device *vdev; + struct pvr2_context_stream *stream; + int ctxt_idx; + enum pvr2_config config; +}; + +struct pvr2_v4l2_fh { + struct pvr2_channel channel; + struct pvr2_v4l2_dev *dev_info; + enum v4l2_priority prio; + struct pvr2_ioread *rhp; + struct file *file; + struct pvr2_v4l2 *vhead; + struct pvr2_v4l2_fh *vnext; + struct pvr2_v4l2_fh *vprev; + wait_queue_head_t wait_data; + int fw_mode_flag; +}; + +struct pvr2_v4l2 { + struct pvr2_channel channel; + struct pvr2_v4l2_fh *vfirst; + struct pvr2_v4l2_fh *vlast; + + struct v4l2_prio_state prio; + + /* streams */ + struct pvr2_v4l2_dev video_dev; +}; + +static int video_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1}; +module_param_array(video_nr, int, NULL, 0444); +MODULE_PARM_DESC(video_nr, "Offset for device's minor"); + +struct v4l2_capability pvr_capability ={ + .driver = "pvrusb2", + .card = "Hauppauge WinTV pvr-usb2", + .bus_info = "usb", + .version = KERNEL_VERSION(0,8,0), + .capabilities = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE | + V4L2_CAP_TUNER | V4L2_CAP_AUDIO | + V4L2_CAP_READWRITE), + .reserved = {0,0,0,0} +}; + +static struct v4l2_tuner pvr_v4l2_tuners[]= { + { + .index = 0, + .name = "TV Tuner", + .type = V4L2_TUNER_ANALOG_TV, + .capability = (V4L2_TUNER_CAP_NORM | + V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | + V4L2_TUNER_CAP_LANG2), + .rangelow = 0, + .rangehigh = 0, + .rxsubchans = V4L2_TUNER_SUB_STEREO, + .audmode = V4L2_TUNER_MODE_STEREO, + .signal = 0, + .afc = 0, + .reserved = {0,0,0,0} + } +}; + +struct v4l2_fmtdesc pvr_fmtdesc [] = { + { + .index = 0, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .flags = V4L2_FMT_FLAG_COMPRESSED, + .description = "MPEG1/2", + // This should really be V4L2_PIX_FMT_MPEG, but xawtv + // breaks when I do that. + .pixelformat = 0, // V4L2_PIX_FMT_MPEG, + .reserved = { 0, 0, 0, 0 } + } +}; + +#define PVR_FORMAT_PIX 0 +#define PVR_FORMAT_VBI 1 + +struct v4l2_format pvr_format [] = { + [PVR_FORMAT_PIX] = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt = { + .pix = { + .width = 720, + .height = 576, + // This should really be V4L2_PIX_FMT_MPEG, + // but xawtv breaks when I do that. + .pixelformat = 0, // V4L2_PIX_FMT_MPEG, + .field = V4L2_FIELD_INTERLACED, + .bytesperline = 0, // doesn't make sense + // here + //FIXME : Don't know what to put here... + .sizeimage = (32*1024), + .colorspace = 0, // doesn't make sense here + .priv = 0 + } + } + }, + [PVR_FORMAT_VBI] = { + .type = V4L2_BUF_TYPE_VBI_CAPTURE, + .fmt = { + .vbi = { + .sampling_rate = 27000000, + .offset = 248, + .samples_per_line = 1443, + .sample_format = V4L2_PIX_FMT_GREY, + .start = { 0, 0 }, + .count = { 0, 0 }, + .flags = 0, + .reserved = { 0, 0 } + } + } + } +}; + +/* + * pvr_ioctl() + * + * This is part of Video 4 Linux API. The procedure handles ioctl() calls. + * + */ +static int pvr2_v4l2_do_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, void *arg) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + struct pvr2_v4l2 *vp = fh->vhead; + struct pvr2_v4l2_dev *dev_info = fh->dev_info; + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + int ret = -EINVAL; + + if (pvrusb2_debug & PVR2_TRACE_V4LIOCTL) { + v4l_print_ioctl(pvr2_hdw_get_driver_name(hdw),cmd); + } + + if (!pvr2_hdw_dev_ok(hdw)) { + pvr2_trace(PVR2_TRACE_ERROR_LEGS, + "ioctl failed - bad or no context"); + return -EFAULT; + } + + /* check priority */ + switch (cmd) { + case VIDIOC_S_CTRL: + case VIDIOC_S_STD: + case VIDIOC_S_INPUT: + case VIDIOC_S_TUNER: + case VIDIOC_S_FREQUENCY: + ret = v4l2_prio_check(&vp->prio, &fh->prio); + if (ret) + return ret; + } + + switch (cmd) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + + memcpy(cap, &pvr_capability, sizeof(struct v4l2_capability)); + + ret = 0; + break; + } + + case VIDIOC_G_PRIORITY: + { + enum v4l2_priority *p = arg; + + *p = v4l2_prio_max(&vp->prio); + ret = 0; + break; + } + + case VIDIOC_S_PRIORITY: + { + enum v4l2_priority *prio = arg; + + ret = v4l2_prio_change(&vp->prio, &fh->prio, *prio); + break; + } + + case VIDIOC_ENUMSTD: + { + struct v4l2_standard *vs = (struct v4l2_standard *)arg; + int idx = vs->index; + ret = pvr2_hdw_get_stdenum_value(hdw,vs,idx+1); + break; + } + + case VIDIOC_G_STD: + { + int val = 0; + ret = pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR),&val); + *(v4l2_std_id *)arg = val; + break; + } + + case VIDIOC_S_STD: + { + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR), + *(v4l2_std_id *)arg); + break; + } + + case VIDIOC_ENUMINPUT: + { + struct pvr2_ctrl *cptr; + struct v4l2_input *vi = (struct v4l2_input *)arg; + struct v4l2_input tmp; + unsigned int cnt; + + cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_INPUT); + + memset(&tmp,0,sizeof(tmp)); + tmp.index = vi->index; + ret = 0; + switch (vi->index) { + case PVR2_CVAL_INPUT_TV: + case PVR2_CVAL_INPUT_RADIO: + tmp.type = V4L2_INPUT_TYPE_TUNER; + break; + case PVR2_CVAL_INPUT_SVIDEO: + case PVR2_CVAL_INPUT_COMPOSITE: + tmp.type = V4L2_INPUT_TYPE_CAMERA; + break; + default: + ret = -EINVAL; + break; + } + if (ret < 0) break; + + cnt = 0; + pvr2_ctrl_get_valname(cptr,vi->index, + tmp.name,sizeof(tmp.name)-1,&cnt); + tmp.name[cnt] = 0; + + /* Don't bother with audioset, since this driver currently + always switches the audio whenever the video is + switched. */ + + /* Handling std is a tougher problem. It doesn't make + sense in cases where a device might be multi-standard. + We could just copy out the current value for the + standard, but it can change over time. For now just + leave it zero. */ + + memcpy(vi, &tmp, sizeof(tmp)); + + ret = 0; + break; + } + + case VIDIOC_G_INPUT: + { + struct pvr2_ctrl *cptr; + struct v4l2_input *vi = (struct v4l2_input *)arg; + int val; + cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_INPUT); + val = 0; + ret = pvr2_ctrl_get_value(cptr,&val); + vi->index = val; + break; + } + + case VIDIOC_S_INPUT: + { + struct v4l2_input *vi = (struct v4l2_input *)arg; + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_INPUT), + vi->index); + break; + } + + case VIDIOC_ENUMAUDIO: + { + ret = -EINVAL; + break; + } + + case VIDIOC_G_AUDIO: + { + ret = -EINVAL; + break; + } + + case VIDIOC_S_AUDIO: + { + ret = -EINVAL; + break; + } + case VIDIOC_G_TUNER: + { + struct v4l2_tuner *vt = (struct v4l2_tuner *)arg; + unsigned int status_mask; + int val; + if (vt->index !=0) break; + + status_mask = pvr2_hdw_get_signal_status(hdw); + + memcpy(vt, &pvr_v4l2_tuners[vt->index], + sizeof(struct v4l2_tuner)); + + vt->signal = 0; + if (status_mask & PVR2_SIGNAL_OK) { + if (status_mask & PVR2_SIGNAL_STEREO) { + vt->rxsubchans = V4L2_TUNER_SUB_STEREO; + } else { + vt->rxsubchans = V4L2_TUNER_SUB_MONO; + } + if (status_mask & PVR2_SIGNAL_SAP) { + vt->rxsubchans |= (V4L2_TUNER_SUB_LANG1 | + V4L2_TUNER_SUB_LANG2); + } + vt->signal = 65535; + } + + val = 0; + ret = pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_AUDIOMODE), + &val); + vt->audmode = val; + break; + } + + case VIDIOC_S_TUNER: + { + struct v4l2_tuner *vt=(struct v4l2_tuner *)arg; + + if (vt->index != 0) + break; + + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_AUDIOMODE), + vt->audmode); + } + + case VIDIOC_S_FREQUENCY: + { + const struct v4l2_frequency *vf = (struct v4l2_frequency *)arg; + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_FREQUENCY), + vf->frequency * 62500); + break; + } + + case VIDIOC_G_FREQUENCY: + { + struct v4l2_frequency *vf = (struct v4l2_frequency *)arg; + int val = 0; + ret = pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_FREQUENCY), + &val); + val /= 62500; + vf->frequency = val; + break; + } + + case VIDIOC_ENUM_FMT: + { + struct v4l2_fmtdesc *fd = (struct v4l2_fmtdesc *)arg; + + /* Only one format is supported : mpeg.*/ + if (fd->index != 0) + break; + + memcpy(fd, pvr_fmtdesc, sizeof(struct v4l2_fmtdesc)); + ret = 0; + break; + } + + case VIDIOC_G_FMT: + { + struct v4l2_format *vf = (struct v4l2_format *)arg; + int val; + switch(vf->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + memcpy(vf, &pvr_format[PVR_FORMAT_PIX], + sizeof(struct v4l2_format)); + val = 0; + pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_HRES), + &val); + vf->fmt.pix.width = val; + val = 0; + pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_VRES), + &val); + vf->fmt.pix.height = val; + ret = 0; + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + // ????? Still need to figure out to do VBI correctly + ret = -EINVAL; + break; + default: + ret = -EINVAL; + break; + } + break; + } + + case VIDIOC_TRY_FMT: + case VIDIOC_S_FMT: + { + struct v4l2_format *vf = (struct v4l2_format *)arg; + + ret = 0; + switch(vf->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + int h = vf->fmt.pix.height; + int w = vf->fmt.pix.width; + + if (h < 200) { + h = 200; + } else if (h > 625) { + h = 625; + } + if (w < 320) { + w = 320; + } else if (w > 720) { + w = 720; + } + + memcpy(vf, &pvr_format[PVR_FORMAT_PIX], + sizeof(struct v4l2_format)); + vf->fmt.pix.width = w; + vf->fmt.pix.height = h; + + if (cmd == VIDIOC_S_FMT) { + pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw, + PVR2_CID_HRES), + vf->fmt.pix.width); + pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_by_id(hdw, + PVR2_CID_VRES), + vf->fmt.pix.height); + } + } break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + // ????? Still need to figure out to do VBI correctly + ret = -EINVAL; + break; + default: + ret = -EINVAL; + break; + } + break; + } + + case VIDIOC_STREAMON: + { + ret = pvr2_hdw_set_stream_type(hdw,dev_info->config); + if (ret < 0) return ret; + ret = pvr2_hdw_set_streaming(hdw,!0); + break; + } + + case VIDIOC_STREAMOFF: + { + ret = pvr2_hdw_set_streaming(hdw,0); + break; + } + + case VIDIOC_QUERYCTRL: + { + struct pvr2_ctrl *cptr; + struct v4l2_queryctrl *vc = (struct v4l2_queryctrl *)arg; + ret = 0; + if (vc->id & V4L2_CTRL_FLAG_NEXT_CTRL) { + cptr = pvr2_hdw_get_ctrl_nextv4l( + hdw,(vc->id & ~V4L2_CTRL_FLAG_NEXT_CTRL)); + if (cptr) vc->id = pvr2_ctrl_get_v4lid(cptr); + } else { + cptr = pvr2_hdw_get_ctrl_v4l(hdw,vc->id); + } + if (!cptr) { + pvr2_trace(PVR2_TRACE_V4LIOCTL, + "QUERYCTRL id=0x%x not implemented here", + vc->id); + ret = -EINVAL; + break; + } + + pvr2_trace(PVR2_TRACE_V4LIOCTL, + "QUERYCTRL id=0x%x mapping name=%s (%s)", + vc->id,pvr2_ctrl_get_name(cptr), + pvr2_ctrl_get_desc(cptr)); + strlcpy(vc->name,pvr2_ctrl_get_desc(cptr),sizeof(vc->name)); + vc->flags = pvr2_ctrl_get_v4lflags(cptr); + vc->default_value = pvr2_ctrl_get_def(cptr); + switch (pvr2_ctrl_get_type(cptr)) { + case pvr2_ctl_enum: + vc->type = V4L2_CTRL_TYPE_MENU; + vc->minimum = 0; + vc->maximum = pvr2_ctrl_get_cnt(cptr) - 1; + vc->step = 1; + break; + case pvr2_ctl_bool: + vc->type = V4L2_CTRL_TYPE_BOOLEAN; + vc->minimum = 0; + vc->maximum = 1; + vc->step = 1; + break; + case pvr2_ctl_int: + vc->type = V4L2_CTRL_TYPE_INTEGER; + vc->minimum = pvr2_ctrl_get_min(cptr); + vc->maximum = pvr2_ctrl_get_max(cptr); + vc->step = 1; + break; + default: + pvr2_trace(PVR2_TRACE_V4LIOCTL, + "QUERYCTRL id=0x%x name=%s not mappable", + vc->id,pvr2_ctrl_get_name(cptr)); + ret = -EINVAL; + break; + } + break; + } + + case VIDIOC_QUERYMENU: + { + struct v4l2_querymenu *vm = (struct v4l2_querymenu *)arg; + unsigned int cnt = 0; + ret = pvr2_ctrl_get_valname(pvr2_hdw_get_ctrl_v4l(hdw,vm->id), + vm->index, + vm->name,sizeof(vm->name)-1, + &cnt); + vm->name[cnt] = 0; + break; + } + + case VIDIOC_G_CTRL: + { + struct v4l2_control *vc = (struct v4l2_control *)arg; + int val = 0; + ret = pvr2_ctrl_get_value(pvr2_hdw_get_ctrl_v4l(hdw,vc->id), + &val); + vc->value = val; + break; + } + + case VIDIOC_S_CTRL: + { + struct v4l2_control *vc = (struct v4l2_control *)arg; + ret = pvr2_ctrl_set_value(pvr2_hdw_get_ctrl_v4l(hdw,vc->id), + vc->value); + break; + } + + case VIDIOC_G_EXT_CTRLS: + { + struct v4l2_ext_controls *ctls = + (struct v4l2_ext_controls *)arg; + struct v4l2_ext_control *ctrl; + unsigned int idx; + int val; + for (idx = 0; idx < ctls->count; idx++) { + ctrl = ctls->controls + idx; + ret = pvr2_ctrl_get_value( + pvr2_hdw_get_ctrl_v4l(hdw,ctrl->id),&val); + if (ret) { + ctls->error_idx = idx; + break; + } + /* Ensure that if read as a 64 bit value, the user + will still get a hopefully sane value */ + ctrl->value64 = 0; + ctrl->value = val; + } + break; + } + + case VIDIOC_S_EXT_CTRLS: + { + struct v4l2_ext_controls *ctls = + (struct v4l2_ext_controls *)arg; + struct v4l2_ext_control *ctrl; + unsigned int idx; + for (idx = 0; idx < ctls->count; idx++) { + ctrl = ctls->controls + idx; + ret = pvr2_ctrl_set_value( + pvr2_hdw_get_ctrl_v4l(hdw,ctrl->id), + ctrl->value); + if (ret) { + ctls->error_idx = idx; + break; + } + } + break; + } + + case VIDIOC_TRY_EXT_CTRLS: + { + struct v4l2_ext_controls *ctls = + (struct v4l2_ext_controls *)arg; + struct v4l2_ext_control *ctrl; + struct pvr2_ctrl *pctl; + unsigned int idx; + /* For the moment just validate that the requested control + actually exists. */ + for (idx = 0; idx < ctls->count; idx++) { + ctrl = ctls->controls + idx; + pctl = pvr2_hdw_get_ctrl_v4l(hdw,ctrl->id); + if (!pctl) { + ret = -EINVAL; + ctls->error_idx = idx; + break; + } + } + break; + } + + case VIDIOC_LOG_STATUS: + { + pvr2_hdw_trigger_module_log(hdw); + ret = 0; + break; + } + + default : + ret = v4l_compat_translate_ioctl(inode,file,cmd, + arg,pvr2_v4l2_do_ioctl); + } + + pvr2_hdw_commit_ctl(hdw); + + if (ret < 0) { + if (pvrusb2_debug & PVR2_TRACE_V4LIOCTL) { + pvr2_trace(PVR2_TRACE_V4LIOCTL, + "pvr2_v4l2_do_ioctl failure, ret=%d",ret); + } else { + if (pvrusb2_debug & PVR2_TRACE_V4LIOCTL) { + pvr2_trace(PVR2_TRACE_V4LIOCTL, + "pvr2_v4l2_do_ioctl failure, ret=%d" + " command was:",ret); + v4l_print_ioctl(pvr2_hdw_get_driver_name(hdw), + cmd); + } + } + } else { + pvr2_trace(PVR2_TRACE_V4LIOCTL, + "pvr2_v4l2_do_ioctl complete, ret=%d (0x%x)", + ret,ret); + } + return ret; +} + + +static void pvr2_v4l2_dev_destroy(struct pvr2_v4l2_dev *dip) +{ + pvr2_trace(PVR2_TRACE_INIT, + "unregistering device video%d [%s]", + dip->vdev->minor,pvr2_config_get_name(dip->config)); + if (dip->ctxt_idx >= 0) { + mutex_lock(&device_lock); + devices[dip->ctxt_idx] = NULL; + dip->ctxt_idx = -1; + mutex_unlock(&device_lock); + } + video_unregister_device(dip->vdev); +} + + +static void pvr2_v4l2_destroy_no_lock(struct pvr2_v4l2 *vp) +{ + pvr2_hdw_v4l_store_minor_number(vp->channel.mc_head->hdw,-1); + pvr2_v4l2_dev_destroy(&vp->video_dev); + + pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_v4l2 id=%p",vp); + pvr2_channel_done(&vp->channel); + kfree(vp); +} + + +void pvr2_v4l2_internal_check(struct pvr2_channel *chp) +{ + struct pvr2_v4l2 *vp; + vp = container_of(chp,struct pvr2_v4l2,channel); + if (!vp->channel.mc_head->disconnect_flag) return; + if (vp->vfirst) return; + pvr2_v4l2_destroy_no_lock(vp); +} + + +int pvr2_v4l2_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + +/* Temporary hack : use ivtv api until a v4l2 one is available. */ +#define IVTV_IOC_G_CODEC 0xFFEE7703 +#define IVTV_IOC_S_CODEC 0xFFEE7704 + if (cmd == IVTV_IOC_G_CODEC || cmd == IVTV_IOC_S_CODEC) return 0; + return video_usercopy(inode, file, cmd, arg, pvr2_v4l2_do_ioctl); +} + + +int pvr2_v4l2_release(struct inode *inode, struct file *file) +{ + struct pvr2_v4l2_fh *fhp = file->private_data; + struct pvr2_v4l2 *vp = fhp->vhead; + struct pvr2_context *mp = fhp->vhead->channel.mc_head; + + pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_release"); + + if (fhp->rhp) { + struct pvr2_stream *sp; + struct pvr2_hdw *hdw; + hdw = fhp->channel.mc_head->hdw; + pvr2_hdw_set_streaming(hdw,0); + sp = pvr2_ioread_get_stream(fhp->rhp); + if (sp) pvr2_stream_set_callback(sp,0,0); + pvr2_ioread_destroy(fhp->rhp); + fhp->rhp = 0; + } + v4l2_prio_close(&vp->prio, &fhp->prio); + file->private_data = NULL; + + pvr2_context_enter(mp); do { + if (fhp->vnext) { + fhp->vnext->vprev = fhp->vprev; + } else { + vp->vlast = fhp->vprev; + } + if (fhp->vprev) { + fhp->vprev->vnext = fhp->vnext; + } else { + vp->vfirst = fhp->vnext; + } + fhp->vnext = 0; + fhp->vprev = 0; + fhp->vhead = 0; + pvr2_channel_done(&fhp->channel); + pvr2_trace(PVR2_TRACE_STRUCT, + "Destroying pvr_v4l2_fh id=%p",fhp); + kfree(fhp); + if (vp->channel.mc_head->disconnect_flag && !vp->vfirst) { + pvr2_v4l2_destroy_no_lock(vp); + } + } while (0); pvr2_context_exit(mp); + return 0; +} + + +int pvr2_v4l2_open(struct inode *inode, struct file *file) +{ + struct pvr2_v4l2_dev *dip = 0; /* Our own context pointer */ + struct pvr2_v4l2_fh *fhp; + struct pvr2_v4l2 *vp; + struct pvr2_hdw *hdw; + + mutex_lock(&device_lock); + /* MCI 7-Jun-2006 Even though we're just doing what amounts to an + atomic read of the device mapping array here, we still need the + mutex. The problem is that there is a tiny race possible when + we register the device. We can't update the device mapping + array until after the device has been registered, owing to the + fact that we can't know the minor device number until after the + registration succeeds. And if another thread tries to open the + device in the window of time after registration but before the + map is updated, then it will get back an erroneous null pointer + and the open will result in a spurious failure. The only way to + prevent that is to (a) be inside the mutex here before we access + the array, and (b) cover the entire registration process later + on with this same mutex. Thus if we get inside the mutex here, + then we can be assured that the registration process actually + completed correctly. This is an unhappy complication from the + use of global data in a driver that lives in a preemptible + environment. It sure would be nice if the video device itself + had a means for storing and retrieving a local context pointer. + Oh wait. It did. But now it's gone. Silly me. */ + { + unsigned int midx = iminor(file->f_dentry->d_inode); + if (midx < sizeof(devices)/sizeof(devices[0])) { + dip = devices[midx]; + } + } + mutex_unlock(&device_lock); + + if (!dip) return -ENODEV; /* Should be impossible but I'm paranoid */ + + vp = dip->v4lp; + hdw = vp->channel.hdw; + + pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_open"); + + if (!pvr2_hdw_dev_ok(hdw)) { + pvr2_trace(PVR2_TRACE_OPEN_CLOSE, + "pvr2_v4l2_open: hardware not ready"); + return -EIO; + } + + fhp = kmalloc(sizeof(*fhp),GFP_KERNEL); + if (!fhp) { + return -ENOMEM; + } + memset(fhp,0,sizeof(*fhp)); + + init_waitqueue_head(&fhp->wait_data); + fhp->dev_info = dip; + + pvr2_context_enter(vp->channel.mc_head); do { + pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr_v4l2_fh id=%p",fhp); + pvr2_channel_init(&fhp->channel,vp->channel.mc_head); + fhp->vnext = 0; + fhp->vprev = vp->vlast; + if (vp->vlast) { + vp->vlast->vnext = fhp; + } else { + vp->vfirst = fhp; + } + vp->vlast = fhp; + fhp->vhead = vp; + } while (0); pvr2_context_exit(vp->channel.mc_head); + + fhp->file = file; + file->private_data = fhp; + v4l2_prio_open(&vp->prio,&fhp->prio); + + fhp->fw_mode_flag = pvr2_hdw_cpufw_get_enabled(hdw); + + return 0; +} + + +static void pvr2_v4l2_notify(struct pvr2_v4l2_fh *fhp) +{ + wake_up(&fhp->wait_data); +} + +static int pvr2_v4l2_iosetup(struct pvr2_v4l2_fh *fh) +{ + int ret; + struct pvr2_stream *sp; + struct pvr2_hdw *hdw; + if (fh->rhp) return 0; + + /* First read() attempt. Try to claim the stream and start + it... */ + if ((ret = pvr2_channel_claim_stream(&fh->channel, + fh->dev_info->stream)) != 0) { + /* Someone else must already have it */ + return ret; + } + + fh->rhp = pvr2_channel_create_mpeg_stream(fh->dev_info->stream); + if (!fh->rhp) { + pvr2_channel_claim_stream(&fh->channel,0); + return -ENOMEM; + } + + hdw = fh->channel.mc_head->hdw; + sp = fh->dev_info->stream->stream; + pvr2_stream_set_callback(sp,(pvr2_stream_callback)pvr2_v4l2_notify,fh); + pvr2_hdw_set_stream_type(hdw,fh->dev_info->config); + pvr2_hdw_set_streaming(hdw,!0); + ret = pvr2_ioread_set_enabled(fh->rhp,!0); + + return ret; +} + + +static ssize_t pvr2_v4l2_read(struct file *file, + char __user *buff, size_t count, loff_t *ppos) +{ + struct pvr2_v4l2_fh *fh = file->private_data; + int ret; + + if (fh->fw_mode_flag) { + struct pvr2_hdw *hdw = fh->channel.mc_head->hdw; + char *tbuf; + int c1,c2; + int tcnt = 0; + unsigned int offs = *ppos; + + tbuf = kmalloc(PAGE_SIZE,GFP_KERNEL); + if (!tbuf) return -ENOMEM; + + while (count) { + c1 = count; + if (c1 > PAGE_SIZE) c1 = PAGE_SIZE; + c2 = pvr2_hdw_cpufw_get(hdw,offs,tbuf,c1); + if (c2 < 0) { + tcnt = c2; + break; + } + if (!c2) break; + if (copy_to_user(buff,tbuf,c2)) { + tcnt = -EFAULT; + break; + } + offs += c2; + tcnt += c2; + buff += c2; + count -= c2; + *ppos += c2; + } + kfree(tbuf); + return tcnt; + } + + if (!fh->rhp) { + ret = pvr2_v4l2_iosetup(fh); + if (ret) { + return ret; + } + } + + for (;;) { + ret = pvr2_ioread_read(fh->rhp,buff,count); + if (ret >= 0) break; + if (ret != -EAGAIN) break; + if (file->f_flags & O_NONBLOCK) break; + /* Doing blocking I/O. Wait here. */ + ret = wait_event_interruptible( + fh->wait_data, + pvr2_ioread_avail(fh->rhp) >= 0); + if (ret < 0) break; + } + + return ret; +} + + +static unsigned int pvr2_v4l2_poll(struct file *file, poll_table *wait) +{ + unsigned int mask = 0; + struct pvr2_v4l2_fh *fh = file->private_data; + int ret; + + if (fh->fw_mode_flag) { + mask |= POLLIN | POLLRDNORM; + return mask; + } + + if (!fh->rhp) { + ret = pvr2_v4l2_iosetup(fh); + if (ret) return POLLERR; + } + + poll_wait(file,&fh->wait_data,wait); + + if (pvr2_ioread_avail(fh->rhp) >= 0) { + mask |= POLLIN | POLLRDNORM; + } + + return mask; +} + + +static struct file_operations vdev_fops = { + .owner = THIS_MODULE, + .open = pvr2_v4l2_open, + .release = pvr2_v4l2_release, + .read = pvr2_v4l2_read, + .ioctl = pvr2_v4l2_ioctl, + .llseek = no_llseek, + .poll = pvr2_v4l2_poll, +}; + + +#define VID_HARDWARE_PVRUSB2 38 /* FIXME : need a good value */ + +static struct video_device vdev_template = { + .owner = THIS_MODULE, + .type = VID_TYPE_CAPTURE | VID_TYPE_TUNER, + .type2 = (V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VBI_CAPTURE + | V4L2_CAP_TUNER | V4L2_CAP_AUDIO + | V4L2_CAP_READWRITE), + .hardware = VID_HARDWARE_PVRUSB2, + .fops = &vdev_fops, +}; + + +static void pvr2_v4l2_dev_init(struct pvr2_v4l2_dev *dip, + struct pvr2_v4l2 *vp, + enum pvr2_config cfg) +{ + int mindevnum; + int unit_number; + int v4l_type; + dip->v4lp = vp; + dip->config = cfg; + + + switch (cfg) { + case pvr2_config_mpeg: + v4l_type = VFL_TYPE_GRABBER; + dip->stream = &vp->channel.mc_head->video_stream; + break; + case pvr2_config_vbi: + v4l_type = VFL_TYPE_VBI; + break; + case pvr2_config_radio: + v4l_type = VFL_TYPE_RADIO; + break; + default: + /* Bail out (this should be impossible) */ + err("Failed to set up pvrusb2 v4l dev" + " due to unrecognized config"); + return; + } + + if (!dip->stream) { + err("Failed to set up pvrusb2 v4l dev" + " due to missing stream instance"); + return; + } + + dip->vdev = video_device_alloc(); + if (!dip->vdev) { + err("Alloc of pvrusb2 v4l video device failed"); + return; + } + + memcpy(dip->vdev,&vdev_template,sizeof(vdev_template)); + dip->vdev->release = video_device_release; + mutex_lock(&device_lock); + + mindevnum = -1; + unit_number = pvr2_hdw_get_unit_number(vp->channel.mc_head->hdw); + if ((unit_number >= 0) && (unit_number < PVR_NUM)) { + mindevnum = video_nr[unit_number]; + } + if ((video_register_device(dip->vdev, v4l_type, mindevnum) < 0) && + (video_register_device(dip->vdev, v4l_type, -1) < 0)) { + err("Failed to register pvrusb2 v4l video device"); + } else { + pvr2_trace(PVR2_TRACE_INIT, + "registered device video%d [%s]", + dip->vdev->minor,pvr2_config_get_name(dip->config)); + } + + if ((dip->vdev->minor < sizeof(devices)/sizeof(devices[0])) && + (devices[dip->vdev->minor] == NULL)) { + dip->ctxt_idx = dip->vdev->minor; + devices[dip->ctxt_idx] = dip; + } + mutex_unlock(&device_lock); + + pvr2_hdw_v4l_store_minor_number(vp->channel.mc_head->hdw, + dip->vdev->minor); +} + + +struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *mnp) +{ + struct pvr2_v4l2 *vp; + + vp = kmalloc(sizeof(*vp),GFP_KERNEL); + if (!vp) return vp; + memset(vp,0,sizeof(*vp)); + vp->video_dev.ctxt_idx = -1; + pvr2_channel_init(&vp->channel,mnp); + pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_v4l2 id=%p",vp); + + vp->channel.check_func = pvr2_v4l2_internal_check; + + /* register streams */ + pvr2_v4l2_dev_init(&vp->video_dev,vp,pvr2_config_mpeg); + + + return vp; +} + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-v4l2.h b/drivers/media/video/pvrusb2/pvrusb2-v4l2.h new file mode 100644 index 00000000000..9a995e2d225 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-v4l2.h @@ -0,0 +1,40 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef __PVRUSB2_V4L2_H +#define __PVRUSB2_V4L2_H + +#include "pvrusb2-context.h" + +struct pvr2_v4l2; + +struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *); + +#endif /* __PVRUSB2_V4L2_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 75 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-video-v4l.c b/drivers/media/video/pvrusb2/pvrusb2-video-v4l.c new file mode 100644 index 00000000000..e4ec7f25194 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-video-v4l.c @@ -0,0 +1,253 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + + This source file is specifically designed to interface with the + saa711x support that is available in the v4l available starting + with linux 2.6.15. + +*/ + +#include "pvrusb2-video-v4l.h" +#include "pvrusb2-i2c-cmd-v4l2.h" + + +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/saa7115.h> +#include <linux/errno.h> +#include <linux/slab.h> + +struct pvr2_v4l_decoder { + struct pvr2_i2c_handler handler; + struct pvr2_decoder_ctrl ctrl; + struct pvr2_i2c_client *client; + struct pvr2_hdw *hdw; + unsigned long stale_mask; +}; + + +static void set_input(struct pvr2_v4l_decoder *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + struct v4l2_routing route; + + pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 set_input(%d)",hdw->input_val); + switch(hdw->input_val) { + case PVR2_CVAL_INPUT_TV: + route.input = SAA7115_COMPOSITE4; + break; + case PVR2_CVAL_INPUT_COMPOSITE: + route.input = SAA7115_COMPOSITE5; + break; + case PVR2_CVAL_INPUT_SVIDEO: + route.input = SAA7115_SVIDEO2; + break; + case PVR2_CVAL_INPUT_RADIO: + // ????? No idea yet what to do here + default: + return; + } + route.output = 0; + pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_S_VIDEO_ROUTING,&route); +} + + +static int check_input(struct pvr2_v4l_decoder *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + return hdw->input_dirty != 0; +} + + +static void set_audio(struct pvr2_v4l_decoder *ctxt) +{ + u32 val; + struct pvr2_hdw *hdw = ctxt->hdw; + + pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 set_audio %d", + hdw->srate_val); + switch (hdw->srate_val) { + default: + case V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000: + val = 48000; + break; + case V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100: + val = 44100; + break; + case V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000: + val = 32000; + break; + } + pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_AUDIO_CLOCK_FREQ,&val); +} + + +static int check_audio(struct pvr2_v4l_decoder *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + return hdw->srate_dirty != 0; +} + + +struct pvr2_v4l_decoder_ops { + void (*update)(struct pvr2_v4l_decoder *); + int (*check)(struct pvr2_v4l_decoder *); +}; + + +static const struct pvr2_v4l_decoder_ops decoder_ops[] = { + { .update = set_input, .check = check_input}, + { .update = set_audio, .check = check_audio}, +}; + + +static void decoder_detach(struct pvr2_v4l_decoder *ctxt) +{ + ctxt->client->handler = 0; + ctxt->hdw->decoder_ctrl = 0; + kfree(ctxt); +} + + +static int decoder_check(struct pvr2_v4l_decoder *ctxt) +{ + unsigned long msk; + unsigned int idx; + + for (idx = 0; idx < sizeof(decoder_ops)/sizeof(decoder_ops[0]); + idx++) { + msk = 1 << idx; + if (ctxt->stale_mask & msk) continue; + if (decoder_ops[idx].check(ctxt)) { + ctxt->stale_mask |= msk; + } + } + return ctxt->stale_mask != 0; +} + + +static void decoder_update(struct pvr2_v4l_decoder *ctxt) +{ + unsigned long msk; + unsigned int idx; + + for (idx = 0; idx < sizeof(decoder_ops)/sizeof(decoder_ops[0]); + idx++) { + msk = 1 << idx; + if (!(ctxt->stale_mask & msk)) continue; + ctxt->stale_mask &= ~msk; + decoder_ops[idx].update(ctxt); + } +} + + +static int decoder_detect(struct pvr2_i2c_client *cp) +{ + /* Attempt to query the decoder - let's see if it will answer */ + struct v4l2_tuner vt; + int ret; + + memset(&vt,0,sizeof(vt)); + ret = pvr2_i2c_client_cmd(cp,VIDIOC_G_TUNER,&vt); + return ret == 0; /* Return true if it answered */ +} + + +static void decoder_enable(struct pvr2_v4l_decoder *ctxt,int fl) +{ + pvr2_trace(PVR2_TRACE_CHIPS,"i2c v4l2 decoder_enable(%d)",fl); + pvr2_v4l2_cmd_stream(ctxt->client,fl); +} + + +static int decoder_is_tuned(struct pvr2_v4l_decoder *ctxt) +{ + struct v4l2_tuner vt; + int ret; + + memset(&vt,0,sizeof(vt)); + ret = pvr2_i2c_client_cmd(ctxt->client,VIDIOC_G_TUNER,&vt); + if (ret < 0) return -EINVAL; + return vt.signal ? 1 : 0; +} + + +static unsigned int decoder_describe(struct pvr2_v4l_decoder *ctxt,char *buf,unsigned int cnt) +{ + return scnprintf(buf,cnt,"handler: pvrusb2-video-v4l"); +} + + +const static struct pvr2_i2c_handler_functions hfuncs = { + .detach = (void (*)(void *))decoder_detach, + .check = (int (*)(void *))decoder_check, + .update = (void (*)(void *))decoder_update, + .describe = (unsigned int (*)(void *,char *,unsigned int))decoder_describe, +}; + + +int pvr2_i2c_decoder_v4l_setup(struct pvr2_hdw *hdw, + struct pvr2_i2c_client *cp) +{ + struct pvr2_v4l_decoder *ctxt; + + if (hdw->decoder_ctrl) return 0; + if (cp->handler) return 0; + if (!decoder_detect(cp)) return 0; + + ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL); + if (!ctxt) return 0; + memset(ctxt,0,sizeof(*ctxt)); + + ctxt->handler.func_data = ctxt; + ctxt->handler.func_table = &hfuncs; + ctxt->ctrl.ctxt = ctxt; + ctxt->ctrl.detach = (void (*)(void *))decoder_detach; + ctxt->ctrl.enable = (void (*)(void *,int))decoder_enable; + ctxt->ctrl.tuned = (int (*)(void *))decoder_is_tuned; + ctxt->client = cp; + ctxt->hdw = hdw; + ctxt->stale_mask = (1 << (sizeof(decoder_ops)/ + sizeof(decoder_ops[0]))) - 1; + hdw->decoder_ctrl = &ctxt->ctrl; + cp->handler = &ctxt->handler; + pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x saa711x V4L2 handler set up", + cp->client->addr); + return !0; +} + + + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-video-v4l.h b/drivers/media/video/pvrusb2/pvrusb2-video-v4l.h new file mode 100644 index 00000000000..2b917fda02e --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-video-v4l.h @@ -0,0 +1,52 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_VIDEO_V4L_H +#define __PVRUSB2_VIDEO_V4L_H + +/* + + This module connects the pvrusb2 driver to the I2C chip level + driver which handles device video processing. This interface is + used internally by the driver; higher level code should only + interact through the interface provided by pvrusb2-hdw.h. + +*/ + + + +#include "pvrusb2-i2c-core.h" + +int pvr2_i2c_decoder_v4l_setup(struct pvr2_hdw *,struct pvr2_i2c_client *); + + +#endif /* __PVRUSB2_VIDEO_V4L_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-wm8775.c b/drivers/media/video/pvrusb2/pvrusb2-wm8775.c new file mode 100644 index 00000000000..fcad346e395 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-wm8775.c @@ -0,0 +1,170 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + + This source file is specifically designed to interface with the + wm8775. + +*/ + +#include "pvrusb2-wm8775.h" +#include "pvrusb2-i2c-cmd-v4l2.h" + + +#include "pvrusb2-hdw-internal.h" +#include "pvrusb2-debug.h" +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <linux/errno.h> +#include <linux/slab.h> + +struct pvr2_v4l_wm8775 { + struct pvr2_i2c_handler handler; + struct pvr2_i2c_client *client; + struct pvr2_hdw *hdw; + unsigned long stale_mask; +}; + + +static void set_input(struct pvr2_v4l_wm8775 *ctxt) +{ + struct v4l2_routing route; + struct pvr2_hdw *hdw = ctxt->hdw; + int msk = 0; + + memset(&route,0,sizeof(route)); + + pvr2_trace(PVR2_TRACE_CHIPS,"i2c wm8775 set_input(val=%d msk=0x%x)", + hdw->input_val,msk); + + // Always point to input #1 no matter what + route.input = 2; + pvr2_i2c_client_cmd(ctxt->client,VIDIOC_INT_S_AUDIO_ROUTING,&route); +} + +static int check_input(struct pvr2_v4l_wm8775 *ctxt) +{ + struct pvr2_hdw *hdw = ctxt->hdw; + return hdw->input_dirty != 0; +} + + +struct pvr2_v4l_wm8775_ops { + void (*update)(struct pvr2_v4l_wm8775 *); + int (*check)(struct pvr2_v4l_wm8775 *); +}; + + +static const struct pvr2_v4l_wm8775_ops wm8775_ops[] = { + { .update = set_input, .check = check_input}, +}; + + +static unsigned int wm8775_describe(struct pvr2_v4l_wm8775 *ctxt, + char *buf,unsigned int cnt) +{ + return scnprintf(buf,cnt,"handler: pvrusb2-wm8775"); +} + + +static void wm8775_detach(struct pvr2_v4l_wm8775 *ctxt) +{ + ctxt->client->handler = 0; + kfree(ctxt); +} + + +static int wm8775_check(struct pvr2_v4l_wm8775 *ctxt) +{ + unsigned long msk; + unsigned int idx; + + for (idx = 0; idx < sizeof(wm8775_ops)/sizeof(wm8775_ops[0]); + idx++) { + msk = 1 << idx; + if (ctxt->stale_mask & msk) continue; + if (wm8775_ops[idx].check(ctxt)) { + ctxt->stale_mask |= msk; + } + } + return ctxt->stale_mask != 0; +} + + +static void wm8775_update(struct pvr2_v4l_wm8775 *ctxt) +{ + unsigned long msk; + unsigned int idx; + + for (idx = 0; idx < sizeof(wm8775_ops)/sizeof(wm8775_ops[0]); + idx++) { + msk = 1 << idx; + if (!(ctxt->stale_mask & msk)) continue; + ctxt->stale_mask &= ~msk; + wm8775_ops[idx].update(ctxt); + } +} + + +const static struct pvr2_i2c_handler_functions hfuncs = { + .detach = (void (*)(void *))wm8775_detach, + .check = (int (*)(void *))wm8775_check, + .update = (void (*)(void *))wm8775_update, + .describe = (unsigned int (*)(void *,char *,unsigned int))wm8775_describe, +}; + + +int pvr2_i2c_wm8775_setup(struct pvr2_hdw *hdw,struct pvr2_i2c_client *cp) +{ + struct pvr2_v4l_wm8775 *ctxt; + + if (cp->handler) return 0; + + ctxt = kmalloc(sizeof(*ctxt),GFP_KERNEL); + if (!ctxt) return 0; + memset(ctxt,0,sizeof(*ctxt)); + + ctxt->handler.func_data = ctxt; + ctxt->handler.func_table = &hfuncs; + ctxt->client = cp; + ctxt->hdw = hdw; + ctxt->stale_mask = (1 << (sizeof(wm8775_ops)/ + sizeof(wm8775_ops[0]))) - 1; + cp->handler = &ctxt->handler; + pvr2_trace(PVR2_TRACE_CHIPS,"i2c 0x%x wm8775 V4L2 handler set up", + cp->client->addr); + return !0; +} + + + + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2-wm8775.h b/drivers/media/video/pvrusb2/pvrusb2-wm8775.h new file mode 100644 index 00000000000..8aaeff4e1e2 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2-wm8775.h @@ -0,0 +1,53 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_WM8775_H +#define __PVRUSB2_WM8775_H + +/* + + This module connects the pvrusb2 driver to the I2C chip level + driver which performs analog -> digital audio conversion for + external audio inputs. This interface is used internally by the + driver; higher level code should only interact through the + interface provided by pvrusb2-hdw.h. + +*/ + + + +#include "pvrusb2-i2c-core.h" + +int pvr2_i2c_wm8775_setup(struct pvr2_hdw *,struct pvr2_i2c_client *); + + +#endif /* __PVRUSB2_WM8775_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/pvrusb2/pvrusb2.h b/drivers/media/video/pvrusb2/pvrusb2.h new file mode 100644 index 00000000000..074533e9c21 --- /dev/null +++ b/drivers/media/video/pvrusb2/pvrusb2.h @@ -0,0 +1,43 @@ +/* + * + * $Id$ + * + * Copyright (C) 2005 Mike Isely <isely@pobox.com> + * Copyright (C) 2004 Aurelien Alleaume <slts@free.fr> + * + * 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 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __PVRUSB2_H +#define __PVRUSB2_H + +/* Maximum number of pvrusb2 instances we can track at once. You + might want to increase this - however the driver operation will not + be impaired if it is too small. Instead additional units just + won't have an ID assigned and it might not be possible to specify + module paramters for those extra units. */ +#define PVR_NUM 20 + +#endif /* __PVRUSB2_H */ + +/* + Stuff for Emacs to see, in order to encourage consistent editing style: + *** Local Variables: *** + *** mode: c *** + *** fill-column: 70 *** + *** tab-width: 8 *** + *** c-basic-offset: 8 *** + *** End: *** + */ diff --git a/drivers/media/video/saa7134/saa6752hs.c b/drivers/media/video/saa7134/saa6752hs.c index de7b9e6e932..afc8f352b8e 100644 --- a/drivers/media/video/saa7134/saa6752hs.c +++ b/drivers/media/video/saa7134/saa6752hs.c @@ -432,10 +432,10 @@ static void saa6752hs_old_set_params(struct i2c_client* client, } static int handle_ctrl(struct saa6752hs_mpeg_params *params, - struct v4l2_ext_control *ctrl, int cmd) + struct v4l2_ext_control *ctrl, unsigned int cmd) { int old = 0, new; - int set = cmd == VIDIOC_S_EXT_CTRLS; + int set = (cmd == VIDIOC_S_EXT_CTRLS); new = ctrl->value; switch (ctrl->id) { diff --git a/drivers/media/video/stradis.c b/drivers/media/video/stradis.c index 6be9c1131e1..c18b31d9928 100644 --- a/drivers/media/video/stradis.c +++ b/drivers/media/video/stradis.c @@ -2190,7 +2190,7 @@ static struct pci_driver stradis_driver = { .remove = __devexit_p(stradis_remove) }; -int __init stradis_init(void) +static int __init stradis_init(void) { int retval; @@ -2203,7 +2203,7 @@ int __init stradis_init(void) return retval; } -void __exit stradis_exit(void) +static void __exit stradis_exit(void) { pci_unregister_driver(&stradis_driver); printk(KERN_INFO "stradis: module cleanup complete\n"); diff --git a/drivers/media/video/tda9887.c b/drivers/media/video/tda9887.c index b6ae969563b..2fadabf9968 100644 --- a/drivers/media/video/tda9887.c +++ b/drivers/media/video/tda9887.c @@ -22,11 +22,11 @@ */ #define tda9887_info(fmt, arg...) do {\ - printk(KERN_INFO "%s %d-%04x (tda9887): " fmt, t->i2c.name, \ + printk(KERN_INFO "%s %d-%04x: " fmt, t->i2c.name, \ i2c_adapter_id(t->i2c.adapter), t->i2c.addr , ##arg); } while (0) #define tda9887_dbg(fmt, arg...) do {\ if (tuner_debug) \ - printk(KERN_INFO "%s %d-%04x (tda9887): " fmt, t->i2c.name, \ + printk(KERN_INFO "%s %d-%04x: " fmt, t->i2c.name, \ i2c_adapter_id(t->i2c.adapter), t->i2c.addr , ##arg); } while (0) @@ -84,8 +84,7 @@ struct tvnorm { #define cAudioGain6 0x80 // bit c7 #define cTopMask 0x1f // bit c0:4 -#define cTopPalSecamDefault 0x14 // bit c0:4 -#define cTopNtscRadioDefault 0x10 // bit c0:4 +#define cTopDefault 0x10 // bit c0:4 //// third reg (e) #define cAudioIF_4_5 0x00 // bit e0:1 @@ -123,7 +122,7 @@ static struct tvnorm tvnorms[] = { cQSS ), .c = ( cDeemphasisON | cDeemphasis50 | - cTopPalSecamDefault), + cTopDefault), .e = ( cGating_36 | cAudioIF_5_5 | cVideoIF_38_90 ), @@ -134,7 +133,7 @@ static struct tvnorm tvnorms[] = { cQSS ), .c = ( cDeemphasisON | cDeemphasis50 | - cTopPalSecamDefault), + cTopDefault), .e = ( cGating_36 | cAudioIF_6_0 | cVideoIF_38_90 ), @@ -145,7 +144,7 @@ static struct tvnorm tvnorms[] = { cQSS ), .c = ( cDeemphasisON | cDeemphasis50 | - cTopPalSecamDefault), + cTopDefault), .e = ( cGating_36 | cAudioIF_6_5 | cVideoIF_38_90 ), @@ -156,7 +155,7 @@ static struct tvnorm tvnorms[] = { cQSS ), .c = ( cDeemphasisON | cDeemphasis75 | - cTopNtscRadioDefault), + cTopDefault), .e = ( cGating_36 | cAudioIF_4_5 | cVideoIF_45_75 ), @@ -165,7 +164,7 @@ static struct tvnorm tvnorms[] = { .name = "SECAM-BGH", .b = ( cPositiveAmTV | cQSS ), - .c = ( cTopPalSecamDefault), + .c = ( cTopDefault), .e = ( cGating_36 | cAudioIF_5_5 | cVideoIF_38_90 ), @@ -174,7 +173,7 @@ static struct tvnorm tvnorms[] = { .name = "SECAM-L", .b = ( cPositiveAmTV | cQSS ), - .c = ( cTopPalSecamDefault), + .c = ( cTopDefault), .e = ( cGating_36 | cAudioIF_6_5 | cVideoIF_38_90 ), @@ -184,7 +183,7 @@ static struct tvnorm tvnorms[] = { .b = ( cOutputPort2Inactive | cPositiveAmTV | cQSS ), - .c = ( cTopPalSecamDefault), + .c = ( cTopDefault), .e = ( cGating_36 | cAudioIF_6_5 | cVideoIF_33_90 ), @@ -195,7 +194,7 @@ static struct tvnorm tvnorms[] = { cQSS ), .c = ( cDeemphasisON | cDeemphasis50 | - cTopPalSecamDefault), + cTopDefault), .e = ( cGating_36 | cAudioIF_6_5 | cVideoIF_38_90 ), @@ -206,7 +205,7 @@ static struct tvnorm tvnorms[] = { cQSS ), .c = ( cDeemphasisON | cDeemphasis75 | - cTopNtscRadioDefault), + cTopDefault), .e = ( cGating_36 | cAudioIF_4_5 | cVideoIF_45_75 ), @@ -217,7 +216,7 @@ static struct tvnorm tvnorms[] = { cQSS ), .c = ( cDeemphasisON | cDeemphasis50 | - cTopNtscRadioDefault), + cTopDefault), .e = ( cGating_36 | cAudioIF_4_5 | cVideoIF_58_75 ), @@ -230,7 +229,7 @@ static struct tvnorm radio_stereo = { cQSS ), .c = ( cDeemphasisOFF | cAudioGain6 | - cTopNtscRadioDefault), + cTopDefault), .e = ( cTunerGainLow | cAudioIF_5_5 | cRadioIF_38_90 ), @@ -242,7 +241,7 @@ static struct tvnorm radio_mono = { cQSS ), .c = ( cDeemphasisON | cDeemphasis75 | - cTopNtscRadioDefault), + cTopDefault), .e = ( cTunerGainLow | cAudioIF_5_5 | cRadioIF_38_90 ), diff --git a/drivers/media/video/tuner-core.c b/drivers/media/video/tuner-core.c index a26ded7d6fa..011413cf34a 100644 --- a/drivers/media/video/tuner-core.c +++ b/drivers/media/video/tuner-core.c @@ -40,7 +40,6 @@ static unsigned int no_autodetect = 0; static unsigned int show_i2c = 0; /* insmod options used at runtime => read/write */ -static unsigned int tuner_debug_old = 0; int tuner_debug = 0; static unsigned int tv_range[2] = { 44, 958 }; @@ -54,8 +53,6 @@ static char ntsc[] = "-"; module_param(addr, int, 0444); module_param(no_autodetect, int, 0444); module_param(show_i2c, int, 0444); -/* Note: tuner_debug is deprecated and will be removed in 2.6.17 */ -module_param_named(tuner_debug,tuner_debug_old, int, 0444); module_param_named(debug,tuner_debug, int, 0644); module_param_string(pal, pal, sizeof(pal), 0644); module_param_string(secam, secam, sizeof(secam), 0644); @@ -442,11 +439,6 @@ static int tuner_attach(struct i2c_adapter *adap, int addr, int kind) t->audmode = V4L2_TUNER_MODE_STEREO; t->mode_mask = T_UNINITIALIZED; t->tuner_status = tuner_status; - if (tuner_debug_old) { - tuner_debug = tuner_debug_old; - printk(KERN_ERR "tuner: tuner_debug is deprecated and will be removed in 2.6.17.\n"); - printk(KERN_ERR "tuner: use the debug option instead.\n"); - } if (show_i2c) { unsigned char buffer[16]; diff --git a/drivers/media/video/v4l2-common.c b/drivers/media/video/v4l2-common.c index f4b3d64ebf7..97f946db859 100644 --- a/drivers/media/video/v4l2-common.c +++ b/drivers/media/video/v4l2-common.c @@ -1103,7 +1103,7 @@ const char **v4l2_ctrl_get_menu(u32 id) }; static const char *mpeg_stream_vbi_fmt[] = { "No VBI", - "VBI in private packets, IVTV format", + "Private packet, IVTV format", NULL }; diff --git a/drivers/message/fusion/mptfc.c b/drivers/message/fusion/mptfc.c index 74714e5bcf0..3ff8378ea66 100644 --- a/drivers/message/fusion/mptfc.c +++ b/drivers/message/fusion/mptfc.c @@ -305,10 +305,8 @@ mptfc_GetFcDevPage0(MPT_ADAPTER *ioc, int ioc_port, } out: - if (pp0_array) - kfree(pp0_array); - if (p0_array) - kfree(p0_array); + kfree(pp0_array); + kfree(p0_array); return rc; } diff --git a/drivers/message/fusion/mptsas.c b/drivers/message/fusion/mptsas.c index af6ec553ff7..85689ab46cb 100644 --- a/drivers/message/fusion/mptsas.c +++ b/drivers/message/fusion/mptsas.c @@ -1378,8 +1378,7 @@ mptsas_probe_hba_phys(MPT_ADAPTER *ioc) return 0; out_free_port_info: - if (hba) - kfree(hba); + kfree(hba); out: return error; } diff --git a/drivers/message/i2o/iop.c b/drivers/message/i2o/iop.c index febbdd4e060..c74e5460f83 100644 --- a/drivers/message/i2o/iop.c +++ b/drivers/message/i2o/iop.c @@ -1239,7 +1239,6 @@ EXPORT_SYMBOL(i2o_cntxt_list_remove); EXPORT_SYMBOL(i2o_cntxt_list_get_ptr); #endif EXPORT_SYMBOL(i2o_msg_get_wait); -EXPORT_SYMBOL(i2o_msg_nop); EXPORT_SYMBOL(i2o_find_iop); EXPORT_SYMBOL(i2o_iop_find_device); EXPORT_SYMBOL(i2o_event_register); diff --git a/drivers/misc/ibmasm/module.c b/drivers/misc/ibmasm/module.c index 1fdf03fd2da..9706cc19134 100644 --- a/drivers/misc/ibmasm/module.c +++ b/drivers/misc/ibmasm/module.c @@ -85,7 +85,7 @@ static int __devinit ibmasm_init_one(struct pci_dev *pdev, const struct pci_devi } memset(sp, 0, sizeof(struct service_processor)); - sp->lock = SPIN_LOCK_UNLOCKED; + spin_lock_init(&sp->lock); INIT_LIST_HEAD(&sp->command_queue); pci_set_drvdata(pdev, (void *)sp); diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c index 0d435814aaa..39edb8250fb 100644 --- a/drivers/mtd/chips/cfi_cmdset_0001.c +++ b/drivers/mtd/chips/cfi_cmdset_0001.c @@ -357,6 +357,7 @@ struct mtd_info *cfi_cmdset_0001(struct map_info *map, int primary) mtd->resume = cfi_intelext_resume; mtd->flags = MTD_CAP_NORFLASH; mtd->name = map->name; + mtd->writesize = 1; mtd->reboot_notifier.notifier_call = cfi_intelext_reboot; diff --git a/drivers/mtd/chips/jedec.c b/drivers/mtd/chips/jedec.c index c40b48dabed..2c3f019197c 100644 --- a/drivers/mtd/chips/jedec.c +++ b/drivers/mtd/chips/jedec.c @@ -256,6 +256,7 @@ static struct mtd_info *jedec_probe(struct map_info *map) MTD->name = map->name; MTD->type = MTD_NORFLASH; MTD->flags = MTD_CAP_NORFLASH; + MTD->writesize = 1; MTD->erasesize = SectorSize*(map->buswidth); // printk("MTD->erasesize is %x\n",(unsigned int)MTD->erasesize); MTD->size = priv->size; diff --git a/drivers/mtd/chips/map_absent.c b/drivers/mtd/chips/map_absent.c index a611de9b151..ac01a949b68 100644 --- a/drivers/mtd/chips/map_absent.c +++ b/drivers/mtd/chips/map_absent.c @@ -64,7 +64,8 @@ static struct mtd_info *map_absent_probe(struct map_info *map) mtd->write = map_absent_write; mtd->sync = map_absent_sync; mtd->flags = 0; - mtd->erasesize = PAGE_SIZE; + mtd->erasesize = PAGE_SIZE; + mtd->writesize = 1; __module_get(THIS_MODULE); return mtd; diff --git a/drivers/mtd/chips/map_ram.c b/drivers/mtd/chips/map_ram.c index 763925747db..3a66680abfd 100644 --- a/drivers/mtd/chips/map_ram.c +++ b/drivers/mtd/chips/map_ram.c @@ -71,6 +71,7 @@ static struct mtd_info *map_ram_probe(struct map_info *map) mtd->write = mapram_write; mtd->sync = mapram_nop; mtd->flags = MTD_CAP_RAM; + mtd->writesize = 1; mtd->erasesize = PAGE_SIZE; while(mtd->size & (mtd->erasesize - 1)) diff --git a/drivers/mtd/chips/map_rom.c b/drivers/mtd/chips/map_rom.c index bc6ee9ef8a3..1b328b1378f 100644 --- a/drivers/mtd/chips/map_rom.c +++ b/drivers/mtd/chips/map_rom.c @@ -47,6 +47,7 @@ static struct mtd_info *map_rom_probe(struct map_info *map) mtd->sync = maprom_nop; mtd->flags = MTD_CAP_ROM; mtd->erasesize = map->size; + mtd->writesize = 1; __module_get(THIS_MODULE); return mtd; diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c index 0d98c223c5f..be3f1c136d0 100644 --- a/drivers/mtd/devices/block2mtd.c +++ b/drivers/mtd/devices/block2mtd.c @@ -324,6 +324,7 @@ static struct block2mtd_dev *add_device(char *devname, int erase_size) dev->mtd.size = dev->blkdev->bd_inode->i_size & PAGE_MASK; dev->mtd.erasesize = erase_size; + dev->mtd.writesize = 1; dev->mtd.type = MTD_RAM; dev->mtd.flags = MTD_CAP_RAM; dev->mtd.erase = block2mtd_erase; diff --git a/drivers/mtd/devices/ms02-nv.c b/drivers/mtd/devices/ms02-nv.c index 4ab7670770e..08dfb899b27 100644 --- a/drivers/mtd/devices/ms02-nv.c +++ b/drivers/mtd/devices/ms02-nv.c @@ -225,6 +225,7 @@ static int __init ms02nv_init_one(ulong addr) mtd->owner = THIS_MODULE; mtd->read = ms02nv_read; mtd->write = ms02nv_write; + mtd->writesize = 1; ret = -EIO; if (add_mtd_device(mtd)) { diff --git a/drivers/mtd/devices/mtd_dataflash.c b/drivers/mtd/devices/mtd_dataflash.c index a19480d0788..04271d02b6b 100644 --- a/drivers/mtd/devices/mtd_dataflash.c +++ b/drivers/mtd/devices/mtd_dataflash.c @@ -478,6 +478,7 @@ add_dataflash(struct spi_device *spi, char *name, device->name = (pdata && pdata->name) ? pdata->name : priv->name; device->size = nr_pages * pagesize; device->erasesize = pagesize; + device->writesize = pagesize; device->owner = THIS_MODULE; device->type = MTD_DATAFLASH; device->flags = MTD_CAP_NORFLASH; diff --git a/drivers/mtd/devices/phram.c b/drivers/mtd/devices/phram.c index e09e416667d..6c7337f9ebb 100644 --- a/drivers/mtd/devices/phram.c +++ b/drivers/mtd/devices/phram.c @@ -151,6 +151,7 @@ static int register_device(char *name, unsigned long start, unsigned long len) new->mtd.owner = THIS_MODULE; new->mtd.type = MTD_RAM; new->mtd.erasesize = PAGE_SIZE; + new->mtd.writesize = 1; ret = -EAGAIN; if (add_mtd_device(&new->mtd)) { diff --git a/drivers/mtd/devices/pmc551.c b/drivers/mtd/devices/pmc551.c index 666cce1bf60..f620d74f100 100644 --- a/drivers/mtd/devices/pmc551.c +++ b/drivers/mtd/devices/pmc551.c @@ -778,7 +778,8 @@ static int __init init_pmc551(void) mtd->type = MTD_RAM; mtd->name = "PMC551 RAM board"; mtd->erasesize = 0x10000; - mtd->owner = THIS_MODULE; + mtd->writesize = 1; + mtd->owner = THIS_MODULE; if (add_mtd_device(mtd)) { printk(KERN_NOTICE "pmc551: Failed to register new device\n"); diff --git a/drivers/mtd/devices/slram.c b/drivers/mtd/devices/slram.c index b3f665e3c38..542a0c00900 100644 --- a/drivers/mtd/devices/slram.c +++ b/drivers/mtd/devices/slram.c @@ -209,6 +209,7 @@ static int register_device(char *name, unsigned long start, unsigned long length (*curmtd)->mtdinfo->owner = THIS_MODULE; (*curmtd)->mtdinfo->type = MTD_RAM; (*curmtd)->mtdinfo->erasesize = SLRAM_BLK_SZ; + (*curmtd)->mtdinfo->writesize = 1; if (add_mtd_device((*curmtd)->mtdinfo)) { E("slram: Failed to register new device\n"); diff --git a/drivers/mtd/maps/ixp2000.c b/drivers/mtd/maps/ixp2000.c index 2c9cc7f37e9..c26488a1793 100644 --- a/drivers/mtd/maps/ixp2000.c +++ b/drivers/mtd/maps/ixp2000.c @@ -42,7 +42,6 @@ struct ixp2000_flash_info { struct map_info map; struct mtd_partition *partitions; struct resource *res; - int nr_banks; }; static inline unsigned long flash_bank_setup(struct map_info *map, unsigned long ofs) @@ -183,7 +182,6 @@ static int ixp2000_flash_probe(struct platform_device *dev) */ info->map.phys = NO_XIP; - info->nr_banks = ixp_data->nr_banks; info->map.size = ixp_data->nr_banks * window_size; info->map.bankwidth = 1; diff --git a/drivers/mtd/maps/physmap.c b/drivers/mtd/maps/physmap.c index 433c3cac3ca..d6301f08906 100644 --- a/drivers/mtd/maps/physmap.c +++ b/drivers/mtd/maps/physmap.c @@ -182,7 +182,7 @@ static struct physmap_flash_data physmap_flash_data = { static struct resource physmap_flash_resource = { .start = CONFIG_MTD_PHYSMAP_START, - .end = CONFIG_MTD_PHYSMAP_START + CONFIG_MTD_PHYSMAP_LEN, + .end = CONFIG_MTD_PHYSMAP_START + CONFIG_MTD_PHYSMAP_LEN - 1, .flags = IORESOURCE_MEM, }; diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c index aa18d45b264..9a4b59d9252 100644 --- a/drivers/mtd/mtdchar.c +++ b/drivers/mtd/mtdchar.c @@ -78,7 +78,7 @@ static loff_t mtd_lseek (struct file *file, loff_t offset, int orig) return -EINVAL; } - if (offset >= 0 && offset < mtd->size) + if (offset >= 0 && offset <= mtd->size) return file->f_pos = offset; return -EINVAL; diff --git a/drivers/mtd/nand/nand_base.c b/drivers/mtd/nand/nand_base.c index 27083ed0a01..80a76654d96 100644 --- a/drivers/mtd/nand/nand_base.c +++ b/drivers/mtd/nand/nand_base.c @@ -1176,7 +1176,7 @@ static int nand_write_oob_std(struct mtd_info *mtd, struct nand_chip *chip, status = chip->waitfunc(mtd, chip); - return status; + return status & NAND_STATUS_FAIL ? -EIO : 0; } /** @@ -1271,10 +1271,6 @@ static int nand_do_read_oob(struct mtd_info *mtd, loff_t from, sndcmd = chip->ecc.read_oob(mtd, chip, page, sndcmd); buf = nand_transfer_oob(chip, buf, ops); - readlen -= ops->ooblen; - if (!readlen) - break; - if (!(chip->options & NAND_NO_READRDY)) { /* * Apply delay or wait for ready/busy pin. Do this @@ -1288,6 +1284,10 @@ static int nand_do_read_oob(struct mtd_info *mtd, loff_t from, nand_wait_ready(mtd); } + readlen -= ops->ooblen; + if (!readlen) + break; + /* Increment page address */ realpage++; @@ -1610,13 +1610,13 @@ static int nand_do_write_ops(struct mtd_info *mtd, loff_t to, if (!writelen) return 0; + chipnr = (int)(to >> chip->chip_shift); + chip->select_chip(mtd, chipnr); + /* Check, if it is write protected */ if (nand_check_wp(mtd)) return -EIO; - chipnr = (int)(to >> chip->chip_shift); - chip->select_chip(mtd, chipnr); - realpage = (int)(to >> chip->page_shift); page = realpage & chip->pagemask; blockmask = (1 << (chip->phys_erase_shift - chip->page_shift)) - 1; diff --git a/drivers/mtd/nand/ndfc.c b/drivers/mtd/nand/ndfc.c index fe8d38514ba..e5bd88f2d56 100644 --- a/drivers/mtd/nand/ndfc.c +++ b/drivers/mtd/nand/ndfc.c @@ -61,15 +61,15 @@ static void ndfc_select_chip(struct mtd_info *mtd, int chip) static void ndfc_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) { - struct nand_chip *chip = mtd->priv; + struct ndfc_controller *ndfc = &ndfc_ctrl; if (cmd == NAND_CMD_NONE) return; if (ctrl & NAND_CLE) - writel(cmd & 0xFF, chip->IO_ADDR_W + NDFC_CMD); + writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_CMD); else - writel(cmd & 0xFF, chip->IO_ADDR_W + NDFC_ALE); + writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_ALE); } static int ndfc_ready(struct mtd_info *mtd) diff --git a/drivers/mtd/nand/s3c2410.c b/drivers/mtd/nand/s3c2410.c index 2c262fe03d8..ff5cef24d5b 100644 --- a/drivers/mtd/nand/s3c2410.c +++ b/drivers/mtd/nand/s3c2410.c @@ -63,8 +63,6 @@ #include <asm/arch/regs-nand.h> #include <asm/arch/nand.h> -#define PFX "s3c2410-nand: " - #ifdef CONFIG_MTD_NAND_S3C2410_HWECC static int hardware_ecc = 1; #else @@ -99,6 +97,12 @@ struct s3c2410_nand_mtd { int scan_res; }; +enum s3c_cpu_type { + TYPE_S3C2410, + TYPE_S3C2412, + TYPE_S3C2440, +}; + /* overview of the s3c2410 nand state */ struct s3c2410_nand_info { @@ -112,9 +116,11 @@ struct s3c2410_nand_info { struct resource *area; struct clk *clk; void __iomem *regs; + void __iomem *sel_reg; + int sel_bit; int mtd_count; - unsigned char is_s3c2440; + enum s3c_cpu_type cpu_type; }; /* conversion functions */ @@ -148,7 +154,7 @@ static inline int allow_clk_stop(struct s3c2410_nand_info *info) #define NS_IN_KHZ 1000000 -static int s3c2410_nand_calc_rate(int wanted, unsigned long clk, int max) +static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max) { int result; @@ -172,53 +178,58 @@ static int s3c2410_nand_calc_rate(int wanted, unsigned long clk, int max) /* controller setup */ -static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, struct platform_device *pdev) +static int s3c2410_nand_inithw(struct s3c2410_nand_info *info, + struct platform_device *pdev) { struct s3c2410_platform_nand *plat = to_nand_plat(pdev); unsigned long clkrate = clk_get_rate(info->clk); + int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4; int tacls, twrph0, twrph1; - unsigned long cfg; + unsigned long cfg = 0; /* calculate the timing information for the controller */ clkrate /= 1000; /* turn clock into kHz for ease of use */ if (plat != NULL) { - tacls = s3c2410_nand_calc_rate(plat->tacls, clkrate, 4); - twrph0 = s3c2410_nand_calc_rate(plat->twrph0, clkrate, 8); - twrph1 = s3c2410_nand_calc_rate(plat->twrph1, clkrate, 8); + tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max); + twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8); + twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8); } else { /* default timings */ - tacls = 4; + tacls = tacls_max; twrph0 = 8; twrph1 = 8; } if (tacls < 0 || twrph0 < 0 || twrph1 < 0) { - printk(KERN_ERR PFX "cannot get timings suitable for board\n"); + dev_err(info->device, "cannot get suitable timings\n"); return -EINVAL; } - printk(KERN_INFO PFX "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n", + dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n", tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate), twrph1, to_ns(twrph1, clkrate)); - if (!info->is_s3c2440) { + switch (info->cpu_type) { + case TYPE_S3C2410: cfg = S3C2410_NFCONF_EN; cfg |= S3C2410_NFCONF_TACLS(tacls - 1); cfg |= S3C2410_NFCONF_TWRPH0(twrph0 - 1); cfg |= S3C2410_NFCONF_TWRPH1(twrph1 - 1); - } else { + break; + + case TYPE_S3C2440: + case TYPE_S3C2412: cfg = S3C2440_NFCONF_TACLS(tacls - 1); cfg |= S3C2440_NFCONF_TWRPH0(twrph0 - 1); cfg |= S3C2440_NFCONF_TWRPH1(twrph1 - 1); /* enable the controller and de-assert nFCE */ - writel(S3C2440_NFCONT_ENABLE | S3C2440_NFCONT_ENABLE, - info->regs + S3C2440_NFCONT); + writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT); } - pr_debug(PFX "NF_CONF is 0x%lx\n", cfg); + dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg); writel(cfg, info->regs + S3C2410_NFCONF); return 0; @@ -231,26 +242,21 @@ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip) struct s3c2410_nand_info *info; struct s3c2410_nand_mtd *nmtd; struct nand_chip *this = mtd->priv; - void __iomem *reg; unsigned long cur; - unsigned long bit; nmtd = this->priv; info = nmtd->info; - bit = (info->is_s3c2440) ? S3C2440_NFCONT_nFCE : S3C2410_NFCONF_nFCE; - reg = info->regs + ((info->is_s3c2440) ? S3C2440_NFCONT : S3C2410_NFCONF); - if (chip != -1 && allow_clk_stop(info)) clk_enable(info->clk); - cur = readl(reg); + cur = readl(info->sel_reg); if (chip == -1) { - cur |= bit; + cur |= info->sel_bit; } else { if (nmtd->set != NULL && chip > nmtd->set->nr_chips) { - printk(KERN_ERR PFX "chip %d out of range\n", chip); + dev_err(info->device, "invalid chip %d\n", chip); return; } @@ -259,10 +265,10 @@ static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip) (info->platform->select_chip) (nmtd->set, chip); } - cur &= ~bit; + cur &= ~info->sel_bit; } - writel(cur, reg); + writel(cur, info->sel_reg); if (chip == -1 && allow_clk_stop(info)) clk_disable(info->clk); @@ -311,15 +317,25 @@ static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd, static int s3c2410_nand_devready(struct mtd_info *mtd) { struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); - - if (info->is_s3c2440) - return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY; return readb(info->regs + S3C2410_NFSTAT) & S3C2410_NFSTAT_BUSY; } +static int s3c2440_nand_devready(struct mtd_info *mtd) +{ + struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); + return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY; +} + +static int s3c2412_nand_devready(struct mtd_info *mtd) +{ + struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd); + return readb(info->regs + S3C2412_NFSTAT) & S3C2412_NFSTAT_READY; +} + /* ECC handling functions */ -static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc) +static int s3c2410_nand_correct_data(struct mtd_info *mtd, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) { pr_debug("s3c2410_nand_correct_data(%p,%p,%p,%p)\n", mtd, dat, read_ecc, calc_ecc); @@ -487,11 +503,8 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, struct s3c2410_nand_set *set) { struct nand_chip *chip = &nmtd->chip; + void __iomem *regs = info->regs; - chip->IO_ADDR_R = info->regs + S3C2410_NFDATA; - chip->IO_ADDR_W = info->regs + S3C2410_NFDATA; - chip->cmd_ctrl = s3c2410_nand_hwcontrol; - chip->dev_ready = s3c2410_nand_devready; chip->write_buf = s3c2410_nand_write_buf; chip->read_buf = s3c2410_nand_read_buf; chip->select_chip = s3c2410_nand_select_chip; @@ -500,11 +513,37 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, chip->options = 0; chip->controller = &info->controller; - if (info->is_s3c2440) { - chip->IO_ADDR_R = info->regs + S3C2440_NFDATA; - chip->IO_ADDR_W = info->regs + S3C2440_NFDATA; - chip->cmd_ctrl = s3c2440_nand_hwcontrol; - } + switch (info->cpu_type) { + case TYPE_S3C2410: + chip->IO_ADDR_W = regs + S3C2410_NFDATA; + info->sel_reg = regs + S3C2410_NFCONF; + info->sel_bit = S3C2410_NFCONF_nFCE; + chip->cmd_ctrl = s3c2410_nand_hwcontrol; + chip->dev_ready = s3c2410_nand_devready; + break; + + case TYPE_S3C2440: + chip->IO_ADDR_W = regs + S3C2440_NFDATA; + info->sel_reg = regs + S3C2440_NFCONT; + info->sel_bit = S3C2440_NFCONT_nFCE; + chip->cmd_ctrl = s3c2440_nand_hwcontrol; + chip->dev_ready = s3c2440_nand_devready; + break; + + case TYPE_S3C2412: + chip->IO_ADDR_W = regs + S3C2440_NFDATA; + info->sel_reg = regs + S3C2440_NFCONT; + info->sel_bit = S3C2412_NFCONT_nFCE0; + chip->cmd_ctrl = s3c2440_nand_hwcontrol; + chip->dev_ready = s3c2412_nand_devready; + + if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT) + dev_info(info->device, "System booted from NAND\n"); + + break; + } + + chip->IO_ADDR_R = chip->IO_ADDR_W; nmtd->info = info; nmtd->mtd.priv = chip; @@ -512,17 +551,25 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, nmtd->set = set; if (hardware_ecc) { - chip->ecc.correct = s3c2410_nand_correct_data; - chip->ecc.hwctl = s3c2410_nand_enable_hwecc; chip->ecc.calculate = s3c2410_nand_calculate_ecc; + chip->ecc.correct = s3c2410_nand_correct_data; chip->ecc.mode = NAND_ECC_HW; chip->ecc.size = 512; chip->ecc.bytes = 3; chip->ecc.layout = &nand_hw_eccoob; - if (info->is_s3c2440) { - chip->ecc.hwctl = s3c2440_nand_enable_hwecc; - chip->ecc.calculate = s3c2440_nand_calculate_ecc; + switch (info->cpu_type) { + case TYPE_S3C2410: + chip->ecc.hwctl = s3c2410_nand_enable_hwecc; + chip->ecc.calculate = s3c2410_nand_calculate_ecc; + break; + + case TYPE_S3C2412: + case TYPE_S3C2440: + chip->ecc.hwctl = s3c2440_nand_enable_hwecc; + chip->ecc.calculate = s3c2440_nand_calculate_ecc; + break; + } } else { chip->ecc.mode = NAND_ECC_SOFT; @@ -537,7 +584,8 @@ static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info, * nand layer to look for devices */ -static int s3c24xx_nand_probe(struct platform_device *pdev, int is_s3c2440) +static int s3c24xx_nand_probe(struct platform_device *pdev, + enum s3c_cpu_type cpu_type) { struct s3c2410_platform_nand *plat = to_nand_plat(pdev); struct s3c2410_nand_info *info; @@ -592,7 +640,7 @@ static int s3c24xx_nand_probe(struct platform_device *pdev, int is_s3c2440) info->device = &pdev->dev; info->platform = plat; info->regs = ioremap(res->start, size); - info->is_s3c2440 = is_s3c2440; + info->cpu_type = cpu_type; if (info->regs == NULL) { dev_err(&pdev->dev, "cannot reserve register region\n"); @@ -699,12 +747,17 @@ static int s3c24xx_nand_resume(struct platform_device *dev) static int s3c2410_nand_probe(struct platform_device *dev) { - return s3c24xx_nand_probe(dev, 0); + return s3c24xx_nand_probe(dev, TYPE_S3C2410); } static int s3c2440_nand_probe(struct platform_device *dev) { - return s3c24xx_nand_probe(dev, 1); + return s3c24xx_nand_probe(dev, TYPE_S3C2440); +} + +static int s3c2412_nand_probe(struct platform_device *dev) +{ + return s3c24xx_nand_probe(dev, TYPE_S3C2412); } static struct platform_driver s3c2410_nand_driver = { @@ -729,16 +782,29 @@ static struct platform_driver s3c2440_nand_driver = { }, }; +static struct platform_driver s3c2412_nand_driver = { + .probe = s3c2412_nand_probe, + .remove = s3c2410_nand_remove, + .suspend = s3c24xx_nand_suspend, + .resume = s3c24xx_nand_resume, + .driver = { + .name = "s3c2412-nand", + .owner = THIS_MODULE, + }, +}; + static int __init s3c2410_nand_init(void) { printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n"); + platform_driver_register(&s3c2412_nand_driver); platform_driver_register(&s3c2440_nand_driver); return platform_driver_register(&s3c2410_nand_driver); } static void __exit s3c2410_nand_exit(void) { + platform_driver_unregister(&s3c2412_nand_driver); platform_driver_unregister(&s3c2440_nand_driver); platform_driver_unregister(&s3c2410_nand_driver); } diff --git a/drivers/mtd/nand/ts7250.c b/drivers/mtd/nand/ts7250.c index a0b4b1edcb0..f40081069ab 100644 --- a/drivers/mtd/nand/ts7250.c +++ b/drivers/mtd/nand/ts7250.c @@ -97,7 +97,7 @@ static void ts7250_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl) unsigned long addr = TS72XX_NAND_CONTROL_VIRT_BASE; unsigned char bits; - bits = (ctrl & NAND_CNE) << 2; + bits = (ctrl & NAND_NCE) << 2; bits |= ctrl & NAND_CLE; bits |= (ctrl & NAND_ALE) >> 2; diff --git a/drivers/net/fec.c b/drivers/net/fec.c index bd6983d1afb..db694c83298 100644 --- a/drivers/net/fec.c +++ b/drivers/net/fec.c @@ -22,7 +22,7 @@ * Copyright (c) 2001-2005 Greg Ungerer (gerg@snapgear.com) * * Bug fixes and cleanup by Philippe De Muyter (phdm@macqel.be) - * Copyright (c) 2004-2005 Macq Electronique SA. + * Copyright (c) 2004-2006 Macq Electronique SA. */ #include <linux/config.h> @@ -51,7 +51,7 @@ #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || \ defined(CONFIG_M5272) || defined(CONFIG_M528x) || \ - defined(CONFIG_M520x) + defined(CONFIG_M520x) || defined(CONFIG_M532x) #include <asm/coldfire.h> #include <asm/mcfsim.h> #include "fec.h" @@ -80,6 +80,8 @@ static unsigned int fec_hw[] = { (MCF_MBAR + 0x1000), #elif defined(CONFIG_M520x) (MCF_MBAR+0x30000), +#elif defined(CONFIG_M532x) + (MCF_MBAR+0xfc030000), #else &(((immap_t *)IMAP_ADDR)->im_cpm.cp_fec), #endif @@ -143,7 +145,7 @@ typedef struct { #define TX_RING_MOD_MASK 15 /* for this to work */ #if (((RX_RING_SIZE + TX_RING_SIZE) * 8) > PAGE_SIZE) -#error "FEC: descriptor ring size contants too large" +#error "FEC: descriptor ring size constants too large" #endif /* Interrupt events/masks. @@ -167,12 +169,12 @@ typedef struct { /* - * The 5270/5271/5280/5282 RX control register also contains maximum frame + * The 5270/5271/5280/5282/532x RX control register also contains maximum frame * size bits. Other FEC hardware does not, so we need to take that into * account when setting it. */ #if defined(CONFIG_M523x) || defined(CONFIG_M527x) || defined(CONFIG_M528x) || \ - defined(CONFIG_M520x) + defined(CONFIG_M520x) || defined(CONFIG_M532x) #define OPT_FRAME_SIZE (PKT_MAXBUF_SIZE << 16) #else #define OPT_FRAME_SIZE 0 @@ -308,6 +310,7 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) struct fec_enet_private *fep; volatile fec_t *fecp; volatile cbd_t *bdp; + unsigned short status; fep = netdev_priv(dev); fecp = (volatile fec_t*)dev->base_addr; @@ -320,8 +323,9 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) /* Fill in a Tx ring entry */ bdp = fep->cur_tx; + status = bdp->cbd_sc; #ifndef final_version - if (bdp->cbd_sc & BD_ENET_TX_READY) { + if (status & BD_ENET_TX_READY) { /* Ooops. All transmit buffers are full. Bail out. * This should not happen, since dev->tbusy should be set. */ @@ -332,7 +336,7 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) /* Clear all of the status flags. */ - bdp->cbd_sc &= ~BD_ENET_TX_STATS; + status &= ~BD_ENET_TX_STATS; /* Set buffer length and buffer pointer. */ @@ -366,21 +370,22 @@ fec_enet_start_xmit(struct sk_buff *skb, struct net_device *dev) spin_lock_irq(&fep->lock); - /* Send it on its way. Tell FEC its ready, interrupt when done, - * its the last BD of the frame, and to put the CRC on the end. + /* Send it on its way. Tell FEC it's ready, interrupt when done, + * it's the last BD of the frame, and to put the CRC on the end. */ - bdp->cbd_sc |= (BD_ENET_TX_READY | BD_ENET_TX_INTR + status |= (BD_ENET_TX_READY | BD_ENET_TX_INTR | BD_ENET_TX_LAST | BD_ENET_TX_TC); + bdp->cbd_sc = status; dev->trans_start = jiffies; /* Trigger transmission start */ - fecp->fec_x_des_active = 0x01000000; + fecp->fec_x_des_active = 0; /* If this was the last BD in the ring, start at the beginning again. */ - if (bdp->cbd_sc & BD_ENET_TX_WRAP) { + if (status & BD_ENET_TX_WRAP) { bdp = fep->tx_bd_base; } else { bdp++; @@ -491,43 +496,44 @@ fec_enet_tx(struct net_device *dev) { struct fec_enet_private *fep; volatile cbd_t *bdp; + unsigned short status; struct sk_buff *skb; fep = netdev_priv(dev); spin_lock(&fep->lock); bdp = fep->dirty_tx; - while ((bdp->cbd_sc&BD_ENET_TX_READY) == 0) { + while (((status = bdp->cbd_sc) & BD_ENET_TX_READY) == 0) { if (bdp == fep->cur_tx && fep->tx_full == 0) break; skb = fep->tx_skbuff[fep->skb_dirty]; /* Check for errors. */ - if (bdp->cbd_sc & (BD_ENET_TX_HB | BD_ENET_TX_LC | + if (status & (BD_ENET_TX_HB | BD_ENET_TX_LC | BD_ENET_TX_RL | BD_ENET_TX_UN | BD_ENET_TX_CSL)) { fep->stats.tx_errors++; - if (bdp->cbd_sc & BD_ENET_TX_HB) /* No heartbeat */ + if (status & BD_ENET_TX_HB) /* No heartbeat */ fep->stats.tx_heartbeat_errors++; - if (bdp->cbd_sc & BD_ENET_TX_LC) /* Late collision */ + if (status & BD_ENET_TX_LC) /* Late collision */ fep->stats.tx_window_errors++; - if (bdp->cbd_sc & BD_ENET_TX_RL) /* Retrans limit */ + if (status & BD_ENET_TX_RL) /* Retrans limit */ fep->stats.tx_aborted_errors++; - if (bdp->cbd_sc & BD_ENET_TX_UN) /* Underrun */ + if (status & BD_ENET_TX_UN) /* Underrun */ fep->stats.tx_fifo_errors++; - if (bdp->cbd_sc & BD_ENET_TX_CSL) /* Carrier lost */ + if (status & BD_ENET_TX_CSL) /* Carrier lost */ fep->stats.tx_carrier_errors++; } else { fep->stats.tx_packets++; } #ifndef final_version - if (bdp->cbd_sc & BD_ENET_TX_READY) + if (status & BD_ENET_TX_READY) printk("HEY! Enet xmit interrupt and TX_READY.\n"); #endif /* Deferred means some collisions occurred during transmit, * but we eventually sent the packet OK. */ - if (bdp->cbd_sc & BD_ENET_TX_DEF) + if (status & BD_ENET_TX_DEF) fep->stats.collisions++; /* Free the sk buffer associated with this last transmit. @@ -538,7 +544,7 @@ fec_enet_tx(struct net_device *dev) /* Update pointer to next buffer descriptor to be transmitted. */ - if (bdp->cbd_sc & BD_ENET_TX_WRAP) + if (status & BD_ENET_TX_WRAP) bdp = fep->tx_bd_base; else bdp++; @@ -568,9 +574,14 @@ fec_enet_rx(struct net_device *dev) struct fec_enet_private *fep; volatile fec_t *fecp; volatile cbd_t *bdp; + unsigned short status; struct sk_buff *skb; ushort pkt_len; __u8 *data; + +#ifdef CONFIG_M532x + flush_cache_all(); +#endif fep = netdev_priv(dev); fecp = (volatile fec_t*)dev->base_addr; @@ -580,13 +591,13 @@ fec_enet_rx(struct net_device *dev) */ bdp = fep->cur_rx; -while (!(bdp->cbd_sc & BD_ENET_RX_EMPTY)) { +while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) { #ifndef final_version /* Since we have allocated space to hold a complete frame, * the last indicator should be set. */ - if ((bdp->cbd_sc & BD_ENET_RX_LAST) == 0) + if ((status & BD_ENET_RX_LAST) == 0) printk("FEC ENET: rcv is not +last\n"); #endif @@ -594,26 +605,26 @@ while (!(bdp->cbd_sc & BD_ENET_RX_EMPTY)) { goto rx_processing_done; /* Check for errors. */ - if (bdp->cbd_sc & (BD_ENET_RX_LG | BD_ENET_RX_SH | BD_ENET_RX_NO | + if (status & (BD_ENET_RX_LG | BD_ENET_RX_SH | BD_ENET_RX_NO | BD_ENET_RX_CR | BD_ENET_RX_OV)) { fep->stats.rx_errors++; - if (bdp->cbd_sc & (BD_ENET_RX_LG | BD_ENET_RX_SH)) { + if (status & (BD_ENET_RX_LG | BD_ENET_RX_SH)) { /* Frame too long or too short. */ fep->stats.rx_length_errors++; } - if (bdp->cbd_sc & BD_ENET_RX_NO) /* Frame alignment */ + if (status & BD_ENET_RX_NO) /* Frame alignment */ fep->stats.rx_frame_errors++; - if (bdp->cbd_sc & BD_ENET_RX_CR) /* CRC Error */ - fep->stats.rx_crc_errors++; - if (bdp->cbd_sc & BD_ENET_RX_OV) /* FIFO overrun */ + if (status & BD_ENET_RX_CR) /* CRC Error */ fep->stats.rx_crc_errors++; + if (status & BD_ENET_RX_OV) /* FIFO overrun */ + fep->stats.rx_fifo_errors++; } /* Report late collisions as a frame error. * On this error, the BD is closed, but we don't know what we * have in the buffer. So, just drop this frame on the floor. */ - if (bdp->cbd_sc & BD_ENET_RX_CL) { + if (status & BD_ENET_RX_CL) { fep->stats.rx_errors++; fep->stats.rx_frame_errors++; goto rx_processing_done; @@ -639,9 +650,7 @@ while (!(bdp->cbd_sc & BD_ENET_RX_EMPTY)) { } else { skb->dev = dev; skb_put(skb,pkt_len-4); /* Make room */ - eth_copy_and_sum(skb, - (unsigned char *)__va(bdp->cbd_bufaddr), - pkt_len-4, 0); + eth_copy_and_sum(skb, data, pkt_len-4, 0); skb->protocol=eth_type_trans(skb,dev); netif_rx(skb); } @@ -649,15 +658,16 @@ while (!(bdp->cbd_sc & BD_ENET_RX_EMPTY)) { /* Clear the status flags for this buffer. */ - bdp->cbd_sc &= ~BD_ENET_RX_STATS; + status &= ~BD_ENET_RX_STATS; /* Mark the buffer empty. */ - bdp->cbd_sc |= BD_ENET_RX_EMPTY; + status |= BD_ENET_RX_EMPTY; + bdp->cbd_sc = status; /* Update BD pointer to next entry. */ - if (bdp->cbd_sc & BD_ENET_RX_WRAP) + if (status & BD_ENET_RX_WRAP) bdp = fep->rx_bd_base; else bdp++; @@ -667,9 +677,9 @@ while (!(bdp->cbd_sc & BD_ENET_RX_EMPTY)) { * incoming frames. On a heavily loaded network, we should be * able to keep up at the expense of system resources. */ - fecp->fec_r_des_active = 0x01000000; + fecp->fec_r_des_active = 0; #endif - } /* while (!(bdp->cbd_sc & BD_ENET_RX_EMPTY)) */ + } /* while (!((status = bdp->cbd_sc) & BD_ENET_RX_EMPTY)) */ fep->cur_rx = (cbd_t *)bdp; #if 0 @@ -680,11 +690,12 @@ while (!(bdp->cbd_sc & BD_ENET_RX_EMPTY)) { * our way back to the interrupt return only to come right back * here. */ - fecp->fec_r_des_active = 0x01000000; + fecp->fec_r_des_active = 0; #endif } +/* called from interrupt context */ static void fec_enet_mii(struct net_device *dev) { @@ -696,10 +707,12 @@ fec_enet_mii(struct net_device *dev) fep = netdev_priv(dev); ep = fep->hwp; mii_reg = ep->fec_mii_data; + + spin_lock(&fep->lock); if ((mip = mii_head) == NULL) { printk("MII and no head!\n"); - return; + goto unlock; } if (mip->mii_func != NULL) @@ -711,6 +724,9 @@ fec_enet_mii(struct net_device *dev) if ((mip = mii_head) != NULL) ep->fec_mii_data = mip->mii_regval; + +unlock: + spin_unlock(&fep->lock); } static int @@ -728,8 +744,7 @@ mii_queue(struct net_device *dev, int regval, void (*func)(uint, struct net_devi retval = 0; - save_flags(flags); - cli(); + spin_lock_irqsave(&fep->lock,flags); if ((mip = mii_free) != NULL) { mii_free = mip->mii_next; @@ -749,7 +764,7 @@ mii_queue(struct net_device *dev, int regval, void (*func)(uint, struct net_devi retval = 1; } - restore_flags(flags); + spin_unlock_irqrestore(&fep->lock,flags); return(retval); } @@ -1216,7 +1231,7 @@ static phy_info_t const * const phy_info[] = { }; /* ------------------------------------------------------------------------- */ - +#if !defined(CONFIG_M532x) #ifdef CONFIG_RPXCLASSIC static void mii_link_interrupt(void *dev_id); @@ -1224,6 +1239,7 @@ mii_link_interrupt(void *dev_id); static irqreturn_t mii_link_interrupt(int irq, void * dev_id, struct pt_regs * regs); #endif +#endif #if defined(CONFIG_M5272) @@ -1384,13 +1400,13 @@ static void __inline__ fec_request_intrs(struct net_device *dev) { volatile unsigned char *icrp; volatile unsigned long *imrp; - int i; + int i, ilip; b = (fep->index) ? MCFICM_INTC1 : MCFICM_INTC0; icrp = (volatile unsigned char *) (MCF_IPSBAR + b + MCFINTC_ICR0); - for (i = 23; (i < 36); i++) - icrp[i] = 0x23; + for (i = 23, ilip = 0x28; (i < 36); i++) + icrp[i] = ilip--; imrp = (volatile unsigned long *) (MCF_IPSBAR + b + MCFINTC_IMRH); @@ -1618,6 +1634,159 @@ static void __inline__ fec_uncache(unsigned long addr) /* ------------------------------------------------------------------------- */ +#elif defined(CONFIG_M532x) +/* + * Code specific for M532x + */ +static void __inline__ fec_request_intrs(struct net_device *dev) +{ + struct fec_enet_private *fep; + int b; + static const struct idesc { + char *name; + unsigned short irq; + } *idp, id[] = { + { "fec(TXF)", 36 }, + { "fec(TXB)", 37 }, + { "fec(TXFIFO)", 38 }, + { "fec(TXCR)", 39 }, + { "fec(RXF)", 40 }, + { "fec(RXB)", 41 }, + { "fec(MII)", 42 }, + { "fec(LC)", 43 }, + { "fec(HBERR)", 44 }, + { "fec(GRA)", 45 }, + { "fec(EBERR)", 46 }, + { "fec(BABT)", 47 }, + { "fec(BABR)", 48 }, + { NULL }, + }; + + fep = netdev_priv(dev); + b = (fep->index) ? 128 : 64; + + /* Setup interrupt handlers. */ + for (idp = id; idp->name; idp++) { + if (request_irq(b+idp->irq,fec_enet_interrupt,0,idp->name,dev)!=0) + printk("FEC: Could not allocate %s IRQ(%d)!\n", + idp->name, b+idp->irq); + } + + /* Unmask interrupts */ + MCF_INTC0_ICR36 = 0x2; + MCF_INTC0_ICR37 = 0x2; + MCF_INTC0_ICR38 = 0x2; + MCF_INTC0_ICR39 = 0x2; + MCF_INTC0_ICR40 = 0x2; + MCF_INTC0_ICR41 = 0x2; + MCF_INTC0_ICR42 = 0x2; + MCF_INTC0_ICR43 = 0x2; + MCF_INTC0_ICR44 = 0x2; + MCF_INTC0_ICR45 = 0x2; + MCF_INTC0_ICR46 = 0x2; + MCF_INTC0_ICR47 = 0x2; + MCF_INTC0_ICR48 = 0x2; + + MCF_INTC0_IMRH &= ~( + MCF_INTC_IMRH_INT_MASK36 | + MCF_INTC_IMRH_INT_MASK37 | + MCF_INTC_IMRH_INT_MASK38 | + MCF_INTC_IMRH_INT_MASK39 | + MCF_INTC_IMRH_INT_MASK40 | + MCF_INTC_IMRH_INT_MASK41 | + MCF_INTC_IMRH_INT_MASK42 | + MCF_INTC_IMRH_INT_MASK43 | + MCF_INTC_IMRH_INT_MASK44 | + MCF_INTC_IMRH_INT_MASK45 | + MCF_INTC_IMRH_INT_MASK46 | + MCF_INTC_IMRH_INT_MASK47 | + MCF_INTC_IMRH_INT_MASK48 ); + + /* Set up gpio outputs for MII lines */ + MCF_GPIO_PAR_FECI2C |= (0 | + MCF_GPIO_PAR_FECI2C_PAR_MDC_EMDC | + MCF_GPIO_PAR_FECI2C_PAR_MDIO_EMDIO); + MCF_GPIO_PAR_FEC = (0 | + MCF_GPIO_PAR_FEC_PAR_FEC_7W_FEC | + MCF_GPIO_PAR_FEC_PAR_FEC_MII_FEC); +} + +static void __inline__ fec_set_mii(struct net_device *dev, struct fec_enet_private *fep) +{ + volatile fec_t *fecp; + + fecp = fep->hwp; + fecp->fec_r_cntrl = OPT_FRAME_SIZE | 0x04; + fecp->fec_x_cntrl = 0x00; + + /* + * Set MII speed to 2.5 MHz + */ + fep->phy_speed = ((((MCF_CLK / 2) / (2500000 / 10)) + 5) / 10) * 2; + fecp->fec_mii_speed = fep->phy_speed; + + fec_restart(dev, 0); +} + +static void __inline__ fec_get_mac(struct net_device *dev) +{ + struct fec_enet_private *fep = netdev_priv(dev); + volatile fec_t *fecp; + unsigned char *iap, tmpaddr[ETH_ALEN]; + + fecp = fep->hwp; + + if (FEC_FLASHMAC) { + /* + * Get MAC address from FLASH. + * If it is all 1's or 0's, use the default. + */ + iap = FEC_FLASHMAC; + if ((iap[0] == 0) && (iap[1] == 0) && (iap[2] == 0) && + (iap[3] == 0) && (iap[4] == 0) && (iap[5] == 0)) + iap = fec_mac_default; + if ((iap[0] == 0xff) && (iap[1] == 0xff) && (iap[2] == 0xff) && + (iap[3] == 0xff) && (iap[4] == 0xff) && (iap[5] == 0xff)) + iap = fec_mac_default; + } else { + *((unsigned long *) &tmpaddr[0]) = fecp->fec_addr_low; + *((unsigned short *) &tmpaddr[4]) = (fecp->fec_addr_high >> 16); + iap = &tmpaddr[0]; + } + + memcpy(dev->dev_addr, iap, ETH_ALEN); + + /* Adjust MAC if using default MAC address */ + if (iap == fec_mac_default) + dev->dev_addr[ETH_ALEN-1] = fec_mac_default[ETH_ALEN-1] + fep->index; +} + +static void __inline__ fec_enable_phy_intr(void) +{ +} + +static void __inline__ fec_disable_phy_intr(void) +{ +} + +static void __inline__ fec_phy_ack_intr(void) +{ +} + +static void __inline__ fec_localhw_setup(void) +{ +} + +/* + * Do not need to make region uncached on 532x. + */ +static void __inline__ fec_uncache(unsigned long addr) +{ +} + +/* ------------------------------------------------------------------------- */ + + #else /* @@ -1985,9 +2154,12 @@ fec_enet_open(struct net_device *dev) mii_do_cmd(dev, fep->phy->config); mii_do_cmd(dev, phy_cmd_config); /* display configuration */ - /* FIXME: use netif_carrier_{on,off} ; this polls - * until link is up which is wrong... could be - * 30 seconds or more we are trapped in here. -jgarzik + /* Poll until the PHY tells us its configuration + * (not link state). + * Request is initiated by mii_do_cmd above, but answer + * comes by interrupt. + * This should take about 25 usec per register at 2.5 MHz, + * and we read approximately 5 registers. */ while(!fep->sequence_done) schedule(); @@ -2253,15 +2425,11 @@ int __init fec_enet_init(struct net_device *dev) */ fec_request_intrs(dev); - /* Clear and enable interrupts */ - fecp->fec_ievent = 0xffc00000; - fecp->fec_imask = (FEC_ENET_TXF | FEC_ENET_TXB | - FEC_ENET_RXF | FEC_ENET_RXB | FEC_ENET_MII); fecp->fec_hash_table_high = 0; fecp->fec_hash_table_low = 0; fecp->fec_r_buff_size = PKT_MAXBLR_SIZE; fecp->fec_ecntrl = 2; - fecp->fec_r_des_active = 0x01000000; + fecp->fec_r_des_active = 0; dev->base_addr = (unsigned long)fecp; @@ -2281,6 +2449,11 @@ int __init fec_enet_init(struct net_device *dev) /* setup MII interface */ fec_set_mii(dev, fep); + /* Clear and enable interrupts */ + fecp->fec_ievent = 0xffc00000; + fecp->fec_imask = (FEC_ENET_TXF | FEC_ENET_TXB | + FEC_ENET_RXF | FEC_ENET_RXB | FEC_ENET_MII); + /* Queue up command to detect the PHY and initialize the * remainder of the interface. */ @@ -2312,11 +2485,6 @@ fec_restart(struct net_device *dev, int duplex) fecp->fec_ecntrl = 1; udelay(10); - /* Enable interrupts we wish to service. - */ - fecp->fec_imask = (FEC_ENET_TXF | FEC_ENET_TXB | - FEC_ENET_RXF | FEC_ENET_RXB | FEC_ENET_MII); - /* Clear any outstanding interrupt. */ fecp->fec_ievent = 0xffc00000; @@ -2408,7 +2576,12 @@ fec_restart(struct net_device *dev, int duplex) /* And last, enable the transmit and receive processing. */ fecp->fec_ecntrl = 2; - fecp->fec_r_des_active = 0x01000000; + fecp->fec_r_des_active = 0; + + /* Enable interrupts we wish to service. + */ + fecp->fec_imask = (FEC_ENET_TXF | FEC_ENET_TXB | + FEC_ENET_RXF | FEC_ENET_RXB | FEC_ENET_MII); } static void @@ -2420,9 +2593,16 @@ fec_stop(struct net_device *dev) fep = netdev_priv(dev); fecp = fep->hwp; - fecp->fec_x_cntrl = 0x01; /* Graceful transmit stop */ - - while(!(fecp->fec_ievent & FEC_ENET_GRA)); + /* + ** We cannot expect a graceful transmit stop without link !!! + */ + if (fep->link) + { + fecp->fec_x_cntrl = 0x01; /* Graceful transmit stop */ + udelay(10); + if (!(fecp->fec_ievent & FEC_ENET_GRA)) + printk("fec_stop : Graceful transmit stop did not complete !\n"); + } /* Whack a reset. We should wait for this. */ diff --git a/drivers/net/fs_enet/fs_enet-mii.c b/drivers/net/fs_enet/fs_enet-mii.c index c6770377ef8..0cd07150bf4 100644 --- a/drivers/net/fs_enet/fs_enet-mii.c +++ b/drivers/net/fs_enet/fs_enet-mii.c @@ -431,8 +431,7 @@ static struct fs_enet_mii_bus *create_bus(const struct fs_mii_bus_info *bi) return bus; err: - if (bus) - kfree(bus); + kfree(bus); return ERR_PTR(ret); } diff --git a/drivers/net/wireless/ipw2200.c b/drivers/net/wireless/ipw2200.c index 081a8999666..a8a8f975432 100644 --- a/drivers/net/wireless/ipw2200.c +++ b/drivers/net/wireless/ipw2200.c @@ -1229,12 +1229,6 @@ static struct ipw_fw_error *ipw_alloc_error_log(struct ipw_priv *priv) return error; } -static void ipw_free_error_log(struct ipw_fw_error *error) -{ - if (error) - kfree(error); -} - static ssize_t show_event_log(struct device *d, struct device_attribute *attr, char *buf) { @@ -1296,10 +1290,9 @@ static ssize_t clear_error(struct device *d, const char *buf, size_t count) { struct ipw_priv *priv = dev_get_drvdata(d); - if (priv->error) { - ipw_free_error_log(priv->error); - priv->error = NULL; - } + + kfree(priv->error); + priv->error = NULL; return count; } @@ -1970,8 +1963,7 @@ static void ipw_irq_tasklet(struct ipw_priv *priv) struct ipw_fw_error *error = ipw_alloc_error_log(priv); ipw_dump_error_log(priv, error); - if (error) - ipw_free_error_log(error); + kfree(error); } #endif } else { @@ -11693,10 +11685,8 @@ static void ipw_pci_remove(struct pci_dev *pdev) } } - if (priv->error) { - ipw_free_error_log(priv->error); - priv->error = NULL; - } + kfree(priv->error); + priv->error = NULL; #ifdef CONFIG_IPW2200_PROMISCUOUS ipw_prom_free(priv); diff --git a/drivers/pcmcia/m8xx_pcmcia.c b/drivers/pcmcia/m8xx_pcmcia.c index 0e07d953511..d0f68ab8f04 100644 --- a/drivers/pcmcia/m8xx_pcmcia.c +++ b/drivers/pcmcia/m8xx_pcmcia.c @@ -157,7 +157,7 @@ MODULE_LICENSE("Dual MPL/GPL"); static int pcmcia_schlvl = PCMCIA_SCHLVL; -static spinlock_t events_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(events_lock); #define PCMCIA_SOCKET_KEY_5V 1 @@ -644,7 +644,7 @@ static struct platform_device m8xx_device = { }; static u32 pending_events[PCMCIA_SOCKETS_NO]; -static spinlock_t pending_event_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(pending_event_lock); static irqreturn_t m8xx_interrupt(int irq, void *dev, struct pt_regs *regs) { diff --git a/drivers/rapidio/rio-access.c b/drivers/rapidio/rio-access.c index b9fab2ae3a3..8b56bbdd011 100644 --- a/drivers/rapidio/rio-access.c +++ b/drivers/rapidio/rio-access.c @@ -17,8 +17,8 @@ * These interrupt-safe spinlocks protect all accesses to RIO * configuration space and doorbell access. */ -static spinlock_t rio_config_lock = SPIN_LOCK_UNLOCKED; -static spinlock_t rio_doorbell_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(rio_config_lock); +static DEFINE_SPINLOCK(rio_doorbell_lock); /* * Wrappers for all RIO configuration access functions. They just check diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c index 5396beec30d..1cb61a761cb 100644 --- a/drivers/rtc/class.c +++ b/drivers/rtc/class.c @@ -94,7 +94,9 @@ exit_kfree: kfree(rtc); exit_idr: + mutex_lock(&idr_lock); idr_remove(&rtc_idr, id); + mutex_unlock(&idr_lock); exit: dev_err(dev, "rtc core: unable to register %s, err = %d\n", diff --git a/drivers/rtc/rtc-ds1553.c b/drivers/rtc/rtc-ds1553.c index ecafbad41a2..762521a1419 100644 --- a/drivers/rtc/rtc-ds1553.c +++ b/drivers/rtc/rtc-ds1553.c @@ -226,7 +226,7 @@ static int ds1553_rtc_ioctl(struct device *dev, unsigned int cmd, struct rtc_plat_data *pdata = platform_get_drvdata(pdev); if (pdata->irq < 0) - return -ENOIOCTLCMD; + return -ENOIOCTLCMD; /* fall back into rtc-dev's emulation */ switch (cmd) { case RTC_AIE_OFF: pdata->irqen &= ~RTC_AF; diff --git a/drivers/rtc/rtc-sa1100.c b/drivers/rtc/rtc-sa1100.c index ab486fbc828..9cd1cb304bb 100644 --- a/drivers/rtc/rtc-sa1100.c +++ b/drivers/rtc/rtc-sa1100.c @@ -45,7 +45,7 @@ static unsigned long rtc_freq = 1024; static struct rtc_time rtc_alarm; -static spinlock_t sa1100_rtc_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(sa1100_rtc_lock); static int rtc_update_alarm(struct rtc_time *alrm) { diff --git a/drivers/rtc/rtc-vr41xx.c b/drivers/rtc/rtc-vr41xx.c index 33e029207e2..4b9291dd444 100644 --- a/drivers/rtc/rtc-vr41xx.c +++ b/drivers/rtc/rtc-vr41xx.c @@ -93,7 +93,7 @@ static void __iomem *rtc2_base; static unsigned long epoch = 1970; /* Jan 1 1970 00:00:00 */ -static spinlock_t rtc_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(rtc_lock); static char rtc_name[] = "RTC"; static unsigned long periodic_frequency; static unsigned long periodic_count; diff --git a/drivers/s390/block/dasd_eer.c b/drivers/s390/block/dasd_eer.c index 2d946b6ca07..2d8af709947 100644 --- a/drivers/s390/block/dasd_eer.c +++ b/drivers/s390/block/dasd_eer.c @@ -89,7 +89,7 @@ struct eerbuffer { }; static LIST_HEAD(bufferlist); -static spinlock_t bufferlock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(bufferlock); static DECLARE_WAIT_QUEUE_HEAD(dasd_eer_read_wait_queue); /* diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig index c84b02aec1f..96a81cd1761 100644 --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -501,7 +501,7 @@ config SCSI_ATA_PIIX tristate "Intel PIIX/ICH SATA support" depends on SCSI_SATA && PCI help - This option enables support for ICH5 Serial ATA. + This option enables support for ICH5/6/7/8 Serial ATA. If PATA support was enabled previously, this enables support for select Intel PIIX/ICH PATA host controllers. diff --git a/drivers/scsi/ahci.c b/drivers/scsi/ahci.c index 4bb77f62b3b..f0594677771 100644 --- a/drivers/scsi/ahci.c +++ b/drivers/scsi/ahci.c @@ -48,7 +48,7 @@ #include <asm/io.h> #define DRV_NAME "ahci" -#define DRV_VERSION "1.3" +#define DRV_VERSION "2.0" enum { diff --git a/drivers/scsi/ata_piix.c b/drivers/scsi/ata_piix.c index 521b718763f..94b1261a259 100644 --- a/drivers/scsi/ata_piix.c +++ b/drivers/scsi/ata_piix.c @@ -93,7 +93,7 @@ #include <linux/libata.h> #define DRV_NAME "ata_piix" -#define DRV_VERSION "1.10" +#define DRV_VERSION "2.00" enum { PIIX_IOCFG = 0x54, /* IDE I/O configuration register */ diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c index 6c66877be2b..d1c1c30d123 100644 --- a/drivers/scsi/libata-core.c +++ b/drivers/scsi/libata-core.c @@ -88,6 +88,10 @@ int libata_fua = 0; module_param_named(fua, libata_fua, int, 0444); MODULE_PARM_DESC(fua, "FUA support (0=off, 1=on)"); +static int ata_probe_timeout = ATA_TMOUT_INTERNAL / HZ; +module_param(ata_probe_timeout, int, 0444); +MODULE_PARM_DESC(ata_probe_timeout, "Set ATA probing timeout (seconds)"); + MODULE_AUTHOR("Jeff Garzik"); MODULE_DESCRIPTION("Library module for ATA devices"); MODULE_LICENSE("GPL"); @@ -777,11 +781,9 @@ void ata_std_dev_select (struct ata_port *ap, unsigned int device) void ata_dev_select(struct ata_port *ap, unsigned int device, unsigned int wait, unsigned int can_sleep) { - if (ata_msg_probe(ap)) { + if (ata_msg_probe(ap)) ata_port_printk(ap, KERN_INFO, "ata_dev_select: ENTER, ata%u: " - "device %u, wait %u\n", - ap->id, device, wait); - } + "device %u, wait %u\n", ap->id, device, wait); if (wait) ata_wait_idle(ap); @@ -950,7 +952,8 @@ void ata_port_flush_task(struct ata_port *ap) */ if (!cancel_delayed_work(&ap->port_task)) { if (ata_msg_ctl(ap)) - ata_port_printk(ap, KERN_DEBUG, "%s: flush #2\n", __FUNCTION__); + ata_port_printk(ap, KERN_DEBUG, "%s: flush #2\n", + __FUNCTION__); flush_workqueue(ata_wq); } @@ -1059,7 +1062,7 @@ unsigned ata_exec_internal(struct ata_device *dev, spin_unlock_irqrestore(ap->lock, flags); - rc = wait_for_completion_timeout(&wait, ATA_TMOUT_INTERNAL); + rc = wait_for_completion_timeout(&wait, ata_probe_timeout); ata_port_flush_task(ap); @@ -1081,7 +1084,7 @@ unsigned ata_exec_internal(struct ata_device *dev, if (ata_msg_warn(ap)) ata_dev_printk(dev, KERN_WARNING, - "qc timeout (cmd 0x%x)\n", command); + "qc timeout (cmd 0x%x)\n", command); } spin_unlock_irqrestore(ap->lock, flags); @@ -1093,9 +1096,9 @@ unsigned ata_exec_internal(struct ata_device *dev, if (qc->flags & ATA_QCFLAG_FAILED && !qc->err_mask) { if (ata_msg_warn(ap)) - ata_dev_printk(dev, KERN_WARNING, + ata_dev_printk(dev, KERN_WARNING, "zero err_mask for failed " - "internal command, assuming AC_ERR_OTHER\n"); + "internal command, assuming AC_ERR_OTHER\n"); qc->err_mask |= AC_ERR_OTHER; } @@ -1132,6 +1135,33 @@ unsigned ata_exec_internal(struct ata_device *dev, } /** + * ata_do_simple_cmd - execute simple internal command + * @dev: Device to which the command is sent + * @cmd: Opcode to execute + * + * Execute a 'simple' command, that only consists of the opcode + * 'cmd' itself, without filling any other registers + * + * LOCKING: + * Kernel thread context (may sleep). + * + * RETURNS: + * Zero on success, AC_ERR_* mask on failure + */ +unsigned int ata_do_simple_cmd(struct ata_device *dev, u8 cmd) +{ + struct ata_taskfile tf; + + ata_tf_init(dev, &tf); + + tf.command = cmd; + tf.flags |= ATA_TFLAG_DEVICE; + tf.protocol = ATA_PROT_NODATA; + + return ata_exec_internal(dev, &tf, NULL, DMA_NONE, NULL, 0); +} + +/** * ata_pio_need_iordy - check if iordy needed * @adev: ATA device * @@ -1193,8 +1223,8 @@ int ata_dev_read_id(struct ata_device *dev, unsigned int *p_class, int rc; if (ata_msg_ctl(ap)) - ata_dev_printk(dev, KERN_DEBUG, "%s: ENTER, host %u, dev %u\n", - __FUNCTION__, ap->id, dev->devno); + ata_dev_printk(dev, KERN_DEBUG, "%s: ENTER, host %u, dev %u\n", + __FUNCTION__, ap->id, dev->devno); ata_dev_select(ap, dev->devno, 1, 1); /* select device 0/1 */ @@ -1263,9 +1293,9 @@ int ata_dev_read_id(struct ata_device *dev, unsigned int *p_class, return 0; err_out: - if (ata_msg_warn(ap)) + if (ata_msg_warn(ap)) ata_dev_printk(dev, KERN_WARNING, "failed to IDENTIFY " - "(%s, err_mask=0x%x)\n", reason, err_mask); + "(%s, err_mask=0x%x)\n", reason, err_mask); return rc; } @@ -1318,19 +1348,21 @@ int ata_dev_configure(struct ata_device *dev, int print_info) int i, rc; if (!ata_dev_enabled(dev) && ata_msg_info(ap)) { - ata_dev_printk(dev, KERN_INFO, "%s: ENTER/EXIT (host %u, dev %u) -- nodev\n", - __FUNCTION__, ap->id, dev->devno); + ata_dev_printk(dev, KERN_INFO, + "%s: ENTER/EXIT (host %u, dev %u) -- nodev\n", + __FUNCTION__, ap->id, dev->devno); return 0; } if (ata_msg_probe(ap)) - ata_dev_printk(dev, KERN_DEBUG, "%s: ENTER, host %u, dev %u\n", - __FUNCTION__, ap->id, dev->devno); + ata_dev_printk(dev, KERN_DEBUG, "%s: ENTER, host %u, dev %u\n", + __FUNCTION__, ap->id, dev->devno); /* print device capabilities */ if (ata_msg_probe(ap)) - ata_dev_printk(dev, KERN_DEBUG, "%s: cfg 49:%04x 82:%04x 83:%04x " - "84:%04x 85:%04x 86:%04x 87:%04x 88:%04x\n", + ata_dev_printk(dev, KERN_DEBUG, + "%s: cfg 49:%04x 82:%04x 83:%04x 84:%04x " + "85:%04x 86:%04x 87:%04x 88:%04x\n", __FUNCTION__, id[49], id[82], id[83], id[84], id[85], id[86], id[87], id[88]); @@ -1402,14 +1434,16 @@ int ata_dev_configure(struct ata_device *dev, int print_info) ata_id_major_version(id), ata_mode_string(xfer_mask), (unsigned long long)dev->n_sectors, - dev->cylinders, dev->heads, dev->sectors); + dev->cylinders, dev->heads, + dev->sectors); } if (dev->id[59] & 0x100) { dev->multi_count = dev->id[59] & 0xff; if (ata_msg_info(ap)) - ata_dev_printk(dev, KERN_INFO, "ata%u: dev %u multi count %u\n", - ap->id, dev->devno, dev->multi_count); + ata_dev_printk(dev, KERN_INFO, + "ata%u: dev %u multi count %u\n", + ap->id, dev->devno, dev->multi_count); } dev->cdb_len = 16; @@ -1422,8 +1456,8 @@ int ata_dev_configure(struct ata_device *dev, int print_info) rc = atapi_cdb_len(id); if ((rc < 12) || (rc > ATAPI_CDB_LEN)) { if (ata_msg_warn(ap)) - ata_dev_printk(dev, KERN_WARNING, - "unsupported CDB len\n"); + ata_dev_printk(dev, KERN_WARNING, + "unsupported CDB len\n"); rc = -EINVAL; goto err_out_nosup; } @@ -1466,8 +1500,8 @@ int ata_dev_configure(struct ata_device *dev, int print_info) err_out_nosup: if (ata_msg_probe(ap)) - ata_dev_printk(dev, KERN_DEBUG, - "%s: EXIT, err\n", __FUNCTION__); + ata_dev_printk(dev, KERN_DEBUG, + "%s: EXIT, err\n", __FUNCTION__); return rc; } @@ -3527,7 +3561,7 @@ void swap_buf_le16(u16 *buf, unsigned int buf_words) * Inherited from caller. */ -void ata_mmio_data_xfer(struct ata_device *adev, unsigned char *buf, +void ata_mmio_data_xfer(struct ata_device *adev, unsigned char *buf, unsigned int buflen, int write_data) { struct ata_port *ap = adev->ap; @@ -3573,7 +3607,7 @@ void ata_mmio_data_xfer(struct ata_device *adev, unsigned char *buf, * Inherited from caller. */ -void ata_pio_data_xfer(struct ata_device *adev, unsigned char *buf, +void ata_pio_data_xfer(struct ata_device *adev, unsigned char *buf, unsigned int buflen, int write_data) { struct ata_port *ap = adev->ap; @@ -3607,7 +3641,7 @@ void ata_pio_data_xfer(struct ata_device *adev, unsigned char *buf, * @buflen: buffer length * @write_data: read/write * - * Transfer data from/to the device data register by PIO. Do the + * Transfer data from/to the device data register by PIO. Do the * transfer with interrupts disabled. * * LOCKING: @@ -4946,31 +4980,9 @@ int ata_port_offline(struct ata_port *ap) return 0; } -/* - * Execute a 'simple' command, that only consists of the opcode 'cmd' itself, - * without filling any other registers - */ -static int ata_do_simple_cmd(struct ata_device *dev, u8 cmd) -{ - struct ata_taskfile tf; - int err; - - ata_tf_init(dev, &tf); - - tf.command = cmd; - tf.flags |= ATA_TFLAG_DEVICE; - tf.protocol = ATA_PROT_NODATA; - - err = ata_exec_internal(dev, &tf, NULL, DMA_NONE, NULL, 0); - if (err) - ata_dev_printk(dev, KERN_ERR, "%s: ata command failed: %d\n", - __FUNCTION__, err); - - return err; -} - -static int ata_flush_cache(struct ata_device *dev) +int ata_flush_cache(struct ata_device *dev) { + unsigned int err_mask; u8 cmd; if (!ata_try_flush_cache(dev)) @@ -4981,17 +4993,41 @@ static int ata_flush_cache(struct ata_device *dev) else cmd = ATA_CMD_FLUSH; - return ata_do_simple_cmd(dev, cmd); + err_mask = ata_do_simple_cmd(dev, cmd); + if (err_mask) { + ata_dev_printk(dev, KERN_ERR, "failed to flush cache\n"); + return -EIO; + } + + return 0; } static int ata_standby_drive(struct ata_device *dev) { - return ata_do_simple_cmd(dev, ATA_CMD_STANDBYNOW1); + unsigned int err_mask; + + err_mask = ata_do_simple_cmd(dev, ATA_CMD_STANDBYNOW1); + if (err_mask) { + ata_dev_printk(dev, KERN_ERR, "failed to standby drive " + "(err_mask=0x%x)\n", err_mask); + return -EIO; + } + + return 0; } static int ata_start_drive(struct ata_device *dev) { - return ata_do_simple_cmd(dev, ATA_CMD_IDLEIMMEDIATE); + unsigned int err_mask; + + err_mask = ata_do_simple_cmd(dev, ATA_CMD_IDLEIMMEDIATE); + if (err_mask) { + ata_dev_printk(dev, KERN_ERR, "failed to start drive " + "(err_mask=0x%x)\n", err_mask); + return -EIO; + } + + return 0; } /** @@ -5212,7 +5248,7 @@ static void ata_host_init(struct ata_port *ap, struct Scsi_Host *host, ap->msg_enable = 0x00FF; #elif defined(ATA_DEBUG) ap->msg_enable = ATA_MSG_DRV | ATA_MSG_INFO | ATA_MSG_CTL | ATA_MSG_WARN | ATA_MSG_ERR; -#else +#else ap->msg_enable = ATA_MSG_DRV | ATA_MSG_ERR | ATA_MSG_WARN; #endif @@ -5709,6 +5745,7 @@ int ata_pci_device_resume(struct pci_dev *pdev) static int __init ata_init(void) { + ata_probe_timeout *= HZ; ata_wq = create_workqueue("ata"); if (!ata_wq) return -ENOMEM; @@ -5733,7 +5770,7 @@ module_init(ata_init); module_exit(ata_exit); static unsigned long ratelimit_time; -static spinlock_t ata_ratelimit_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(ata_ratelimit_lock); int ata_ratelimit(void) { diff --git a/drivers/scsi/libata-eh.c b/drivers/scsi/libata-eh.c index 823385981a7..bf5a72aca8a 100644 --- a/drivers/scsi/libata-eh.c +++ b/drivers/scsi/libata-eh.c @@ -93,6 +93,38 @@ static int ata_ering_map(struct ata_ering *ering, return rc; } +static unsigned int ata_eh_dev_action(struct ata_device *dev) +{ + struct ata_eh_context *ehc = &dev->ap->eh_context; + + return ehc->i.action | ehc->i.dev_action[dev->devno]; +} + +static void ata_eh_clear_action(struct ata_device *dev, + struct ata_eh_info *ehi, unsigned int action) +{ + int i; + + if (!dev) { + ehi->action &= ~action; + for (i = 0; i < ATA_MAX_DEVICES; i++) + ehi->dev_action[i] &= ~action; + } else { + /* doesn't make sense for port-wide EH actions */ + WARN_ON(!(action & ATA_EH_PERDEV_MASK)); + + /* break ehi->action into ehi->dev_action */ + if (ehi->action & action) { + for (i = 0; i < ATA_MAX_DEVICES; i++) + ehi->dev_action[i] |= ehi->action & action; + ehi->action &= ~action; + } + + /* turn off the specified per-dev action */ + ehi->dev_action[dev->devno] &= ~action; + } +} + /** * ata_scsi_timed_out - SCSI layer time out callback * @cmd: timed out SCSI command @@ -702,32 +734,11 @@ static void ata_eh_detach_dev(struct ata_device *dev) ap->flags |= ATA_FLAG_SCSI_HOTPLUG; } - spin_unlock_irqrestore(ap->lock, flags); -} - -static void ata_eh_clear_action(struct ata_device *dev, - struct ata_eh_info *ehi, unsigned int action) -{ - int i; + /* clear per-dev EH actions */ + ata_eh_clear_action(dev, &ap->eh_info, ATA_EH_PERDEV_MASK); + ata_eh_clear_action(dev, &ap->eh_context.i, ATA_EH_PERDEV_MASK); - if (!dev) { - ehi->action &= ~action; - for (i = 0; i < ATA_MAX_DEVICES; i++) - ehi->dev_action[i] &= ~action; - } else { - /* doesn't make sense for port-wide EH actions */ - WARN_ON(!(action & ATA_EH_PERDEV_MASK)); - - /* break ehi->action into ehi->dev_action */ - if (ehi->action & action) { - for (i = 0; i < ATA_MAX_DEVICES; i++) - ehi->dev_action[i] |= ehi->action & action; - ehi->action &= ~action; - } - - /* turn off the specified per-dev action */ - ehi->dev_action[dev->devno] &= ~action; - } + spin_unlock_irqrestore(ap->lock, flags); } /** @@ -1592,7 +1603,7 @@ static int ata_eh_revalidate_and_attach(struct ata_port *ap, unsigned int action; dev = &ap->device[i]; - action = ehc->i.action | ehc->i.dev_action[dev->devno]; + action = ata_eh_dev_action(dev); if (action & ATA_EH_REVALIDATE && ata_dev_enabled(dev)) { if (ata_port_offline(ap)) { diff --git a/drivers/scsi/libata-scsi.c b/drivers/scsi/libata-scsi.c index 93d18a74c40..2915bca691e 100644 --- a/drivers/scsi/libata-scsi.c +++ b/drivers/scsi/libata-scsi.c @@ -222,9 +222,7 @@ int ata_cmd_ioctl(struct scsi_device *scsidev, void __user *arg) && copy_to_user(arg + sizeof(args), argbuf, argsize)) rc = -EFAULT; error: - if (argbuf) - kfree(argbuf); - + kfree(argbuf); return rc; } diff --git a/drivers/scsi/libata.h b/drivers/scsi/libata.h index bdd48889709..c325679d9b5 100644 --- a/drivers/scsi/libata.h +++ b/drivers/scsi/libata.h @@ -29,7 +29,7 @@ #define __LIBATA_H__ #define DRV_NAME "libata" -#define DRV_VERSION "1.30" /* must be exactly four chars */ +#define DRV_VERSION "2.00" /* must be exactly four chars */ struct ata_scsi_args { struct ata_device *dev; @@ -50,6 +50,7 @@ extern void ata_port_flush_task(struct ata_port *ap); extern unsigned ata_exec_internal(struct ata_device *dev, struct ata_taskfile *tf, const u8 *cdb, int dma_dir, void *buf, unsigned int buflen); +extern unsigned int ata_do_simple_cmd(struct ata_device *dev, u8 cmd); extern int ata_dev_read_id(struct ata_device *dev, unsigned int *p_class, int post_reset, u16 *id); extern int ata_dev_configure(struct ata_device *dev, int print_info); @@ -64,6 +65,7 @@ extern int ata_check_atapi_dma(struct ata_queued_cmd *qc); extern void ata_dev_select(struct ata_port *ap, unsigned int device, unsigned int wait, unsigned int can_sleep); extern void swap_buf_le16(u16 *buf, unsigned int buf_words); +extern int ata_flush_cache(struct ata_device *dev); extern void ata_dev_init(struct ata_device *dev); extern int ata_task_ioctl(struct scsi_device *scsidev, void __user *arg); extern int ata_cmd_ioctl(struct scsi_device *scsidev, void __user *arg); diff --git a/drivers/scsi/sata_nv.c b/drivers/scsi/sata_nv.c index d18e7e0932e..5cc42c6054e 100644 --- a/drivers/scsi/sata_nv.c +++ b/drivers/scsi/sata_nv.c @@ -44,7 +44,7 @@ #include <linux/libata.h> #define DRV_NAME "sata_nv" -#define DRV_VERSION "0.9" +#define DRV_VERSION "2.0" enum { NV_PORTS = 2, diff --git a/drivers/scsi/sata_sil.c b/drivers/scsi/sata_sil.c index bc9f918a7f2..51d86d750e8 100644 --- a/drivers/scsi/sata_sil.c +++ b/drivers/scsi/sata_sil.c @@ -46,12 +46,13 @@ #include <linux/libata.h> #define DRV_NAME "sata_sil" -#define DRV_VERSION "1.0" +#define DRV_VERSION "2.0" enum { /* * host flags */ + SIL_FLAG_NO_SATA_IRQ = (1 << 28), SIL_FLAG_RERR_ON_DMA_ACT = (1 << 29), SIL_FLAG_MOD15WRITE = (1 << 30), @@ -62,8 +63,9 @@ enum { * Controller IDs */ sil_3112 = 0, - sil_3512 = 1, - sil_3114 = 2, + sil_3112_no_sata_irq = 1, + sil_3512 = 2, + sil_3114 = 3, /* * Register offsets @@ -123,8 +125,8 @@ static const struct pci_device_id sil_pci_tbl[] = { { 0x1095, 0x3512, PCI_ANY_ID, PCI_ANY_ID, 0, 0, sil_3512 }, { 0x1095, 0x3114, PCI_ANY_ID, PCI_ANY_ID, 0, 0, sil_3114 }, { 0x1002, 0x436e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, sil_3112 }, - { 0x1002, 0x4379, PCI_ANY_ID, PCI_ANY_ID, 0, 0, sil_3112 }, - { 0x1002, 0x437a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, sil_3112 }, + { 0x1002, 0x4379, PCI_ANY_ID, PCI_ANY_ID, 0, 0, sil_3112_no_sata_irq }, + { 0x1002, 0x437a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, sil_3112_no_sata_irq }, { } /* terminate list */ }; @@ -217,6 +219,16 @@ static const struct ata_port_info sil_port_info[] = { .udma_mask = 0x3f, /* udma0-5 */ .port_ops = &sil_ops, }, + /* sil_3112_no_sata_irq */ + { + .sht = &sil_sht, + .host_flags = SIL_DFL_HOST_FLAGS | SIL_FLAG_MOD15WRITE | + SIL_FLAG_NO_SATA_IRQ, + .pio_mask = 0x1f, /* pio0-4 */ + .mwdma_mask = 0x07, /* mwdma0-2 */ + .udma_mask = 0x3f, /* udma0-5 */ + .port_ops = &sil_ops, + }, /* sil_3512 */ { .sht = &sil_sht, @@ -437,6 +449,10 @@ static irqreturn_t sil_interrupt(int irq, void *dev_instance, if (unlikely(!ap || ap->flags & ATA_FLAG_DISABLED)) continue; + /* turn off SATA_IRQ if not supported */ + if (ap->flags & SIL_FLAG_NO_SATA_IRQ) + bmdma2 &= ~SIL_DMA_SATA_IRQ; + if (bmdma2 == 0xffffffff || !(bmdma2 & (SIL_DMA_COMPLETE | SIL_DMA_SATA_IRQ))) continue; @@ -474,8 +490,9 @@ static void sil_thaw(struct ata_port *ap) ata_chk_status(ap); ata_bmdma_irq_clear(ap); - /* turn on SATA IRQ */ - writel(SIL_SIEN_N, mmio_base + sil_port[ap->port_no].sien); + /* turn on SATA IRQ if supported */ + if (!(ap->flags & SIL_FLAG_NO_SATA_IRQ)) + writel(SIL_SIEN_N, mmio_base + sil_port[ap->port_no].sien); /* turn on IRQ */ tmp = readl(mmio_base + SIL_SYSCFG); diff --git a/drivers/scsi/sata_sil24.c b/drivers/scsi/sata_sil24.c index c8b477c6724..b5f8fa95567 100644 --- a/drivers/scsi/sata_sil24.c +++ b/drivers/scsi/sata_sil24.c @@ -31,7 +31,7 @@ #include <asm/io.h> #define DRV_NAME "sata_sil24" -#define DRV_VERSION "0.24" +#define DRV_VERSION "0.3" /* * Port request block (PRB) 32 bytes diff --git a/drivers/scsi/sata_svw.c b/drivers/scsi/sata_svw.c index c94b870cf37..7566c2cabaf 100644 --- a/drivers/scsi/sata_svw.c +++ b/drivers/scsi/sata_svw.c @@ -54,7 +54,7 @@ #endif /* CONFIG_PPC_OF */ #define DRV_NAME "sata_svw" -#define DRV_VERSION "1.8" +#define DRV_VERSION "2.0" enum { /* Taskfile registers offsets */ diff --git a/drivers/scsi/sata_uli.c b/drivers/scsi/sata_uli.c index f668c997e9a..64f3c1aeed2 100644 --- a/drivers/scsi/sata_uli.c +++ b/drivers/scsi/sata_uli.c @@ -37,7 +37,7 @@ #include <linux/libata.h> #define DRV_NAME "sata_uli" -#define DRV_VERSION "0.6" +#define DRV_VERSION "1.0" enum { uli_5289 = 0, diff --git a/drivers/scsi/sata_via.c b/drivers/scsi/sata_via.c index 322890b400a..67c3d299977 100644 --- a/drivers/scsi/sata_via.c +++ b/drivers/scsi/sata_via.c @@ -47,7 +47,7 @@ #include <asm/io.h> #define DRV_NAME "sata_via" -#define DRV_VERSION "1.2" +#define DRV_VERSION "2.0" enum board_ids_enum { vt6420, diff --git a/drivers/scsi/sata_vsc.c b/drivers/scsi/sata_vsc.c index 6d0c4f18e65..616fd9634b4 100644 --- a/drivers/scsi/sata_vsc.c +++ b/drivers/scsi/sata_vsc.c @@ -47,7 +47,7 @@ #include <linux/libata.h> #define DRV_NAME "sata_vsc" -#define DRV_VERSION "1.2" +#define DRV_VERSION "2.0" enum { /* Interrupt register offsets (from chip base address) */ @@ -443,16 +443,12 @@ err_out: } -/* - * Intel 31244 is supposed to be identical. - * Compatibility is untested as of yet. - */ static const struct pci_device_id vsc_sata_pci_tbl[] = { - { PCI_VENDOR_ID_VITESSE, PCI_DEVICE_ID_VITESSE_VSC7174, + { PCI_VENDOR_ID_VITESSE, 0x7174, PCI_ANY_ID, PCI_ANY_ID, 0x10600, 0xFFFFFF, 0 }, - { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_GD31244, + { PCI_VENDOR_ID_INTEL, 0x3200, PCI_ANY_ID, PCI_ANY_ID, 0x10600, 0xFFFFFF, 0 }, - { } + { } /* terminate list */ }; diff --git a/drivers/sn/ioc3.c b/drivers/sn/ioc3.c index 501316b198e..ed946311d3a 100644 --- a/drivers/sn/ioc3.c +++ b/drivers/sn/ioc3.c @@ -26,7 +26,7 @@ static DECLARE_RWSEM(ioc3_devices_rwsem); static struct ioc3_submodule *ioc3_submodules[IOC3_MAX_SUBMODULES]; static struct ioc3_submodule *ioc3_ethernet; -static rwlock_t ioc3_submodules_lock = RW_LOCK_UNLOCKED; +static DEFINE_RWLOCK(ioc3_submodules_lock); /* NIC probing code */ diff --git a/drivers/video/au1100fb.c b/drivers/video/au1100fb.c index d63c3f48585..9ef68cd83bb 100644 --- a/drivers/video/au1100fb.c +++ b/drivers/video/au1100fb.c @@ -743,8 +743,7 @@ void __exit au1100fb_cleanup(void) { driver_unregister(&au1100fb_driver); - if (drv_info.opt_mode) - kfree(drv_info.opt_mode); + kfree(drv_info.opt_mode); } module_init(au1100fb_init); diff --git a/drivers/video/backlight/hp680_bl.c b/drivers/video/backlight/hp680_bl.c index a71e984c93d..ffc72ae3ada 100644 --- a/drivers/video/backlight/hp680_bl.c +++ b/drivers/video/backlight/hp680_bl.c @@ -27,7 +27,7 @@ static int hp680bl_suspended; static int current_intensity = 0; -static spinlock_t bl_lock = SPIN_LOCK_UNLOCKED; +static DEFINE_SPINLOCK(bl_lock); static struct backlight_device *hp680_backlight_device; static void hp680bl_send_intensity(struct backlight_device *bd) |